View树的绘制流程

  参考Android 8.0源码

  在View类和ViewGroup类中,都有同样的静态内部类MeasureSpec。每一个控件View都包含了一个静态内部类MeasureSpec,这个类用来记录控件的测量模式和测量大小。
  在ViewGroup类中有一个静态内部类LayoutParams,还有一个静态内部类MarginLayoutParams继承自LayoutParams类。在其他的容器类型的控件中,也都有一个静态内部类LayoutParams,继承自ViewGroup类的内部类MarginLayoutParams。

  这里先表明一下,ViewRootImpl类是在WindowManagerGlobal.java类中初始化的。是在WindowManagerGlobal.java类的addView()方法中进行初始化的,然后调用ViewRootImpl的setView()方法。以下是ViewRootImpl.java类中的调用关系:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    //---;
    requestLayout();
    //---;
}
public void requestLayout() {
    //---;
    scheduleTraversals();
    //---;
}
void scheduleTraversals() {
    //---;
    mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    //---;
}
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
void doTraversal() {
    //---;
    performTraversals();
    //---;
}
private void performTraversals() {
    //---;
}

performTraversals()

  ViewRootImpl负责Activity整个GUI的绘制,而绘制是从ViewRootImpl的performTraversals()方法开始的。

  performTravsals()方法中关键的三个方法如下:

//1576-2361;
private void performTraversals() {
    //代码省略;
    //2155,2181;
    performMeasure(childWidthMeasureSpec,childHeightMeasureSped);
    //代码省略;
    //2200;
    performLayout(lp,mWidth,mHeight);
    //代码省略;
    //2347;
    performDraw();
    //省略代码;
}

  performMeasure()方法测量组件的大小,performLayout()方法用于子组件的定位(放在窗口的什么地方),而performDraw()方法自然就是将组件的外观绘制出来了。

performMeasure()--测量组件的大小

  performMeasure()方法负责组件自身尺寸的测量。在layout布局文件中,每一个组件都必须设置layout_width和layout_height属性,属性值有三种可选模式:wrap_content、match_parent和数值,performMeasure()方法根据设置的模式计算出组件的宽度和高度。事实上,大多数情况下,模式为match_parent和数值的时候是不需要计算的,传过来的就是父容器自己计算好的尺寸或是一个指定的精确值,只有当模式为wrap_content的时候才需要根据内容进行尺寸的测量。

//2404-2414;
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

  对象mView是View树的根视图,代码中调用了mView的measure()方法。

//View.java中的measure()方法;
//21961-22024;
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    //省略代码;
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    //省略代码;
}

  onMeasure()方法是为组件尺寸的测量预留的功能接口,当然也定义了默认的实现,默认的实现并没有太多意义,在绝大部分情况下,我们需要重写onMeasure()方法。

  如果测量的是容器的尺寸,而容器的尺寸又依赖于子组件的大小,所以必须先测量容器中子组件的大小,不然,测量出来的宽度和高度永远为0。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int opticalWidth  = insets.left + insets.right;
        int opticalHeight = insets.top  + insets.bottom;
        measuredWidth  += optical ? opticalWidth  : -opticalWidth;
        measuredHeight += optical ? opticalHeight : -opticalHeight;
    }
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

performLayout()--确定子组件的位置

  performLayout()方法用于确定子组件的位置,所以该方法只针对于ViewGroup容器类。作为容器必须为容器中的子View精确定义位置和大小。

//2467-2542;
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
    //代码省略;
    host.layout(0, 0,host.getMeasuredWidth(),host.getMeasuredHeight());
    //代码省略;
    getRunQueue().post(new Runnable() {
        @Override
        public void run() {
            int numValidRequests = finalRequesters.size();
            for (int i = 0; i < numValidRequests; ++i) {
                final View view = finalRequesters.get(i);
                Log.w("View", "requestLayout() improperly called by " + view + " during second layout pass: posting in next frame");
                view.requestLayout();
            }
        }
    });
}

  代码中的host是View树中的根视图(DecorView),也就是最外层容器,容器的位置安排在左上角(0,0),其大小默认会填满mContentParent容器。layout()方法的代码如下(在View.java类中):

//19571-19616;
public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        if (shouldDrawRoundScrollbar()) {
            if(mRoundScrollbarRenderer == null) {
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            }
        } else {
            mRoundScrollbarRenderer = null;
        }
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
        mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
        notifyEnterOrExitForAutoFillIfNeeded(true);
    }
}

  在layout()方法中,在定位之前如果需要重新测量组件的大小,则先调用onMeasure()方法,接下来执行setOptioncalFrame()或setFrame()方法确定自身的位置与大小(setOptioncalFrame()方法最终也是调用setFrame()方法的),此时只是保存了相关的值,与具体的绘制无关。随后,onLayout()方法被调用,该方法是空方法,代码如下:

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

  onLayout()方法在这里的作用是当前组件为容器时,负责定位容器中的子组件,这其实是一个递归的过程,如果子组件也是一个容器,该容器依然要负责它的子组件的定位,依此类推,直到所有的组件都定位完成为止。也就是说,从最顶层的DecorView开始定位,像多米诺骨牌一样。从上往下驱动,最后每一个组件都放到了它应该出现的位置上。onLayout()方法和onMeasure()方法一样,是为开发人员预留的功能扩展接口,自定义容器时,该方法必须重写。

