When you first start writing custom Views in Android, it’s common to feel unsure where to begin. Generally, there are two approaches:
- From scratch: Extend
View and implement the appearance entirely through calculation and drawing. - Extend an existing View: Add child views to an existing component, or override methods to change its behavior.
Subclassing View#
Here is the simplest example of extending View:
1
2
3
4
5
6
7
8
9
10
| public class SimpleView extends View {
public SimpleView(Context context) {
super(context);
}
public SimpleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
|
If you don’t need XML attributes, you can omit the AttributeSet constructor — but then the View cannot be used in XML layouts.
Custom Attributes#
AttributeSet parses XML attributes into an array for code access. Attribute types must be declared in advance, typically in res/values/attrs.xml:
1
2
3
4
5
6
7
| <declare-styleable name="SimpleView">
<attr name="showContent" format="boolean" />
<attr name="showPosition" format="enum">
<enum name="left" value="0" />
<enum name="right" value="1" />
</attr>
</declare-styleable>
|
Supported types: boolean, string, dimension, enum, fraction, reference, color. You can also use | to specify multiple types.
Using Custom Attributes in XML#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:hxq="http://schemas.android.com/apk/res/blog.haoxiqiang"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="blog.haoxiqiang.MainActivity" >
<blog.haoxiqiang.SimpleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
hxq:showContent="true"
hxq:showPosition="right"
android:text="@string/hello_world" />
</RelativeLayout>
|
The namespace format: http://schemas.android.com/apk/res/[your package name] or http://schemas.android.com/apk/res/auto.
Complete Example: View with Circle and Text#
This custom View draws a blue circle with optional text. showContent toggles the text, and showPosition controls whether it draws on the left or right side of the screen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| package blog.haoxiqiang;
public class SimpleView extends View {
// attr
private boolean showContent = false;
private int position = 0;
// param
private Paint mCirclePaint;
private Paint mTextPaint;
private final int radius = 100;
private int width = 0;
private boolean initLayout = false;
public SimpleView(Context context) {
super(context);
init(context);
}
public SimpleView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SimpleView);
if (typedArray != null) {
showContent = typedArray.getBoolean(R.styleable.SimpleView_showContent, false);
position = typedArray.getInt(R.styleable.SimpleView_showPosition, 0);
}
typedArray.recycle();
init(context);
}
private void init(Context context) {
mCirclePaint = new Paint();
mCirclePaint.setColor(Color.BLUE);
mCirclePaint.setStrokeWidth(8);
mTextPaint = new Paint();
mTextPaint.setColor(Color.RED);
mTextPaint.setStrokeWidth(5);
mTextPaint.setTextSize(35.0f);
this.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (!initLayout) {
width = SimpleView.this.getWidth();
Log.i("onGlobalLayout", "initLayout:" + width);
SimpleView.this.invalidate();
initLayout = true;
}
}
});
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int x = position == 0 ? radius : width - 2 * radius;
x = x < 0 ? 0 : x;
canvas.drawCircle(x, 100, radius, mCirclePaint);
if (showContent) {
canvas.drawText("SimpleView", x, 100, mTextPaint);
}
}
}
|
The next part will discuss how onMeasure works.
References#