View的绘制流程和底层代码的理解和实现过程

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 的总结

  1. MeasureSpec.getSize(widthMeasureSpec);idthMeasureSpec模式得到相对应的size,
    Return the size in pixels defined in the supplied measure specification(官方文档)
  2. child.getMeasuredWidth() ;获取测量的宽度
  3. child.getMeasuredHeight() :获取测量的高度
  4. MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); 手动设置测量的大小
  5. child.measure(widthMeasureSpec, heightMeasureSpec); 子View的测量具体的方法

如果读者在阅读的过程中,有任何的问题可以积极反馈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值