Android O: View的绘制流程(三):布局和绘制

前一篇文章Android O: View的绘制流程(二):测量中, 
我们分析了View的测量流程。 
当View测量完毕后,就要开始进行布局和绘制相关的工作, 
本篇文章就来分析下这部分流程。


一、View的layout 
我们从ViewRootImpl.java的performLayout函数开始分析:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    ............
    //ViewRootImpl中的mView为DecorView
    final View host = mView;
    ............
    try {
        //进入View的layout函数
        //参数分别为left position, top position, right postion, bottom postion
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        .......  
    }
    .........
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

跟进View的layout函数:

public void layout(int l, int t, int r, int b) {
    .............
    //保留旧数据
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    //measure时也判断过, 当前View为ViewGroup且设置为视觉边界布局模式时,才返回true
    //setOpticalFrame最终也会调用setFrame
    //setFrame将会设置View的位置(mLeft, mTop, mRight, mBottom)
    //这四个参数描述了View相对其父View的位置
    //如果View的位置发生了变化,就会返回true
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    //View的measure函数中, 会判断是否增加PFLAG_LAYOUT_REQUIRED
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        //布局其child view
        onLayout(changed, l, t, r, b);
        .........
        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);
            }
        }
    }
    ........
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

从上面代码可以看出,layout函数会判断View的位置是否发生了改变。 
若发生了改变,则需要调用onLayout函数对子View进行重新布局。

由于普通View(非ViewGroup)不含子View,所以View.java中的onLayout方法为空实现。 
因此接下来,我们看看ViewGroup类的onLayout方法。

二、FrameLayout的onLayout 
ViewGroup中的onLayout为一个抽象方法,由具体的ViewGroup实现。 
对于DecorView而言,将调用FrameLayout的onLayout方法:

@Override
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) {
    final int count = getChildCount();

    //parentLeft表示当前View为其子View显示区域指定的一个左边界
    //也就是子View显示区域的左边缘到父View的左边缘的距离
    //parentRight、parentTop、parentBottom的含义类似
    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();

    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();

    //开始对子View进行布局
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);

        //子View宽和高
        final int width = child.getMeasuredWidth();
        final int height = child.getMeasuredHeight();

        //仅计算left和top
        //结合child view的宽和高, 就能得到right和bottom
        int childLeft;
        int childTop;

        int gravity = lp.gravity;
        if (gravity == -1) {
            //对于FrameLayout, 为Gravity.TOP | Gravity.START
            gravity = DEFAULT_CHILD_GRAVITY;
        }

        //得到ViewGroup的布局方向
        final int layoutDirection = getLayoutDirection();

        //child view对应的layout_gravity字段信息
        final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
        final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

        //水平方向
        switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            //水平居中的场景
            case Gravity.CENTER_HORIZONTAL:
                childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                break;
            case Gravity.RIGHT:
                //右对齐的场景
                if (!forceLeftGravity) {
                    childLeft = parentRight - width - lp.rightMargin;
                    break;
                }
            //默认的情况
            case Gravity.LEFT:
            default:
                childLeft = parentLeft + lp.leftMargin;
        }

        //垂直方向
        switch (verticalGravity) {
            //顶端对齐的场景
            case Gravity.TOP:
                childTop = parentTop + lp.topMargin;
                break;
            //垂直居中的场景
            case Gravity.CENTER_VERTICAL:
                childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                break;
            //底部对齐的场景
            case Gravity.BOTTOM:
                 childTop = parentBottom - height - lp.bottomMargin;
                 break;
            //默认为顶对齐
            default:
                 childTop = parentTop + lp.topMargin;
        }

        //子ViewGroup进行布局
        child.layout(childLeft, childTop, childLeft + width, childTop + height);
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

上面代码中,childLeft代表了最终子View的左边缘距父View左边缘的距离; 
childTop代表了子View的上边缘距父View的上边缘的距离。 
当计算出child view的位置信息后,会继续调用layout方法,继续递归布局。

对于ViewGroup而言,onMeasure和onLayout应该是配套使用的。 
我们目前只以比较简单的FrameLayout为例,分析了这部分过程。 
对于其它ViewGroup而言,递归的方式与FrameLayout类似, 
但具体的细节差异较大。

