view的绘制流程

view的绘制流程

一下内容均是从这个链接中来的:链接: [link](https://www.jianshu.com/p/1075d7d521ec).
这段时间学习自定义view,在这个过程中,碰到了一个问题:在onCreate()方法中获取view的宽高失败,没有获取到,返回的是0;
这是因为view的绘制还没有开始,只有在onResume()方法执行之后,才会开始准备绘制view,所以我们如果要获取view的宽高,就必须要在view绘制完后才能获取
以LinearLayout 布局为例,向里面添加三个textView

在这里插入图片描述

当onResume()方法执行完后,经过一系列的方法调用,最后会进入ViewRootImpl类的requestLayout()方法
下面是setContentView() 中的源码,里面有这样一行代码 mLayoutInflater.inflate(layoutResID, mContentParent); 会来到 ViewRootImpl 类的 requestLayout() 方法。View 的绘制流程就是从这个方法开始的。
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            // 一系列方法下来之后,进入performTraversals这个方法真的很长
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

performTraversals(重点)

经过一系列的方法调用,最后会进入这个方法中来,这时候view的绘制才真正开始,这个方法中主要调用了以下三个方法来实现view的绘制:

View绘制流程第一步:

1、performMeasure 这个方法主要是对view进行测量,如果是viewGroup的话,比如LinearLayout,LinearLayout在调用onMeasure()方法的时候,会不断的循环测量子View,如果是垂直方向,高度是子View的高度叠加,我们现在来看看是怎么测量子View的。
  private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
         // 调用onMeasure()
         onMeasure(widthMeasureSpec, heightMeasureSpec); 
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            // 我们以垂直为例
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }
     void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        // See how tall everyone is. Also remember max width.
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            // 测量子孩子
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

            final int childHeight = child.getMeasuredHeight();

            final int totalLength = mTotalLength;
            // 高度是子View的高度不断的叠加
            mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));
        }
        int heightSize = mTotalLength;
        // Check against our minimum height
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
        
        // Reconcile our calculated size with the heightMeasureSpec
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        // 设置宽高
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);
    }
LinearLayout在调用onMeasure()方法的时候,会不断的循环测量子View,如果是垂直方向,高度是子View的高度叠加,我们现在来看看是怎么测量子View的。
    void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        //  getChildMeasureSpec 这个方法非常关键
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
getChildMeasureSpec()这个方法可以说很重要
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //获取当前Parent View的Mode和Size
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        //获取Parent size与padding差值(也就是Parent剩余大小),若差值小于0直接返回0
        int size = Math.max(0, specSize - padding);
        //定义返回值存储变量
        int resultSize = 0;
        int resultMode = 0;
        //依据当前Parent的Mode进行switch分支逻辑
        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                //如果child的layout_w和h属性在xml或者java中给予具体大于等于0的数值
                //设置child的size为真实layout_w和h属性值,mode为EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                //如果child的layout_wOrh属性在xml或者java中给予MATCH_PARENT
                //设置child的size为size,mode为EXACTLY
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                //如果child的layout_wOrh属性在xml或者java中给予WRAP_CONTENT
                //设置child的size为size,mode为AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // ......
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                // 如果父 View 是 AT_MOST 就算子 View 是 MATCH_PARENT,
                // 其实子View获得的测量模式还是AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

            // ......
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
View的绘制流程第一步是onMeasure(),该方法用来测量和指定布局到底占多大的宽高,因为控件的宽高是由父布局和控件本身来决定的,所以测量是不断的往内走,而最终确定宽高是由内不断的往外走,是递归的方式。

View绘制流程第二步:performLayout

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        final View host = mView;
        // 调用layout()方法
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    }

    public void layout(int l, int t, int r, int b) {
        onLayout(changed, l, t, r, b);
    }
接下来如果按照我们写的布局小示例,将会调用LinearLayout中的onLayout()方法
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            // 我们以垂直为例
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }
    void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;

        // Where right end of child should go
        //计算父窗口推荐的子View宽度
        final int width = right - left;
        //计算父窗口推荐的子View右侧位置
        int childRight = width - mPaddingRight;

        // Space available for child
        //child可使用空间大小
        int childSpace = width - paddingLeft - mPaddingRight;
        //通过ViewGroup的getChildCount方法获取ViewGroup的子View个数
        final int count = getVirtualChildCount();
        //获取Gravity属性设置
        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
        //依据majorGravity计算childTop的位置值
        switch (majorGravity) {
           case Gravity.BOTTOM:
               // mTotalLength contains the padding already
               childTop = mPaddingTop + bottom - top - mTotalLength;
               break;

               // mTotalLength contains the padding already
           case Gravity.CENTER_VERTICAL:
               childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
               break;

           case Gravity.TOP:
           default:
               childTop = mPaddingTop;
               break;
        }
        //重点!!!开始遍历
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                //LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的意义就是为layout过程提供视图显示范围的参考值
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                //获取子View的LayoutParams
                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();

                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                //依据不同的absoluteGravity计算childLeft位置
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                + lp.leftMargin - lp.rightMargin;
                        break;

                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - lp.rightMargin;
                        break;

                    case Gravity.LEFT:
                    default:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                }

                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }

                childTop += lp.topMargin;
                //通过垂直排列计算调运child的layout设置child的位置
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }
总结一下,从上面分析可以看出layout也是从顶层父View向子View的递归调用view.layout方法的过程,即父View根据第一步performMeasure,来获取子View所的布局大小和布局参数,将子View放在合适的位置上,不过这个方法没有再往外走,只是不断的往里面走

View绘制流程第三步:performDraw

这个方法主要是画,比如画文本,画圆,画线等等,但是对于继承ViewGroup的布局来说,不会用这个方法
    private void performDraw() {
        try {
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

    private void draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        if (!surface.isValid()) {
            return;
        }
        
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
            return;
        }
    }

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        // Draw with software renderer.
        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);
        // ... ...
        mView.draw(canvas);
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值