首先,还是贴张图,这是我在网上看到的一张图,感觉它把view的整个流程都展现出来了
我们要用一个view,一般就是两种方式,
1.在局部文件里面 , 作为一个节点2.在代码中 , 通过 LayoutInflater 的 inflate() 加载 ; 然后父控件通过 addview 把 inflate 出来的这个控件添加进来了 ~( 其实上一种方式本质上也是这样 )
ViewRoot
在所有 view 之上有一个 ViewRoot,我们 通过调用它的 performTraversals() 来判断是否需要调用 measure()layout(),draw() 这一系列函数 .大致是这样的下面我们就假设view直接在ViewRoot之下(如果不在它下面的话就算是一个view的子控件)
OnMeasure()
首先 ,RootView 调用了 measure() 来测量大小 .首先要确定自己的大小 , 通过 onMeasure(), 我们能重载的也是它 , 调用 setMeasuredDimension() 会为这个 view 设定自己的宽度 (mMeasuredWidth) 和高度 (mMeasuredHeigth), 若该 View 是 ViewGroup, 则需在此处让子视图测量自己的大小 , 确保所有视图都测量无误 .( 通过 ViewGroup 提供的约束条件 )
在此过程中 , 会传入两个参数 ,widthMeasureSpec 和 heightMeasureSpec, 分别是高度和宽度的测量规格 , 每个测量规格都由两部分组成 ,mode 和 size,size 是默认设置为和父控件相同 ,mode 则分为三种 :
UNSPECIFIED( 不确定 ): 程序员可以任意设置大小
AT_MOST( 至多 ): 大小不能超过给定的大小 , 即 specSize
EXACTLY( 精确 ): 大小只能为 specSize
下面是源码中的处理方式
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
Tip:viewgroup 提供了measureChild() 来帮助测量子控件 .
Tip:getMeasureWidth() 在 measure() 结束后就可以取到值 ,getWidth() 则要在 layout() 之后才能取到值 .getMeasureWidth() 的返回值是在 setMeasuredDimension() 时设置的 , 而 getWidth() 的返回值则是通过视图右坐标减去左坐标计算而来 .
Onlayout()(ViewGroup用)
RootView 继续执行 , 来到了 layout().如果是一个简单的控件 , 那么 , 它的布局就是 (0,0,width,height) 它就可以把自己绘制出来了 ~ 但是 , 如果是一个 ViewGroup 的话 , 他还要负责自己的子控件的绘制 .
在 layout() 中 , 首先会判断大小是否发生了变化 , 如果发生了变化 , 那么就会调用 onLayout() 来进行重新布局 .onlayout() 是所有 ViewGroup 必须复写的一个函数 ~ 只要为调用每一个子视图的 layout() 确定布局即可 ~
OnDraw()(自定义ViewGroup一般不需要管)
大小也知道了 , 位置也知道了 , 那么是时候去绘制自己啦 .RootView 创建了一个 canvas, 其大小和位置由之前的结果所决定 , 然后调用了 draw().
draw() 做了以下几件事
1.画背景 2. 为绘制渐变框做准备 3. 调用 onDraw() 4. 子控件 draw() 5. 绘制滚动条
作为一个自定义 View, 我们的重点在 onDraw(), 在这里面传进来了 RootView 创建的
Canvas, 我们通过它 , 就可以自由自在的绘制我们想要的东西啦 ~~
Tip: 自定义 ViewGroup 可以通过重写 dispatchDraw() 来绘制前景 ~
Tip:ViewGroup 需要在构造函数中调用 setWillNotDraw(false),onDraw() 中的代码才会执行 .
requsetLayout() & invalidate()
requestLayout() 会导致重新测量 , 布局 , 以及重绘 ,invalidate() 导致重绘 , 和 touchEvent 结合起来 , 可以搞出各种动画效果 ~
总结一下:自定义View主要重写onMeasure() onDraw();自定义ViewGroup则是onMesure()和onLayout(),并且调用子控件的draw()以及layout()