前言
自定义View是Android开发者必须了解的基础
今天,我将手把手教你写一个自定义View,并理清自定义View所有应该的注意点
阅读本文前,请先阅读我写的一系列自定义View文章
自定义View基础 - 最易懂的自定义View原理系列(1)
自定义View Measure过程 - 最易懂的自定义View原理系列(2)
自定义View Layout过程 - 最易懂的自定义View原理系列(3)
自定义View Draw过程- 最易懂的自定义View原理系列(4)
目录
目录
- 自定义View的分类
自定义View一共分为两大类,具体如下图:
分类
- 具体介绍 & 使用场景
对于自定义View的类型介绍及使用场景如下图:
具体介绍 & 使用场景
- 使用注意点
在使用自定义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的显示效果。
- 具体实例
接下来,我将用自定义View中最常用的继承View来说明自定义View的具体应用和需要注意的点
4.1 继承VIew的介绍
Paste_Image.png
在下面的例子中,我将讲解:
如何实现一个基本的自定义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