三、ViewRootImpl的performDraw 
完成了measure和layout阶段后,View的大小和位置基本上就确定了, 
接下来就进入绘制阶段。

我们同样从ViewRootImpl的performDraw函数入手:

private void performDraw() {
    .......
    final boolean fullRedrawNeeded = mFullRedrawNeeded;
    mFullRedrawNeeded = false;

    mIsDrawing = true;
    ........
    try {
        draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false;
    }   
    ........
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

我们跟进ViewRootImpl的draw函数:

private void draw(boolean fullRedrawNeeded) {
    //省略滚动、动画相关的细节
    ...........
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        //如果采用硬件渲染绘制且ThreadedRenderer可用,进入该流程
        if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
            ..........
            //最后将通过native函数nDrawRenderNode绘制
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
        } else {
            //如果需要进行硬件渲染,但ThreadedRenderer不可用
            //则进行ThreadedRenderer初始化工作(以便下次用)
            ..........

            // 不用硬件渲染,或硬件渲染不可用,则靠软件绘制
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        }
    }
    .........
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

继续分析drawSoftware函数:

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty) {
    // Draw with software renderer.
    final Canvas canvas;
    try {
        ..........
        //获取画布
        canvas = mSurface.lockCanvas(dirty);
        .........
    }  catch (Surface.OutOfResourcesException e) {
        .........
    } catch (IllegalArgumentException e) {
        ........
    }

    try {
        ........
        try {
            ........
            //关键在此
            //此时调用的是DecorView的draw函数
            mView.draw(canvas);
            ........
        } finally {
            ........
        }
    } finally {
        try {
            //unlock
            surface.unlockCanvasAndPost(canvas);
        } catch (IllegalArgumentException e) {
            ...........
        }
        .........
    }
    ........
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

从上述代码可以看出,在不使用硬件绘制的条件下, 
ViewRootImpl的performDraw函数最终会调用View的draw函数。

四、View的draw 
View.java中draw函数的源码如下:

public void draw(Canvas canvas) {
    .............
    //draw函数的实现细节,可以参考注释
    /*
     * 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;
    // 判断View是否具有Fading Edge, xml里需要主动配置, 以支持边缘渐变效果
    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
        // 绘制child view
        dispatchDraw(canvas);
        ..........

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

        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);
        .........
        // we're done...
        return;
    }

    //支持支持边缘渐变效果时的绘制
    //与前面不同的地方主要是:需要保存canvas' layer, 增加渐变效果后,再恢复canvas' layer
    //暂时不深入分析,等研究FADING_EDGE效果时,再来看
    ........
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

在上面的代码中,我们目前最关心的是onDraw和dispatchDraw。 
其中,onDraw用于绘制View自身,需要每个View自己实现; 
dispatchDraw用于绘制child view,由ViewGroup实现。

最后,我们来看看ViewGroup中的dispatchDraw函数:

protected void dispatchDraw(Canvas canvas) {
    boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;

    //处理动画相关的绘制
    if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
        .............
    }
    ................
    // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
    // draw reordering internally
    final ArrayList<View> preorderedList = usingRenderNodeProperties
            ? null : buildOrderedChildList();
    final boolean customOrder = preorderedList == null
            && isChildrenDrawingOrderEnabled();

    //默认先序遍历绘制
    for (int i = 0; i < childrenCount; i++) {
        .......
        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            //内部还是调用View的draw函数
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    .............
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

上述代码中我们省略了许多细节,不过仍可以很清晰地看出, 
整个View的视图结构是按照先序遍历来绘制的(尽管没有分析具体的实现细节, 
但绘制时肯定会依赖布局时得到的信息)。

对于一个ViewGroup而言,会先绘制自身, 
然后绘制child view,最后再绘制一些装饰组件等。

五、总结 
至此,View绘制相关的主要流程全部分析完毕。 
毫无疑问,我们漏掉了太多的细节。

其中,有的细节不太重要,所以我们不需要关注; 
有的细节则不是行文的重点,我们也有意忽略掉了; 
还有些细节,则需要对View绘制有更深刻的理解, 
才能进一步分析。 
目前,由于自己也是第一次深入看View相关的源码, 
故未做进一步分析。以后如果碰到相关的问题,再做进一步的补充。

版权声明:转载请注明:http://blog.csdn.net/gaugamela/article
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值