AndroidView绘制流程四(布局,绘制)

在上一篇文章中,我们得知了View是如何测量的,下面去看一下布局和测绘的流程。
进入ViewRootImpl的performTraversals()方法

    private void performTraversals() {
        //...
        performLayout(lp, mWidth, mHeight);
        //...
    }

进入performLayout()方法,

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                               int desiredWindowHeight) {
        //...
        final View host = mView;
        if (host == null) {
            return;
        }
        //...
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        //...
    }

这里将mView赋值给host对象,而mView是DecorView对象,继承于FrameLayout,调用layout()方法,并传递DecorView的宽高,进入layout()方法,发现其调用了 onLayout(changed, l, t, r, b)方法,在点进去发现该方法是一个空方法,留给子类去实现的

  public void layout(int l, int t, int r, int b) {
        //...
        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);
        }
        //...
    }

这里补充一点,在上述setFrame()方法中,确定了成员变量mLeft、mTop、mBottom、mRight的值,从而确定了控件位置。

由于DecorView是继承于FrameLayout,进入FrameLayout中,寻找onLayout(),发现其调用了layoutChildren(),这里实际上是对子View进行摆放的操作,如计算view的padding值、margin值以及Gravity等,在for循环末端调用 child.layout(),对layout()进行递归操作,从而完成view的布局。

 void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();

        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();

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

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;

                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }

                final int layoutDirection = getLayoutDirection();
                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;
                }

                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

ViewGroup的布局
1)layout 确定自身的位置
2)onLayout确定子View的位置

View的布局
1)layout 确定自身的位置

下面再来看一下View的绘制
进入ViewRootImpl的performDraw()方法,调用了draw()方法,然后又调用了drawSoftware()方法,在该方法中调用了 mView.draw(canvas)方法,进入该draw方法,发现其调用了View的draw()方法

  public void draw(Canvas canvas) {
        /*
         * 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);
            // we're done...
            return;
        }
    }

到这里,发现view的draw方法主要包括6个步骤
* 1. Draw the background 绘制背景 drawBackground()
* 2. If necessary, save the canvas’ layers to prepare for fading 保存图层
* 3. Draw view’s content 绘制自己 onDraw()
* 4. Draw children 绘制子view,如果该View是容器的话 dispatchDraw()
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance) 绘制装饰,如滚动条等 onDrawForeground()

在绘制子View的时候,需要调用dispatchDraw()方法,这里去查看ViewGroup中是如何实现绘制子View的,进入dispatchDraw()方法,发现再代码中,利用for循环调用了drawChild()方法

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

这里最终调用了子View的draw()方法。

ViewGroup的绘制(主要步骤)
1)drawBackground()
2)onDraw()
3)dispatchDraw()
4)onDrawForeground()

View的绘制(主要步骤)
1)drawBackground()
2)onDraw()
3)onDrawForeground()

那么在自定义View的时候,如果该View是一个容器,那么需要实现其onMeasure,onLayout,onDraw方法,如果不是,则需要实现其onMeasure和onDraw方法,其中onDraw方法可选。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值