View的绘制流程和底层代码的理解和实现过程
- 文章目录
- View对象的绘制分为3个步骤
- onMeasure()和onLayout()的作用
- 测量View的三种模式
- 父类View中对应这个三个方法底层实现
- 使用过程中使用到的一些常用的API 的总结
View对象的绘制分为3个步骤
1.第一步:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
这个方法实现测量View本身及它的子View的宽度和高度
调用子View的测试方法并且传入测试规格,测量容器自己的宽和高
需要声明的是,这个方法在执行的过程中,会执行多次
2.第二步:
protected void onLayout(boolean changed, int l, int t, int r, int b){
super.onLayout(changed, l, t, r, b);
}
这个方法实现的是对子View进行排版(位置的按排,位置排好后,控件的真实大小就决定)也就是可以进行调用 childView.getWidth()和childView.getHeight()方法
3.第三步:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
这个方法实现的是对子view的绘制过程,也是现实view前的最后一步操作
onMeasure()和onLayout()的作用
(1)onMeasure方法就是测量自己和指定期望子View的宽高,测量出来之后调用setMeasuredDimension(measuredWidth, measuredHeight)来保存起来测量的值,保存后就可以调用view.getMeasuredWidth()方法来获取保存的值了,但它仅仅是测量的值而已,不一定是View的真正大小,如上面的TextView,在onLayout的时候我们给它的宽度加大了,实际的宽不等于测量的宽,即getWidth() != getMeasuredWidth()
(2)onLayout方法就是给子View分配位置和大小,所以在onLayout方法执行完之后我们就可以调用view.getWidth()方法来获取View的真正的大小了。其实View的大小就是我们分配的left、right、top、bottom相减而得出的。如width = right – left,我们在安排子View的位置的时候是可以超出父容器的的大小范围的,子View的宽高也可以大于父容器,但是超出父容器范围的部分将无法显示,所以我们尽量让子View的位置范围在容器的大小范围内
(3)容器(ViewGroup)一般不需要实现onDraw方法,容器只需要调用子View的draw方法把子View都画出来就可以。
测量View的三种模式
a) int atMost = MeasureSpec.AT_MOST;
官方注释
The child can be as large as it wants up to the specified size
个人理解:
这种测量模式一般使用在“match_content”这种属性值情况下,比如设备的最大的像素值,或者可以理解为屏幕的宽度和高度。
b) int exactly = MeasureSpec.EXACTLY;
官方注释
The parent has determined an exact size for the child. The child is going to be given those bounds
个人理解:
这种测量模式一般使用在“wrap_content”这种属性值情况下,view自己需要占用多大的像素值,或者多大的空间,如果是确定的,则使用的是这种测量模式。
c) int unspecified = MeasureSpec.UNSPECIFIED;
官方注释
The parent has not imposed any constraint on the child. It can be whatever size it wants
个人理解:
这种测量模式一般使用在ScrollView这种控件下面的使用的测量的模式,子view需要多大的高度,父容器会给子view分配出子view想要的大小
父类View中对应这个三个方法底层实现
1. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
这个方法的底层调用的是如下方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));
}
/** 调用的是系统的默认的模式 */
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;
}
a) 通过查看底层代码可以看出 :父类的方法没有做测量的事件,如果模式是未指定,则取最小高,其它模式直接取测量模式中声明的size
b) 如果是xml写的布局文件包含的父容器,则父容器自子调用onMeasure()方法,然后将childView进行测量出来
例如:ScrollView的onMeasure()方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!mFillViewport) {
return;
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.UNSPECIFIED) {
return;
}
if (getChildCount() > 0) {
final View child = getChildAt(0);
int height = getMeasuredHeight();
if (child.getMeasuredHeight() < height) {
final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
height -= mPaddingTop;
height -= mPaddingBottom;
int childHeightMeasureSpec =
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
c) 如果是我们自己实现的view对象,就需要我们自己实现测量方法 ,因此可以不使用父类提供的super.onMeasure(widthMeasureSpec, heightMeasureSpec);
这个方法
它底层调用的是 setMeasuredDimension(0,0)
这个方法 ,参数代表的已经是实际要显示的高度和宽度,也可以理解为保存测量的宽度和高度。
自定义的View中onMeasure()方法的作用为:
(1) 调用子View的测试方法并传入相应的测量规则,(2)测量自身的宽度和高度
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//4个参数代表当前view距离屏幕的左上右下的距离,
//设置子View具体的位置和大小
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制显示view的方法
}
使用过程中使用到的一些常用的API 的总结
MeasureSpec.getSize(widthMeasureSpec)
;idthMeasureSpec模式得到相对应的size,
Return the size in pixels defined in the supplied measure specification
(官方文档)child.getMeasuredWidth()
;获取测量的宽度child.getMeasuredHeight()
:获取测量的高度MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
手动设置测量的大小child.measure(widthMeasureSpec, heightMeasureSpec)
; 子View的测量具体的方法