Android:View绘制流程

View的绘制流程从ViewRoot的performTraversals()方法开始,主要经历三个过程:measure、layout、draw。


一、measure过程

1、调用View的measure(int widthMeasureSpec, int heightMeasureSpec),方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定View的宽度和高度的规格和大小。widthMeasureSpec和heightMeasureSpec这两个值又是从哪里得到的呢?通常情况下,这两个值都是由父布局经过计算后传递给子View的,说明父布局会在一定程度上决定子View的大小。最外层的根布局,它的widthMeasureSpec和heightMeasureSpec由系统产生。注意这个方法是final的,子类无法继承。

2、measure方法中调用onMeasure方法

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

通过setMeasuredDimension方法设置最后的实际测量大小。

3、当View为ViewGroup时,可以通过调用measureChildren()方法来遍历计算每个子View的测量大小。

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
        // 子View通过父布局得到widthMeasureSpec两个参数 
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
    }

小结:View测量的大小是由父布局以及子View本身共同计算的;ViewGroup需要对子View的测量负责,提供widthMeasureSpec,heightMeasureSpec两个参数。


二、layout过程

1、调用View的layout(int l, int t, int r, int b)方法,来进行布局。
2、layout方法中调用onLayout方法,在View中它是一个空方法,而在ViewGroup中它是一个抽象方法,这也不难理解,因为子View的布局通过ViewGroup来操纵,本身不进行操作。不同ViewGroup中必须实现onLayout方法,对子View进行布局。与measure过程对比,measure通过将父布局将widthMeasureSpec和heightMeasureSpec传递给子View然后才开始测量大小。

假设我们继承一个ViewGroup,可对onLayout进行如下操作。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec); 
        // 测量子View大小
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 通过不同实现来达到对子View进行不同布局。
    if (getChildCount() > 0) {
            View childView = getChildAt(0);
            childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
            View childView1 = getChildAt(1);
            childView1.layout(0, childView.getHeight(), childView1.getMeasuredWidth(), childView.getHeight() + childView1.getMeasuredHeight());
        }

    }

这里可以提一下,view.getWidth方法正是layout中right-left,同理getHeight则是b-t;而view.getMeasuredWidth则是measure过程中最后setMeasuredDimension后确定的,一般两者会相同,因为在ViewGroup的onLayout方法会遵守测量的大小。

小结:View本身不会进行布局操作,表现为onLayout方法为空;ViewGroup需要对子View的布局负责,在ViewGroup中onLayout方法对子View进行布局操作。


三、draw过程

调用draw方法,大致流程
// Step 1, draw the background, if needed
// skip step 2 & 5 if possible (common case)
// Step 3, draw the content onDraw(canvas);
// Step 4, draw the children dispatchDraw(canvas);
// Step 6, draw decorations (scrollbars)

在View类中,onDraw是个空方法,这是好理解的,我们继承View之后需要自己来实现这个View的绘制从而达到自己想要的效果,不同的系统View之间肯定有不同的onDraw实现。
dispatchDraw同样是个空方法,而ViewGroup的dispatchDraw方法中就会有具体的绘制代码,其实就是调用子View的draw方法。

小结:View自身onDraw方法为空方法,需要自定义实现;而ViewGroup则可以调用子View进行draw操作。


小结

应用程序的最外层是一个FrameLayout,所以我们的绘制流程应当从ViewGroup开始:

  1. measure操作,ViewGroup调用子View的measure方法将widthMeasureSpec和heightMeasureSpec两个值传给子View,子view测得实际大小。倘若子View也为ViewGroup则不断遍历下去;
  2. layout操作,ViewGroup在onLayout方法中调用子View的layout方法,实现对子View的布局。倘若子View也为ViewGroup则不断遍历下去;
  3. draw操作,ViewGroup在draw方法中调用子View的draw方法,onDraw方法产生绘制效果,所以View中是个空方法,由子类实现。倘若子View也为ViewGroup则不断dispatchDraw遍历下去。

其他

来看一下动态加载View中经常要用到的方法,public View inflate(int resource, ViewGroup root, boolean attachToRoot),

  • 当root为null时,加载的View则无法得到root的属性,
    通过以上分析我们可以得知,子View大小和布局是需要父布局来决定的,所以当root == null时layout_width和layout_height默认为wrap_content;
  • 当root不为null时,得到root的属性,正确设置的layout_width和layout_height;返回的是加载的view;
  • 当root不为null时,得到root的属性,正确设置的layout_width和layout_height;返回的是root

关于View重绘的两个方法invalidate()、requestLayout()。

  • invalidate()方法measure和layout流程是不会重新执行的,只有draw流程可以得到执行。
  • requestLayout()则可以将整个绘制流程可以完完整整地重新走一遍。

最后:
继承自View需要考虑onDraw,
继承自ViewGroup需要考虑measureChildren为子View测量大小和onLayout来布局子View;
继承自TextView、LinearLayout等,考虑丰富实现;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值