一. 自定义View的种类

1. 继承原生控件或者布局

2. 组合原生控件

3. 继承View

二. 简单的自定义View例子

1. 基础搭建

  • 创建一个类继承于View,在构造方法里面初始化画笔。

  • 重写onDraw方法,在里面画圆。

  • 在布局文件中加入CircleView。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class CircleView extends View {

//画笔变量
Paint mPaint;

/**
* 构造方法 java代码new
* @param context
*/
public CircleView(Context context) {
super(context);

init();
}

/**
* 构造方法 xml代码声明
* @param context
* @param attrs
*/
public CircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);

init();
}

/**
* 不会自动调用
* 一般是在xml构造方法中主动调用
* 如View有style属性时
* @param context
* @param attrs
* @param defStyleAttr
*/
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

init();
}

/**
* API21之后才用
* 不会自动调用
* 一般是在xml构造方法中主动调用
* 如View有style属性时
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);

init();
}

//画笔初始化
private void init(){
//创建画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

//设置画笔颜色是蓝色
mPaint.setColor(Color.BLUE);

//设置画笔宽度
mPaint.setStrokeWidth(5f);

//设置画笔模式为填充
mPaint.setStyle(Paint.Style.FILL);
}

//重写onDraw重绘
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//获取控件宽高
int width = getWidth();
int height = getHeight();

//设置圆的半径
int radius = Math.min(width,height) / 2;

//画圆
canvas.drawCircle(width >> 1, height >> 1, radius, mPaint);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<swu.xl.selfview_base.CircleView
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="#000000"
/>

</RelativeLayout>

运行效果:

2. 支持wrap_content功能

  • 在自定义的View内部重写onMeasure方法。
  • 在onMeasure方法中设置wrap_content的默认宽 / 高值。
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
public class CircleView extends View {

//画笔变量
Paint mPaint;

/**
* 构造方法 java代码new
* @param context
*/
public CircleView(Context context) {
super(context);

init();
}

/**
* 构造方法 xml代码声明
* @param context
* @param attrs
*/
public CircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);

init();
}

/**
* 不会自动调用
* 一般是在xml构造方法中主动调用
* 如View有style属性时
* @param context
* @param attrs
* @param defStyleAttr
*/
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

init();
}

/**
* API21之后才用
* 不会自动调用
* 一般是在xml构造方法中主动调用
* 如View有style属性时
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);

init();
}

//画笔初始化
private void init(){
//创建画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

//设置画笔颜色是蓝色
mPaint.setColor(Color.BLUE);

//设置画笔宽度
mPaint.setStrokeWidth(5f);

//设置画笔模式为填充
mPaint.setStyle(Paint.Style.FILL);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

// 获取宽-测量规则的模式和大小
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);

// 获取高-测量规则的模式和大小
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

// 设置wrap_content的默认宽 / 高值
// 默认宽/高的设定并无固定依据,根据需要灵活设置
// 类似TextView,ImageView等针对wrap_content均在onMeasure()对设置默认宽 / 高值有特殊处理,具体读者可以自行查看
int mWidth = 400;
int mHeight = 400;

// 当布局参数设置为wrap_content时,设置默认值
if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, mHeight);
// 宽 / 高任意一个布局参数为= wrap_content时,都设置默认值
} else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, heightSize);
} else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(widthSize, mHeight);
}
}

//重写onDraw重绘
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//获取控件宽高
int width = getWidth();
int height = getHeight();

//设置圆的半径
int radius = Math.min(width,height) / 2;

//画圆
canvas.drawCircle(width >> 1, height >> 1, radius, mPaint);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<swu.xl.selfview_base.CircleView
android:layout_centerInParent="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#000000"
/>

</RelativeLayout>

运行效果:

3. 支持Padding功能

绘制时考虑传入的padding属性值(四个方向)。在自定义View类的重写onDraw进行设置。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public class CircleView extends View {

//画笔变量
Paint mPaint;

/**
* 构造方法 java代码new
* @param context
*/
public CircleView(Context context) {
super(context);

init();
}

/**
* 构造方法 xml代码声明
* @param context
* @param attrs
*/
public CircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);

init();
}

/**
* 不会自动调用
* 一般是在xml构造方法中主动调用
* 如View有style属性时
* @param context
* @param attrs
* @param defStyleAttr
*/
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

init();
}

/**
* API21之后才用
* 不会自动调用
* 一般是在xml构造方法中主动调用
* 如View有style属性时
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);

init();
}

//画笔初始化
private void init(){
//创建画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

//设置画笔颜色是蓝色
mPaint.setColor(Color.BLUE);

//设置画笔宽度
mPaint.setStrokeWidth(5f);

//设置画笔模式为填充
mPaint.setStyle(Paint.Style.FILL);
}

//重写onDraw重绘
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//获取传入的padding
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();

//获取控件宽高
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;

//设置圆的半径
int radius = Math.min(width,height) / 2;

//画圆
canvas.drawCircle(paddingLeft + (width >> 1), paddingTop + (height >> 1), radius, mPaint);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<swu.xl.selfview_base.CircleView
android:layout_centerInParent="true"
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="#000000"
android:padding="10dp"
/>

</RelativeLayout>

运行效果:

4. 自定义属性

系统自带的属性在xml里面基本上都是以android开头,有时候我们需要自定义一些属性,其步骤:

  • 在values目录下创建自定义属性的xml文件。
  • 在自定义View的构造方法中解析自定义属性的值。
  • 在布局文件中使用自定义属性。
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package swu.xl.selfview_base;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.Nullable;

public class CircleView extends View {

//画笔变量
Paint mPaint;

/**
* 构造方法 java代码new
* @param context
*/
public CircleView(Context context) {
super(context);

init();
}

/**
* 构造方法 xml代码声明
* @param context
* @param attrs
*/
public CircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);

init();

//加载自定义属性集合
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);

//加载自定义的颜色属性
//第一个参数是设置的颜色
//第二个参数是默认的颜色
int color = typedArray.getColor(R.styleable.CircleView_circle_color, Color.GRAY);
mPaint.setColor(color);

//释放资源
typedArray.recycle();
}

/**
* 不会自动调用
* 一般是在xml构造方法中主动调用
* 如View有style属性时
* @param context
* @param attrs
* @param defStyleAttr
*/
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

init();
}

/**
* API21之后才用
* 不会自动调用
* 一般是在xml构造方法中主动调用
* 如View有style属性时
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);

init();
}

//画笔初始化
private void init(){
//创建画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

//设置画笔颜色是蓝色
mPaint.setColor(Color.BLUE);

//设置画笔宽度
mPaint.setStrokeWidth(5f);

//设置画笔模式为填充
mPaint.setStyle(Paint.Style.FILL);
}

//重写onDraw重绘
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//获取传入的padding
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();

//获取控件宽高
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;

//设置圆的半径
int radius = Math.min(width,height) / 2;

//画圆
canvas.drawCircle(paddingLeft + (width >> 1), paddingTop + (height >> 1), radius, mPaint);
}
}
1
2
3
4
5
6
7
<!--value文件夹下面的attr_circle.xml文件-->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="circle_color" format="color"/>
</declare-styleable>
</resources>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<swu.xl.selfview_base.CircleView
android:layout_centerInParent="true"
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="#000000"
android:padding="10dp"
app:circle_color="#ff4081"
/>

</RelativeLayout>

运行方式:

自定义属性更多的内容参考:自定义View-自定义属性

参考文章

Android自定义View的三种方式:继承布局,继承原生控件,继承View

手把手教你写一个完整的自定义View