参考资料:
http://www.gcssloop.com/timeline
http://blog.youkuaiyun.com/xmxkf/article/details/51490283
https://blog.youkuaiyun.com/carson_ho/article/details/56009827
目录
一、前言
自定义View可以分为以下两类:
1、继承View及其子类。
我们知道android中所有UI控件的基类都是view,如:Button、TextView等。我们自定义的view可以直接继承View也可以继承它的子类Button、TextView等来实现我们需要的效果,这类View的特点是不包含子View。一般我们也可以利用图片或者组合动画来实现,但是在性能和实现过程方面有一些差距。
2、继承ViewGroup及其子类。
查看源码可知ViewGroup也是View的子类。继承ViewGroup的自定义View一般是利用现有的组件根据特定的布局方式来组成新的组件。包含子View。例如:应用底部导航条中的条目,一般都是上面图标(ImageView),下面文字(TextView),那么这两个就可以用自定义ViewGroup组合成为一个Veiw,提供两个属性分别用来设置文字和图片,使用起来会更加方便。
二、实现流程
自定义View绘制流程简化图如下:
1、构造函数:
构造函数是View的入口,可以用于初始化一些的内容,和获取自定义属性。在AS(Adnroid studio)中创建自定义View时,会提示需要实现四个构造函数:
通过查看源码可以知道,每个构造函数的作用:
public class CircleView extends RelativeLayout {
public CircleView(Context context) {
this(context,null);
// 如果View是在Java代码里面new的,则调用
}
public CircleView(Context context, AttributeSet attrs) {
this(context, attrs,0);
//如果View是在xml里声明的,则调用
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 不会自动调用,一般是在第二个构造函数里主动调用,如View有style属性时
}
/*@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
//API21之后才使用,不会自动调用
}*/
}
在构造函数中我们可以定义一些绘制相关的属性,我们可以利用attr去自定义一些属性。
ps:有一点比较特殊在ImageView中的一些setBitmapXXX()方法会优先构造函数执行。我们在继承ImageView时要做相关的处理。
2、onMeasure()测量View的大小
2.1、为什么要测量View的大小?
创建一个View(执行构造方法)的时候不需要测量控件的大小,只有将这个view放入一个容器(父控件)中的时候才需要测量,而这个测量方法就是父控件唤起调用的。当控件的父控件要放置该控件的时候,父控件会调用子控件的onMeasure方法询问子控件:“你有多大的尺寸,我要给你多大的地方才能容纳你?”,然后传入两个参数(widthMeasureSpec和heightMeasureSpec),这两个参数就是父控件告诉子控件可获得的空间以及关于这个空间的约束条件(MeasureSpec下面会介绍)。子控件拿着这些条件就能正确的测量自身的宽高了。
2.2、测量View的流程?
上面说到onMeasure方法是由父控件调用的,所有父控件都是ViewGroup的子类,ViewGroup是一个抽象类,它里面有一个抽象方法onLayout,这个方法的作用就是摆放它所有的子控件(安排位置),因为是抽象类,不能直接new对象,所以我们在布局文件中可以使用View但是不能直接使用 ViewGroup。
在给子控件确定位置之前,必须要获取到子控件的大小(只有确定了子控件的大小才能正确的确定上下左右四个点的坐标),而ViewGroup并没有重写View的onMeasure方法,也就是说抽象类ViewGroup没有为子控件测量大小的能力,它只能测量自己的大小。但是既然ViewGroup是一个能容纳子控件的容器,系统当然也考虑到测量子控件的问题,所以ViewGroup提供了三个测量子控件相关的方法(measuireChildren\measuireChild\measureChildWithMargins),只是在ViewGroup中没有调用它们,所以它本身不具备为子控件测量大小的能力,但是他有这个潜力。
为什么都有测量子控件的方法了而ViewGroup中不直接重写onMeasure方法,然后在onMeasure中调用呢?因为不同的容器摆放子控件的方式不同,比如RelativeLayout,LinearLayout这两个ViewGroup的子类,它们摆放子控件的方式不同,有的是线性摆放,而有的是叠加摆放,这就导致测量子控件的方式会有所差别,所以ViewGroup就干脆不直接测量子控件,他的子类要测量子控件就根据自己的布局特性重写onMeasure方法去测量。这么看来ViewGroup提供的三个测量子控件的方法岂不是没有作用?答案是NO,既然提供了就肯定有作用,这三个方法只是按照一种通用的方式去测量子控件,很多ViewGruop的子类测量子控件的时候就使用了ViewGroup的measureChildxxx系列方法;还有一个作用就是为我们自定义ViewGroup提供方便。
测量的时候父控件的onMeasure方法会遍历他所有的子控件,挨个调用子控件的measure方法,measure方法会调用onMeasure,然后会调用setMeasureDimension方法保存测量的大小,一次遍历下来,第一个子控件以及这个子控件中的所有子控件都会完成测量工作;然后开始测量第二个子控件…;最后父控件所有的子控件都完成测量以后会调用setMeasureDimension方法保存自己的测量大小。值得注意的是,这个过程不只执行一次,也就是说有可能重复执行,因为有的时候,一轮测量下来,父控件发现某一个子控件的尺寸不符合要求,就会重新测量一遍。
2.3、MeasureSpec介绍
上面说到MeasureSpec约束是由父控件传递给子控件的,这个类里面到底封装了什么东西?我们看一看源码:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* 父控件不强加任何约束给子控件,它可以是它想要任何大小
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* 父控件已为子控件确定了一个确切的大小,孩子将被给