手把手教你写一个完整的自定义View
2018-03-31 10:52
459 查看
参考链接:https://blog.csdn.net/carson_ho/article/details/62037696
自定义View是Android开发者必须了解的基础
今天,我将手把手教你写一个自定义View,并理清自定义View所有应该的注意点
如果不在
具体原因请看文章:为什么你的自定义View wrap_content不起作用?
支持padding & margin
如果不支持,那么
对于继承View的控件,padding是在draw()中处理
对于继承ViewGroup的控件,padding和margin会直接影响measure和layout过程
1. 启动线程/ 动画:使用
2. 停止线程/ 动画:使用
在下面的例子中,我将讲解:如何实现一个基本的自定义View(继承VIew)
如何自身支持wrap_content & padding属性
如何为自定义View提供自定义属性(如颜色等等)
实例说明:画一个实心圆
布局文件添加自定义View组件
注意点设置(支持wrap_content & padding属性自定义属性等等)
下面我将逐个步骤进行说明:
步骤1:创建自定义View类(继承View类)CircleView.java
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
特别注意:
1. View的构造函数一共有4个,具体使用请看:深入理解View的构造函数和
理解View的构造函数
2. 对于绘制内容为何在复写onDraw()里实现,具体请看我写的文章:自定义View Draw过程- 最易懂的自定义View原理系列(4)步骤2:在布局文件中添加自定义View类的组件activity_main.xml
2
3
4
5
6
7
8
9
10
11
12
13
14
15
步骤3:在MainActivity类设置显示MainActivity.java
2
3
4
5
6
7
8
好了,至此,一个基本的自定义View已经实现了。接下来继续看自定义View所有应该注意的点:如何手动支持wrap_content属性
如何手动支持padding属性
如何为自定义View提供自定义属性(如颜色等等)
2
3
4
5
6
如果不手动设置支持
如果不手动设置支持padding属性,那么padding属性在自定义View中是不会生效的。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
但有些时候需要一些系统所没有的属性,称为自定义属性
使用步骤有如下:
在values目录下创建自定义属性的xml文件
在自定义View的构造方法中解析自定义属性的值
在布局文件中使用自定义属性
下面我将对每个步骤进行具体介绍
2
3
4
5
6
7
8
9
10
11
12
对于自定义属性类型 & 格式如下:
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
至此,一个较为规范的自定义View已经完成了。
1.自定义View基础 - 最易懂的自定义View原理系列(1)2.自定义View Measure过程 - 最易懂的自定义View原理系列(2)3. 自定义View Layout过程 - 最易懂的自定义View原理系列(3)4.自定义View Draw过程- 最易懂的自定义View原理系列(4)
自定义View是Android开发者必须了解的基础
今天,我将手把手教你写一个自定义View,并理清自定义View所有应该的注意点
目录
1. 自定义View的分类
自定义View一共分为两大类,具体如下图:2. 具体介绍 & 使用场景
对于自定义View的类型介绍及使用场景如下图:3. 使用注意点
在使用自定义View时有很多注意点(坑),希望大家要非常留意:3.1 支持特殊属性
支持wrap_content如果不在
onMeasure()中对
wrap_content作特殊处理,那么
wrap_content属性将失效
具体原因请看文章:为什么你的自定义View wrap_content不起作用?
支持padding & margin
如果不支持,那么
padding和
margin(ViewGroup情况)的属性将失效
对于继承View的控件,padding是在draw()中处理
对于继承ViewGroup的控件,padding和margin会直接影响measure和layout过程
3.2 多线程应直接使用post方式
View的内部本身提供了post系列的方法,完全可以替代Handler的作用,使用起来更加方便、直接。3.3 避免内存泄露
主要针对View中含有线程或动画的情况:当View退出或不可见时,记得及时停止该View包含的线程和动画,否则会造成内存泄露问题。启动或停止线程/ 动画的方式:1. 启动线程/ 动画:使用
view.onAttachedToWindow(),因为该方法调用的时机是当包含View的Activity启动的时刻
2. 停止线程/ 动画:使用
view.onDetachedFromWindow(),因为该方法调用的时机是当包含View的Activity退出或当前View被remove的时刻
3.4 处理好滑动冲突
当View带有滑动嵌套情况时,必须要处理好滑动冲突,否则会严重影响View的显示效果。4. 具体实例
接下来,我将用自定义View中最常用的继承View来说明自定义View的具体应用和需要注意的点4.1 继承VIew的介绍
在下面的例子中,我将讲解:如何实现一个基本的自定义View(继承VIew)
如何自身支持wrap_content & padding属性
如何为自定义View提供自定义属性(如颜色等等)
实例说明:画一个实心圆
4.2 具体步骤
创建自定义View类(继承View类)布局文件添加自定义View组件
注意点设置(支持wrap_content & padding属性自定义属性等等)
下面我将逐个步骤进行说明:
步骤1:创建自定义View类(继承View类)CircleView.java
// 用于绘制自定义View的具体内容 // 具体绘制是在复写的onDraw()内实现 public class CircleView extends View { // 设置画笔变量 Paint mPaint1; // 自定义View有四个构造函数 // 如果View是在Java代码里面new的,则调用第一个构造函数 public CircleView(Context context){ super(context); // 在构造函数里初始化画笔的操作 init(); } // 如果View是在.xml里声明的,则调用第二个构造函数 // 自定义属性是从AttributeSet参数传进来的 public CircleView(Context context,AttributeSet attrs){ super(context, attrs); init(); } // 不会自动调用 // 一般是在第二个构造函数里主动调用 // 如View有style属性时 public CircleView(Context context,AttributeSet attrs,int defStyleAttr ){ super(context, attrs,defStyleAttr); init(); } //API21之后才使用 // 不会自动调用 // 一般是在第二个构造函数里主动调用 // 如View有style属性时 public CircleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } // 画笔初始化 private void init() { // 创建画笔 mPaint1 = new Paint (); // 设置画笔颜色为蓝色 mPaint1.setColor(Color.BLUE); // 设置画笔宽度为10px mPaint1.setStrokeWidth(5f); //设置画笔模式为填充 mPaint1.setStyle(Paint.Style.FILL); } // 复写onDraw()进行绘制 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 获取控件的高度和宽度 int width = getWidth(); int height = getHeight(); // 设置圆的半径 = 宽,高最小值的2分之1 int r = Math.min(width, height)/2; // 画出圆(蓝色) // 圆心 = 控件的中央,半径 = 宽,高最小值的2分之1 canvas.drawCircle(width/2,height/2,r,mPaint1); } }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
特别注意:
1. View的构造函数一共有4个,具体使用请看:深入理解View的构造函数和
理解View的构造函数
2. 对于绘制内容为何在复写onDraw()里实现,具体请看我写的文章:自定义View Draw过程- 最易懂的自定义View原理系列(4)步骤2:在布局文件中添加自定义View类的组件activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="scut.carson_ho.diy_view.MainActivity"> <!-- 注意添加自定义View组件的标签名:包名 + 自定义View类名--> <!-- 控件背景设置为黑色--> <scut.carson_ho.diy_view.CircleView android:layout_width="match_parent" android:layout_height="150dp" android:background="#000000" </RelativeLayout>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
步骤3:在MainActivity类设置显示MainActivity.java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }1
2
3
4
5
6
7
8
好了,至此,一个基本的自定义View已经实现了。接下来继续看自定义View所有应该注意的点:如何手动支持wrap_content属性
如何手动支持padding属性
如何为自定义View提供自定义属性(如颜色等等)
a. 手动支持wrap_content属性
先来看wrap_content & match_parent属性的区别// 视图的宽和高被设定成刚好适应视图内容的最小尺寸 android:layout_width="wrap_content" // 视图的宽和高延伸至充满整个父布局 android:layout_width="match_parent" // 在Android API 8之前叫作"fill_parent"1
2
3
4
5
6
如果不手动设置支持
wrap_content属性,那么
wrap_content属性是不会生效(显示效果同
match_parent)具体原因 & 解决方案请看我写的文章:为什么你的自定义View wrap_content不起作用?
b. 支持padding属性
padding属性:用于设置控件内容相对控件边缘的边距;区别与margin属性(同样称为:边距):控件边缘相对父控件的边距(父控件控制),具体区别如下:
如果不手动设置支持padding属性,那么padding属性在自定义View中是不会生效的。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="scut.carson_ho.diy_view.MainActivity"> <scut.carson_ho.diy_view.CircleView android:layout_width="match_parent" android:layout_height="match_parent" /** 添加Padding属性,但不会生效 **/ android:padding="20dp" /> </RelativeLayout>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
解决方案
绘制时考虑传入的padding属性值(四个方向)。在自定义View类的复写onDraw()进行设置CircleView.java// 仅看复写的onDraw() @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 获取传入的padding值 final int paddingLeft = getPaddingLeft(); final int paddingRight = getPaddingRight(); final int paddingTop = getPaddingTop(); final int paddingBottom = getPaddingBottom(); // 获取绘制内容的高度和宽度(考虑了四个方向的padding值) int width = getWidth() - paddingLeft - paddingRight ; int height = getHeight() - paddingTop - paddingBottom ; // 设置圆的半径 = 宽,高最小值的2分之1 int r = Math.min(width, height)/2; // 画出圆(蓝色) // 圆心 = 控件的中央,半径 = 宽,高最小值的2分之1 canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,r,mPaint1); }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
c. 提供自定义属性
系统自带属性,如// 基本是以android开头 android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000000" android:padding="30dp"1
2
3
4
5
6
但有些时候需要一些系统所没有的属性,称为自定义属性
使用步骤有如下:
在values目录下创建自定义属性的xml文件
在自定义View的构造方法中解析自定义属性的值
在布局文件中使用自定义属性
下面我将对每个步骤进行具体介绍
步骤1:在values目录下创建自定义属性的xml文件
attrs_circle_view.xml<?xml version="1.0" encoding="utf-8"?> <resources> <!--自定义属性集合:CircleView--> <!--在该集合下,设置不同的自定义属性--> <declare-styleable name="CircleView"> <!--在attr标签下设置需要的自定义属性--> <!--此处定义了一个设置图形的颜色:circle_color属性,格式是color,代表颜色--> <!--格式有很多种,如资源id(reference)等等--> <attr name="circle_color" format="color"/> </declare-styleable> </resources>1
2
3
4
5
6
7
8
9
10
11
12
对于自定义属性类型 & 格式如下:
<-- 1. reference:使用某一资源ID --> <declare-styleable name="名称"> <attr name="background" format="reference" /> </declare-styleable> // 使用格式 <ImageView android:layout_width="42dip" android:layout_height="42dip" android:background="@drawable/图片ID" /> <-- 2. color:颜色值 --> <declare-styleable name="名称"> <attr name="textColor" format="color" /> </declare-styleable> // 格式使用 <TextView android:layout_width="42dip" android:layout_height="42dip" android:textColor="#00FF00" /> <-- 3. boolean:布尔值 --> <declare-styleable name="名称"> <attr name="focusable" format="boolean" /> </declare-styleable> // 格式使用 <Button android:layout_width="42dip" android:layout_height="42dip" android:focusable="true" /> <-- 4. dimension:尺寸值 --> <declare-styleable name="名称"> <attr name="layout_width" format="dimension" /> </declare-styleable> // 格式使用: <Button android:layout_width="42dip" android:layout_height="42dip" /> <-- 5. float:浮点值 --> <declare-styleable name="AlphaAnimation"> <attr name="fromAlpha" format="float" /> <attr name="toAlpha" format="float" /> </declare-styleable> // 格式使用 <alpha android:fromAlpha="1.0" android:toAlpha="0.7" /> <-- 6. integer:整型值 --> <declare-styleable name="AnimatedRotateDrawable"> <attr name="frameDuration" format="integer" /> <attr name="framesCount" format="integer" /> </declare-styleable> // 格式使用 <animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" android:frameDuration="100" android:framesCount="12" /> <-- 7. string:字符串 --> <declare-styleable name="MapView"> <attr name="apiKey" format="string" /> </declare-styleable> // 格式使用 <com.google.android.maps.MapView android:apiKey="0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g" /> <-- 8. fraction:百分数 --> <declare-styleable name="RotateDrawable"> <attr name="pivotX" format="fraction" /> <attr name="pivotY" format="fraction" /> </declare-styleable> // 格式使用 <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:pivotX="200%" android:pivotY="300%" /> <-- 9. enum:枚举值 --> <declare-styleable name="名称"> <attr name="orientation"> <enum name="horizontal" value="0" /> <enum name="vertical" value="1" /> </attr> </declare-styleable> // 格式使用 <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" /> <-- 10. flag:位或运算 --> <declare-styleable name="名称"> <attr name="windowSoftInputMode"> <flag name="stateUnspecified" value="0" /> <flag name="stateUnchanged" value="1" /> <flag name="stateHidden" value="2" /> <flag name="stateAlwaysHidden" value="3" /> <flag name="stateVisible" value="4" /> <flag name="stateAlwaysVisible" value="5" /> <flag name="adjustUnspecified" value="0x00" /> <flag name="adjustResize" value="0x10" /> <flag name="adjustPan" value="0x20" /> <flag name="adjustNothing" value="0x30" /> </attr> </declare-styleable>、 // 使用 <activity android:name=".StyleAndThemeActivity" android:label="@string/app_name" android:windowSoftInputMode="stateUnspecified | stateUnchanged | stateHidden" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <-- 特别注意:属性定义时可以指定多种类型值 --> <declare-styleable name="名称"> <attr name="background" format="reference|color" /> </declare-styleable> // 使用 <ImageView android:layout_width="42dip" android:layout_height="42dip" android:background="@drawable/图片ID|#00FF00" />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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
步骤2:在自定义View的构造方法中解析自定义属性的值
此处是需要解析circle_color属性的值// 该构造函数需要重写 public CircleView(Context context, AttributeSet attrs) { this(context, attrs,0); // 原来是:super(context,attrs); init(); public CircleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // 加载自定义属性集合CircleView TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.CircleView); // 解析集合中的属性circle_color属性 // 该属性的id为:R.styleable.CircleView_circle_color // 将解析的属性传入到画圆的画笔颜色变量当中(本质上是自定义画圆画笔的颜色) // 第二个参数是默认设置颜色(即无指定circle_color情况下使用) mColor = a.getColor(R.styleable.CircleView_circle_color,Color.RED); // 解析后释放资源 a.recycle(); init();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
步骤3:在布局文件中使用自定义属性
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <!--必须添加schemas声明才能使用自定义属性--> 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="scut.carson_ho.diy_view.MainActivity" > <!-- 注意添加自定义View组件的标签名:包名 + 自定义View类名--> <!-- 控件背景设置为黑色--> <scut.carson_ho.diy_view.CircleView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#000000" android:padding="30dp" <!--设置自定义颜色--> app:circle_color="#FF4081" /> </RelativeLayout>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
至此,一个较为规范的自定义View已经完成了。
完整代码下载
Carson_Ho的github:自定义View的具体应用5. 总结
如果希望继续了解自定义View的原理,请参考文章:1.自定义View基础 - 最易懂的自定义View原理系列(1)2.自定义View Measure过程 - 最易懂的自定义View原理系列(2)3. 自定义View Layout过程 - 最易懂的自定义View原理系列(3)4.自定义View Draw过程- 最易懂的自定义View原理系列(4)
相关文章推荐
- 手把手教你写一个完整的自定义View
- 手把手教你写一个完整的自定义View
- 一个简单的自定义View 大风车
- 自定义View实践-一个简单的棋类游戏
- 自定义View_一个非常简洁的柱状图
- Android Training - 创建自定义的Views(Lesson 1 - 创建一个View类)
- mvp获取数据 完整的购物车 +自定义view的加减号
- 自定义---DialogCellEditor 一个完整的实例 3
- 使用xib封装一个自定义view的步骤
- Android自定义View入门---自定义一个TextView
- 自定义View----一个Demo带你彻底掌握View的滑动冲突(五)
- ActionBar上显示自己自定义的一个SearchView(19)
- [微信小程序]实现一个自定义遮罩层组件(完整示例代码附效果图)
- 一个简单的Android自定义view详解
- 一个简单的,能与 seekbar 共享滑动的自定义 view
- Android 一个TextView中设置文字不同字体大小和颜色的最完整方法
- 自定义View-画一个会动的太极图
- android自定义View学习(一)----创建一个视图类
- 实现自定义ViewGroup的一个精简例子
- 一个炫字都不够??!!!手把手带你打造3D自定义view