Android View绘制过程

   view的绘制过程分为3个过程:

(1)measure过程,主要是设定了视图的高和宽

(2)layout过程,设定视图在父视图中的四个点(分别对应View四个成员变量mLeft,mTop,mLeft,mBottom。

(3)draw过程。

一.mesarue()过程

     主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:  mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。

 

 具体的调用链如下:

         ViewRoot根对象地属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调

View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:    

       1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:  mMeasuredHeight)和宽(对应属性:mMeasureWidth)  ;

       2 、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。

     对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了View对象的measure()方法。(由于measureChildWithMargins()方法只是一个过渡层更简单的做法是直接调用View对象的measure()方法)。

 

在View类中measure过程主要涉及三个函数,函数原型分别为

public final void measure(int widthMeasureSpec, int heightMeasureSpec)

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

前面两个函数都是final类型的,不能重载,为此在ViewGroup派生的非抽象类中我们必须重载onMeasure函数,实现measure的原理是:假如View还有子View,则measure子View,直到所有的子View完成measure操作之后,再measure自己。ViewGroup中提供的measureChild或measureChildWithMargins就是实现这个功能的,整个measure调用流程就是个树形的递归过程。

在具体介绍测量原理之前还是先了解些基础知识,即measure函数的参数由类measureSpec的makeMeasureSpec函数方法生成的一个32位整数,该整数的高两位表示模式(Mode),低30位则是具体的尺寸大小(specSize)。

MeasureSpec有三种模式分别是UNSPECIFIED, EXACTLY和AT_MOST,各表示的意义如下

如果是AT_MOST,specSize代表的是最大可获得的尺寸;

如果是EXACTLY,specSize代表的是精确的尺寸;

如果是UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。

那么对于一个View的上述Mode和specSize值默认是怎么获取的呢,他们是根据View的LayoutParams参数来获取的:

参数为fill_parent/match_parent时,Mode为EXACTLY,specSize为剩余的所有空间;

参数为具体的数值,比如像素值(px或dp),Mode为EXACTLY,specSize为传入的值;

参数为wrap_content,Mode为AT_MOST,specSize运行时决定。

二.layout过程

主要作用:为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。

 具体的调用链如下:

       host.layout()开始View树的布局,继而回调给View/ViewGroup类中的layout()方法。具体流程如下:

        1、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)

  接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局) ;

     2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。

上述measure过程达到的结果是设定了视图的高和宽,layout过程的作用就是设定视图在父视图中的四个点(分别对应View四个成员变量mLeft,mTop,mLeft,mBottom)。同样layout也是被fianl修饰符限定为不能重载,不过在ViewGroup中onLayout函数被abstract修饰,即所有派生自ViewGroup的类必须实现onLayout函数,从而实现对其包含的所有子视图的布局设定。

那么上述的measure结果与layout有什么关系,截取ViewRoot和FrameLayout两个类中onLayout函数的部分代码如下:

//ViewRoot的performTraversals函数measure之后对layout的调用代码

host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);

//FrameLayou的onLayout函数部分源码

 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

        final int count = getChildCount();

        ……

        for (int i = 0; i < count; i++) {

            final View child = getChildAt(i);

            if (child.getVisibility() != GONE) {

                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();

                final int height = child.getMeasuredHeight();

                int childLeft = parentLeft;

                int childTop = parentTop;

                final int gravity = lp.gravity;

 

                if (gravity != -1) {

                    final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;

                    final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

 

                    switch (horizontalGravity) {

                        case Gravity.LEFT:

                            childLeft = parentLeft + lp.leftMargin;

                            break;

                        case Gravity.CENTER_HORIZONTAL:

                            childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin;

                            break;

                        case Gravity.RIGHT:

                            childLeft = parentRight - width - lp.rightMargin;

                            break;

                        default:

                            childLeft = parentLeft + lp.leftMargin;

                    }

 

                    switch (verticalGravity) {

                        case Gravity.TOP:

                            childTop = parentTop + lp.topMargin;

                            break;

                        case Gravity.CENTER_VERTICAL:

                            childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin;

                            break;

                        case Gravity.BOTTOM:

                            childTop = parentBottom - height - lp.bottomMargin;

                            break;

                        default:

                            childTop = parentTop + lp.topMargin;

                    }

                }

 

                child.layout(childLeft, childTop, childLeft + width, childTop + height);

            }

        }

    }

从代码显然可知具体layout布局时,就是根据measure过程设置的高和宽,结合视图在父视图中的起始位置,再外加视图的layoutgravity属性来设置四个点的具体位置(在LinearLayout中还会增加对layoutweight属性的考虑)。这个过程相对没有measure那么复杂。

需要注意的是在自定义组合控件的时候,我们可以根据需要不用或只用部分measure过程计算得到的尺寸

三.Draw过程分析

View的Draw过程,其实相对来说应该比measure过程更为复杂,正因为其很复杂,所以android框架层已经将draw过程考虑得相当周全,虽然view类的Draw函数没用final修饰,但是我们自定义的View,一般也不需要去重载实现它,自己目前也没有自己去draw过界面,对整个过程,只能偷别人整理的逻辑,结合源码浏览了一下,在这里做个标注。

draw()方法实现的功能流程如下:

1、调用background.draw(canvas)绘制该View的背景

2、调用onDraw(canvas)方法绘制视图本身(每个View都需要重载该方法,ViewGroup不需要实现该方法)

3、调用dispatchDraw(canvas)方法绘制子视图(ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,其内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法)

4、调用onDrawScrollBars(canvas)绘制滚动条

为了说明measure、layout和draw过程的连续性,摘得draw中的源码如下

……

if (mBackgroundSizeChanged) {

    background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);

    mBackgroundSizeChanged = false;

}

……

上述的mLeft,mTop,mLeft,mBottom就是我们在layout是设定的结果值,这里之所以要用减法获取高宽尺寸而不用measure过程设定的mMeasuredHeight和mMeasureWidth,个人感觉就是因为我们可以在代码中通过直接调用View的layout函数避开measure测算结果而导致真实高宽不等于mMeasuredHeight和mMeasureWidth这种情况。

上述代码中的mBackgroundSizeChanged是个私有成员变量,源码中只能在View的onScrollChanged(int l, int t, int oldl, int oldt) 、layout过程调用的setFrame(int left, int top, int right, int bottom) 和setBackgroundDrawable(Drawable d)这三个函数中对其修改为true。

到这里,除了具体的绘制外,我们对从Activity到View的绘制流程应该比较清楚了。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值