绘制组件

  performDraw()方法执行组件的绘制功能,组件绘制是一个十分复杂的过程,不仅仅绘制组件本身,还要绘制背景、滚动条,好消息是每个组件只需要负责自身的绘制,而且一般来说,容器组件不需要绘制,ViewGroup已经做了大量的工作。

//2781-2841;
private void performDraw() {
    //代码省略;
    final boolean fullRedrawNeeded = mFullRedrawNeeded;
    mFullRedrawNeeded = false;
    mIsDrawing = true;
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
    try {
        draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    //代码省略;
}

  在performDraw()方法中调用了draw()方法:

//2843-3017;
private void draw(boolean fullRedrawNeeded) {
    //代码省略;
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
        return;
    }
    //代码省略;
}
//3022-3117;
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) {
    final Canvas canvas;
    
    final int left = dirty.left;
    final int top = dirty.top;
    final int right = dirty.right;
    final int bottom = dirty.bottom;
    canvas = mSurface.lockCanvas(dirty);
    
    if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
        canvas.drawColor(0, PorterDuff.Mode.CLEAR);
    }
    
    mView.draw(canvas);
    
    surface.unlockCanvasAndPost(canvas);
}

  在drawSoftware()中,出现了Canvas。绘制组件是通过Canvas类完成的,该类定义了若干个绘制图形的方法,通过Paint类配置绘制参数,便能绘制出各种图案效果。为了提高绘图的性能,使用了Surface技术,Surface提供了一套双缓存机制,能大大加快绘图效率,而我们绘图时需要的是Canvas对象也是Surface创建的。

  drawSoftware()方法中调用了mView的draw()方法,mView是Activity界面中View树的根(DecorView),也是一个容器(具体来说是一个FrameLayout布局容器),所以直接来看FrameLayout类的draw()方法,但是FrameLayout中没有重写draw()方法,ViewGroup中也没有重写draw()方法,要到View类中去查看draw()方法。

//19088-19295;
public void draw(Canvas canvas) {
    if(!dirtyOpaque){
        drawBackground(canvas);
    }
    
    if(!dirtyOpaque){
        onDraw(canvas);
    }
    dispatchDraw(canvas);
    
    onDrawForeground(canvas);
}
  • drawBackground(canvas):绘制背景;
      在该方法中,使用了mBackground,是一个Drawable对象,直接绘制在Canvas上,并且与组件要绘制的内容互不干扰。

  • onDraw():绘制自己;View只是组件的抽象定义,并不知道自己什么样子,所以,View定义了onDraw()这样一个空方法,和onMeasure()、onLayout()一样,onDraw()方法同样是预留给子类扩展的功能接口,用于绘制组件自身。组件的外观由该方法来决定。

  • dispatchDraw(canvas):绘制子视图;
      dispatchDraw()方法也是一个空方法,该方法服务于容器组件,容器中的子组件必须通过实现dispatchDraw()方法进行绘制,所以,View类虽然没有实现该方法但它的子类ViewGroup实现了该方法。

//3927-4058;
protected void dispatchDraw(Canvas canvas) {

    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;
        
    for (int i = 0; i < childrenCount; i++) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
            final LayoutParams params = child.getLayoutParams();
            attachLayoutAnimationParameters(child, params, i, childrenCount);
            bindLayoutAnimation(child);
        }
    }
            
    for (int i = 0; i < childrenCount; i++) {
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            final View transientChild = mTransientViews.get(transientIndex);
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {
                more |= drawChild(canvas, transientChild, drawingTime);
            }
            transientIndex++;
            if (transientIndex >= transientCount) {
                transientIndex = -1;
            }
        }
        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            more |= drawChild(canvas, child, drawingTime);
        }
    }
}

  在dispatchDraw()方法中,循环遍历每一个组件,并调用drawChild()方法绘制子组件,而子组件又调用View的draw()方法绘制自己;

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

  组件的绘制也是一个递归过程,说到底Activity的UI界面的根一定是容器,根容器绘制结束后开始绘制子组件,子组件如果是容器继续往下递归绘制,否则将子组件绘制出来,直到所有组件正确绘制为止。

  • onDrawForeground(canvas):绘制前景,其中有绘制进度条;
private void drawBackground(Canvas canvas) {
    
    setBackgroundBounds();
    
}
protected void dispatchDraw(Canvas canvas) {
    
}
public void onDrawForeground(Canvas canvas) {
    
    onDrawScrollBars(canvas);
    
}

  总体来说,UI界面的绘制从开始到结束要经历几个过程:

  • 测量大小,调用onMeasure()方法;
  • 组件定位,回调onLayout()方法;
  • 组件绘制,回调onDraw()方法;

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
----------------------last line for now-----------------------