- 自定义View是Android中的一个重要部分,设计需要对UI界面进行定制化时,往往会用到自定义View的相关知识。
学习来源:Carson带你学自定义View
自定义View的类型
1、自定义组合控件:多个控件组合成一个新的控件,方便复用,例如项目中使用的自定义Dialog。
2、继承系统子View:继承系统的原生View再进行扩展,例如项目中的NumKeyView。
3、继承系统ViewGroup:继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展。
自定义View的基础概念:
1、Android中的坐标系:
在Android坐标系中,以屏幕的左上角为原点,原点往右为正,往下为正。如下图:
2、View坐标系:
在View当中,其位置都是使用与父View的位置来进行确定的,如下图:
通过与父View的相对位置来进行确定子View的位置。主要有以下4个情况:
- 顶部(Top):视图左上顶点到父控件上边界的距离;
- 左边(Left):视图左上顶点到父控件左边界的距离;
- 右边(Right):视图右下顶点到父控件左边界的距离;
- 底部(Bottom):视图右下顶点到父控件上边界的距离。
对应的获取方法: - getLeft():获取View到其父布局左边的距离。
- getRight():获取View到其父布局左边的距离
- getTop():获取View到其父布局顶边的距离
- getBottom():获取View到其父布局底边的距离
3、几个重要的类:
Activity:
负责控制生命周期、处理事件。通过其他的触摸事件回调方法来与Window、View交互。但是不负责真正的View绘制。
Window:
视图的承载器。可以理解为整个的手机的屏幕,其唯一实现类的PhoneView,内部通过windowManager进行管理,将DecorVIew传递给ViewRoot进行绘制逻辑。
DecorView:
顶级VIew,也就是所有VIew 的根节点,本质上是一个FrameLayout的子类,包含标题栏、内容栏两部分。
ViewRoot:
连接器,负责连接windowManagerService和DecorView,同时负责真正的绘制逻辑。也就是onMesure() -> onLayout() -> onDraw();同时也负责wMS通讯,向decorWindow传递事件。
其关系见图:
- 来自Carson的文章
绘制前的准备工作:
从Activity的onCreate的setContentView开始:
- 创建window类,也就是创建对应的phoneWindow。
- 为phoneWindow创建对应的WindowManeger。
- 创建DecorView,同时为其设置相关的布局文件。
- 将DecorView放入到WindowManager中。
- 创建ViewRoot对象。
- 通过WIndowManager将DecorView传递给ViewRoot。
- 开始绘制。
绘制流程:
基本绘制流程:onMeasure() (确定大小)-> onLayout()(确定位置) -> onDraw()(实际绘制)。
onMeasure():
学习onMeasure()前要先了解两个基本的类:
ViewGroup.layoutParams: 指定View的高度和宽度以及其他布局参数。通过xml配置后得来。
MeasureSpec:测量规格类,用来设置view的大小。本质上是一个32位INT类型组成。包含两部分内容信息:
1、 测量模式(mode):占测量规格的前2位,主要分为三种:EXACTLY(精确模式)、AT_MOST(最大限定模式)、UNSPECIED(无限制)。
2、设置大小,也就是设置的精确大小。
三种模式的区别:(重要)
onMeasure的流程:
对于单一的View绘制:
整体绘制流程图:
1、measure():measure的入口,通过该入口开始执行measure流程,在此调用onMeasure()方法开始真正测量。
2、onMesure():测量的真正逻辑,在此进行真正的测量。
3、setMeasureDimension(): 保存测量的结果,一般来说onMeasure可能会执行多次,最后在此方法保存结果。
onMeasure()源码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 参数说明:View的宽 / 高测量规格
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
// setMeasuredDimension() :保存View宽/高的测量值
// 也就是通过getDefaultSize方法传输宽度和高度的MeasureSpec后,把该数据进行保存,完成测量过程
getDefaultSize()源码:
public static int getDefaultSize(int size, int measureSpec) {
// 参数说明:
// size:提供的默认大小
// measureSpec:宽/高的测量规格(含模式 & 测量大小)
// 设置默认大小
int result = size;
// 获取宽/高测量规格的模式 & 测量大小
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
// 模式为UNSPECIFIED时,使用提供的默认大小 = 参数Size
case MeasureSpec.UNSPECIFIED:
result = size;
break;
// 模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值 = measureSpec中的Size
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
// 返回View的宽/高值
return result;
}
对于的ViewGroup的绘制:
基本绘制流程图:
测量流程:
1、measure():measure的入口,通过该入口开始执行measure流程,在此调用onMeasure()方法开始真正测量。
2、onMesure():需要进行复写的方法。在此遍历所有的子VIew,计算出所有的子View的宽高,最后进行合并成最终的ViewGroup尺寸。同时还要在此执行保存尺寸的逻辑。
3、measureChidren():遍历所有的子View,并且调用measureChild()方法测量子View的大小。
4、measureChild():计算子View的大小,同时在此进行一次measure()的流程。
5、getChildMeasureSpec():获得子View的测量大小
6、setMeasureDimension(): 保存测量的结果,一般来说onMeasure可能会执行多次,最后在此方法保存结果。
onLayout()流程
流程图:
- 来自Carson的图
onLayout的流程与onMeasure类似,也是通过计算layout的四个顶点,计算时若有子View计算子View的四个顶点位置,最后保存到 mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom)中
onDraw():
绘制的过程也是与onMeasure()流程相似,基本流程图:
1、draw():绘制的入口。
2、drawBackground():绘制背景。
3、onDraw():绘制本身的内容。
4、disPathDraw():绘制子View,这个不一定有。
5、onDrawScrollBars():绘制装饰边,例如颜色,滑动条等等。
也可以从源码看出来,绘制的过程中,主要流程:绘制背景->绘制本身->绘制子类->绘制装饰类。