View的绘制流程

1 绘制流程

View的绘制流程从ViewRootImpl的requestLayout()开始


image.png

View的绘制流程_第1张图片
image.png

2 measure流程

View的绘制流程_第2张图片
image.png
  • ViewGroup:每个ViewGroup必须复写onMeasure,并且在onMeasure中measureChild,并在measureChild结束之后,调用setMeasuredDimension设置自身的宽高。
final void measure(int widthMeasureSpec, int heightMeasureSpec){
    onMeasure(widthMeasureSpec,heightMeasureSpec);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
    //measureChildren
   省略测量子布局的代码
    setMeasuredDimension(参数);
}
  • View:每个View也必须复写onMeasure,并且在onMeasure中设置自身宽高。
final void measure(int widthMeasureSpec, int heightMeasureSpec){
    //省略其它代码
    onMeasure(widthMeasureSpec,heightMeasureSpec);
}

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

影响View宽高的因素

View的绘制流程_第3张图片
image.png

即ViewGroup先根据每个child的MarginLayoutParams(继承自ViewGroup.LayoutParams,包括android:layout_width、android:layout_height以及各方向的margin),结合自身的MeasureSpec(32位的int类型,包含测量模式与测量大小,其中测量模式分为无约束UNSPECIFIED、精确值EXACTLY以及最大值AT _MOST三种)和padding,得到需要传给各个child的约束条件MeasureSpec,然后child根据MeasureSpec,minWidth属性以及background的最小宽高了,确定出child自身的宽高。

当某个View的measure()方法返回时,它以及它的所有子节点的getMeasuredWidth()和getMeasuredHeight()方法的值就已经设置好了。

3 layout流程

View的绘制流程_第4张图片
image.png
    public void layout(int l, int t, int r, int b){
        //省略其它代码
        setFrame(l, t, r, b);
        onLayout(changed, l, t, r, b);
    }

 protected boolean setFrame(int left, int top, int right, int bottom)

layout方法确定view自身的位置,通过setFrame()设置四个顶点的坐标。然后调用onLayout()确定所有子元素的位置。
所以在自定义view中,对于ViewGroup,需要重写onLayout()方法,并调用child.layout()对子元素进行布局。如果是View,由于没有子View,无需重写该方法。
注意:view可以重写layout和onLayout,而ViewGroup的只能重写onLayout,其layout是final类型。

4 draw流程

View的绘制流程_第5张图片
image.png··········

可以看一下,draw事件是如何从上往下传递,逐个绘制的:

View的绘制流程_第6张图片
image.png

5 多次measure、layout和draw

measure、layout和draw的流程有可能反反复复多次,见下图。

View的绘制流程_第7张图片
image.png
  • ViewGroup导致的重绘

一个父View可能对其子View调用多次measure()方法。举个例子:父节点可能首先会通过一次没有明确尺寸约束(unspecified dimensions)的测量过程来获取每个子View想获得的视图大小。如果最后得到的数值过大或者过小,那么父节点会再次对其子View调用measure()方法,并使用实际的计算结果作为输入参数(即如果子View不同意首次测量结果,父View会进行第二次带约束条件的测量)。

我们知道,顶层View为继承自FrameLayout的DecorView,看看FrameLayout的measure方法:

// FrameLayout的onMeasure函数,DecorView的onMeasure会调用这个函数。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    .....
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            ......
        }
    }
    ........
    count = mMatchParentChildren.size();
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            ........
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

当然,不可能每次都执行两次measure,View的Measure函数中有相关机制,只有在FLAG_FORCE_LAYOUT标志位为1或者widthMeasureSpec、heightMeasureSpec和上次的不一样时才会重新measure。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  ......
  // 当FLAG_FORCE_LAYOUT位为1时,就是当前视图请求一次布局操作
  //或者当前widthSpec和heightSpec不等于上次调用时传入的参数的时候
  //才进行从新绘制。
    if (forceLayout || !matchingSize &&
            (widthMeasureSpec != mOldWidthMeasureSpec ||
                    heightMeasureSpec != mOldHeightMeasureSpec)) {
            ......
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            ......
    }
    ......
}
  • ViewRootImpl导致的重绘
    Activity加载的时候,newSurface是true。
private void performTraversals() {
    ......
    boolean newSurface = false;
    //TODO:决定是否让newSurface为true,导致后边是否让performDraw无法被调用,而是重新scheduleTraversals
    if (!hadSurface) {
        if (mSurface.isValid()) {
            // If we are creating a new surface, then we need to
            // completely redraw it.  Also, when we get to the
            // point of drawing it we will hold off and schedule
            // a new traversal instead.  This is so we can tell the
            // window manager about all of the windows being displayed
            // before actually drawing them, so it can display then
            // all at once.
            newSurface = true;
                    .....
        }
    }
            ......
    if (!cancelDraw && !newSurface) {
        if (!skipDraw || mReportNextDraw) {
            ......
            performDraw();
        }
    } else {  //newSurface为true,会重新scheduleTraversals
        if (viewVisibility == View.VISIBLE) {
            // Try again
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).endChangingAnimations();
            }
            mPendingTransitions.clear();
        }
    }
    ......
}

你可能感兴趣的:(View的绘制流程)