美文网首页
View的绘制流程

View的绘制流程

作者: AndroidTony | 来源:发表于2017-12-14 17:22 被阅读30次

    1 绘制流程

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


    image.png
    image.png

    2 measure流程

    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宽高的因素

    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流程

    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流程

    image.png··········

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

    image.png

    5 多次measure、layout和draw

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

    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的绘制流程

          本文链接:https://www.haomeiwen.com/subject/czjkixtx.html