View的布局、绘制流程

一.布局

前一篇文章已经详细研究了View的测量,现在接着往下看看View的layout过程吧。先回顾一下大致的流程:ViewRootImpl#performLayout()->layout()->onLayout()

Layout的作用是确定ViewGroup的位置,接着ViewGroup会在onLayout()中遍历子View并调用其layout()方法,在layout()中又会调用onLayout来确定子View的位置。

在ViewRootImpl#performLayout()中通过 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()) 调用顶层View的layout()方法,这个host就是DeCorView。

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()的实现比较简单,通过setFrame(l, t, r, b)确定左上角和右下角坐标(mLeft,mTop),(mRight,mBottom),这样就确定了ViewGroup(这里是DeCorView)的位置,接着就是调用onLayout()来完成子元素的布局了。子元素的layout过程跟具体的布局有关,所以在View中onLayout是空实现,那就看看一些具体的ViewGroup,还是选择比较简单的FrameLayout来分析,它的onLayout()又调用了layoutChildren()来完成子View的布局工组,具体实现如下:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
   ......
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            ......
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

子View的layout又会递归这个过程,递归的出口就是到了具体的View。值得注意的一点是,在测量完成后,mMeasuredWidth,mMeasuredHeight被赋值,即我们调用getMeasuredWidth()、getMeasuredHeight()可得到的值。而layout完成后,mLeft、mTop、mRight、mBottom这四个成员变量被赋值,这将影响getWidth()、 getHeight()得到值,下面是它们的实现:

public final int getWidth() {
    return mRight - mLeft;
}

public final int getHeight() {
    return mBottom - mTop;
}

measure的结果与最终View的宽高绝大部分是情况下相等的,只是它们被赋值的时机不同,这点也告诉我们,在自定义View要通过getWidth()、 getHeight()获取控件的宽高,就要等layout完成。

二.绘制

前面已经分析了View的测量、布局的过程,剩下的绘制过程就相对简单了。先回顾一下它的大致流程:ViewRootImpl#performDraw()->ViewGroup#draw()->dispatchDraw()->View#draw()->onDraw(),ViewGroup没用重写draw()方法,具体的实现在View中。

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      1. Draw the background
     *      2. If necessary, save the canvas' layers to prepare for fading
     *      3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */

    // Step 1, draw the background, if needed
    int saveCount;

    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        drawAutofilledHighlight(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }

        // we're done...
        return;
    }
    ...
}    

从方法的注释就可以看出来它遵循下面几个步骤:
(1)绘制背景;
(2)画自己,即调用onDraw
(3)画子View,通过dispatchDraw
(4)画装饰,如scrollbars。

ViewGroup通过dispatchDraw()来绘制子View,在dispatchDraw()中子View的draw()会被调用,draw()中会调用onDraw,这样我们写在onDraw中的绘制代码就得到了执行。大致的流程总结为:

(1)ViewGroup的draw()方法,开始于DecorView的draw,这是个FrameLayout;
(2)DecorView的draw方法绘制背景、onDraw()画它自己、装饰,并且调用dispatchDraw()来绘制子View;
(3)在dispatchDraw()中子View的draw()被调用,如此反复到画完所有该画的View。

《View的布局、绘制流程》 关注微信公众号,第一时间接收推送!

    原文作者:Jdqm
    原文地址: https://www.jianshu.com/p/c1ada1e61b77
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