Creating custom Views in Android
This tutorials describes how to create custom Views with Android. It is based on Eclipse 4.3, Java 1.6 and Android 4.3.
Table of Contents
Views are responsible for measuring, layouting and drawing themselves and their child elements (in case of a ViewGroup). Views are also responsible for saving their UI state and handling touch events.
The Android platform provides a standard set of views (widgets) for creating user interfaces. You can create custom views by extending the
View
class or one of its subclasses.Own views are typically classified as compound views or custom views. Compound views are constructed based on existing views with some predefined layout and logic while customer views draw themself.
For drawing view use the
onDraw()
method. In this method you receive a Canvas
object which allows you to perform drawing operations on it, e.g. draw lines, circle, text or bitmaps. If the view should be re-drawn you call the invalidate()
method which triggers a call to the onDraw()
method of this view.You can implement your custom layout manager by extending the
ViewGroup
class. It is good practice to store any additional layout parameters in an inner class of your ViewGroup
implementation. For example ViewGroup.LayoutParams
implements command layout parameters, andLinearLayout.LayoutParams
implements additional parameters specific to LinearLayout, as for example the layout_weight parameter.Custom and compound views can be used in layout files. For this you need to use the full qualified name in the layout file, e.g. using the package and class name.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
<de.vogella.android.ownview.MyDrawView
android:id="@+id/myDrawView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
Every
View
class support the creating of an image of its current display. The following coding shows an example for that.# Build the Drawing Cache
view.buildDrawingCache();
# Create Bitmap
Bitmap cache = view.getDrawingCache();
# Save Bitmap
saveBitmap(cache);
view.destroyDrawingCache();
A view is displayed if it is attached to a window.
A view has several lifecycle hooks. The
onAttachedToWindow()
is called once the window is available and the onDetachedFromWindow()
is used when the view is removed from its parent and if the parent is attached to a window. This happens for example if the activity is recycled (e.g. via thefinished()
method call) or if the view is recycled in a ListView
. TheonDetachedFromWindow()
method can be used to stop animations and to clean up resources used by the view.Traverals lifecycle events contain out of Animate, Measure, Layout and Draw.
All views must know how to measure and layout themselves. The
requestLayout()
method call tells the view to measure and layout itself. As this operation may influence the layout of other views it calls also requestLayout()
of its parent.The
onMeasure()
method determines the size for the view and its children. It must set the dimension via the setMeasuredDimension()
method is this method call before returning.The
onLayout()
positions the views based on the result of the onMeasure()
method call. This call happens typically once, which onMeasure()
can happens once.You can define additional attributes for your compound or custom views. To define additional attributes create an
attrs.xml
file in your res/values
folder. The following shows an example of attributes defined for a new view called ColorOptionsView
.<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ColorOptionsView">
<attr name="titleText" format="string" localization="suggested" />
<attr name="valueColor" format="color" />
</declare-styleable>
</resources>
To use these attributes in your layout file you have to declare them in the XML header. In the following listing this is done via the
xmlns:custom
part of the code. These attributes are also assigned to the view.<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
<!-- define new name space for your attributes -->
xmlns:custom="http://schemas.android.com/apk/res/com.vogella.android.view.compoundview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<!-- Assume that this is your new component. It uses your new attributes -->
<com.vogella.android.view.compoundview.ColorOptionsView
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
custom:titleText="Background color"
custom:valueColor="@android:color/holo_green_light"
/>
</LinearLayout>
The following example shows how you components can access these attributes.
package com.vogella.android.view.compoundview;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ColorOptionsView extends View {
private View mValue;
private ImageView mImage;
public ColorOptionsView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.Options, 0, 0);
String titleText = a.getString(R.styleable.Options_titleText);
int valueColor = a.getColor(R.styleable.Options_valueColor,
android.R.color.holo_blue_light);
a.recycle();
// more stuff
}
}
Compound Views (also known as Compound Components) are pre-configured
ViewGroups
which can be used as one unit. For such a control you define a layout with View
components and pre-define theView
interaction.You would define a layout file and extend the corresponding
ViewGroup
class. In this class you inflate the layout file and implement the View
connection logicCreate a new Android project called com.vogella.android.customview.compoundview with an activitycalled MainActivity.
Create the following attributes file called
attrs.xml
in your res/xml
folder.<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Options">
<attr name="titleText" format="string" localization="suggested" />
<attr name="valueColor" format="color" />
</declare-styleable>
</resources>
Change the layout file for the Activity to the following.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res/com.vogella.android.view.compoundview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:showDividers="middle"
android:divider="?android:attr/listDivider"
tools:context=".MainActivity" >
<com.vogella.android.view.compoundview.ColorOptionsView
android:id="@+id/view1"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:background="?android:selectableItemBackground"
android:onClick="onClicked"
custom:titleText="Background color"
custom:valueColor="@android:color/holo_green_light"
/>
<com.vogella.android.view.compoundview.ColorOptionsView
android:id="@+id/view2"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:background="?android:selectableItemBackground"
android:onClick="onClicked"
custom:titleText="Foreground color"
custom:valueColor="@android:color/holo_orange_dark"
/>
</LinearLayout>
Create the following layout file called
view_color_options.xml
for your compound view.<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_centerVertical="true"
android:layout_marginLeft="16dp"
android:textSize="18sp"
/>
<View
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_centerVertical="true"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="16dp"
android:layout_centerVertical="true"
android:visibility="gone"
/>
</merge>
Create the following compound view .
package com.vogella.android.customview.compoundview;
import com.vogella.android.view.compoundview.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ColorOptionsView extends LinearLayout {
private View mValue;
private ImageView mImage;
public ColorOptionsView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ColorOptionsView, 0, 0);
String titleText = a.getString(R.styleable.ColorOptionsView_titleText);
int valueColor = a.getColor(R.styleable.ColorOptionsView_valueColor,
android.R.color.holo_blue_light);
a.recycle();
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.view_color_options, this, true);
TextView title = (TextView) getChildAt(0);
title.setText(titleText);
mValue = getChildAt(1);
mValue.setBackgroundColor(valueColor);
mImage = (ImageView) getChildAt(2);
}
public ColorOptionsView(Context context) {
this(context, null);
}
public void setValueColor(int color) {
mValue.setBackgroundColor(color);
}
public void setImageVisible(boolean visible) {
mImage.setVisibility(visible ? View.VISIBLE : View.GONE);
}
}
Change your activity to the following code and run your application.
package com.vogella.android.customview.compoundview;
import com.vogella.android.view.compoundview.R;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
public void onClicked(View view) {
String text = view.getId() == R.id.view1 ? "Background" : "Foreground";
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
}
The running application should look like the following screenshot.
By extending the
View
class or one of its subclasses you can create your own Views
.To draw your
Views
you typically use the 2D Canvas API.The Canvas API allows to create complex graphical effects.
You paint on a
Bitmap
surface. The Canvas
class provides the drawing methods to draw on a bitmap and the Paint
class specifies how you draw on the bitmap.The
Canvas
object contains the bitmap on which you draw. It also provides methods for drawing operations, e.g. drawARGB()
for drawing a color, drawBitmap()
to draw a Bitmap
, drawText()
to draw a text, drawRoundRect()
to draw a rectangle with rounded corners and much more.For drawing on the
Canvas
object you use an object of type Paint
.The
Paint
class allows to specify the color, font and certain effects for the drawing operation.The
setStyle()
method allows to specify if the only the outline (Paint.Style.STROKE
), the filled part (Paint.Style.FILL
) or both (Paint.Style.STROKE_AND_FILL
)should be drawn.You can set the alpha channel of the
Paint
via the setAlpha()
method.Via Shaders you can define that the
Paint
is filled with more than one color.A shader allows to define for a
Paint
object the content which should be drawn. For example you can use a BitmapShader
to define that a bitmap should be used to draw. This allows you for example to draw an image with rounded corners. Simply define a BitmapShader
for your Paint
object and use the drawRoundRect()
method to draw a rectancle with rounded corners.Other Shaders provided by the Android platform are
LinearGradient
, RadialGradient
andSweepGradient
for drawing color gradients.To use a Shaders assign it to your
Paint
object via the setShader()
method.If the area which is filled is larger than the Shaders you can define via the Shader tile mode how the rest should be filled. The
Shader.TileMode.CLAMP
constant defines that the edge corners should be used to fill the extra space, the Shader.TileMode.MIRROR
constant defines that the image is mirrored and Shader.TileMode.REPEAT
defines that the image will be repeated.Most standard view can save there state so that it can be persisted by the system. The Android system call the
onSaveInstanceState()
method and the onRestoreInstanceState(Parcable)
to save and restore the view state.The convention is to extend
View.BasedSavedState
as a static inner class in the view for persisting the data.Android searches based on the ID of the view in the layout for the view and pass a
Bundle
to the view which the view can use to restore its state.You should save and restore the user interface state as the user left it, e.g. the scroll position or the active selection.
You find an example for a custom view (including touch support) under the following URL: Android custom views and touch .
No comments:
Post a Comment