简单聊聊Android View绘制流程

2019-03-31  本文已影响0人  Jevely

哈喽,大家好。今天我们来简单聊聊Android中View的绘制流程。这些东西个人感觉挺枯燥的,不过又是必须要掌握的东西,只有硬着头皮学了。本篇文章我会尽量说的简单易懂,如果有不懂得,可以留言交流。

好了,废话不多说,下面开始正文。


说到View绘制流程,我们就从绘制流程的触发开始讲起。
View的绘制是ActivityThread在创建Activity后调用的handleResumeActivity方法中触发的,所以我们在Avtivity的onCreate方法中获取到的View宽高都是0也是这么来的,因为还没开始绘制。下面我们来看看handleResumeActivity方法

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ...
            WindowManager.LayoutParams l = r.window.getAttributes();
            ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);//这里调用的是WindowManagerImpl的addView方法
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }

        } else if (!willBeVisible) {
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }
       ...
    }

代码省略了很多,其中注释的地方就是关键。我们跟进去发现最后是调用WindowManagerGlobal类的addView方法。下面我们来看看这个方法的源码

    public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
        ...
        ViewRootImpl root;

        synchronized (mLock) {
            ...
            root = new ViewRootImpl(view.getContext(), display);
            ...
            try {
                root.setView(view, wparams, panelParentView);//这里调用了ViewRootImpl的setView方法
            } catch (RuntimeException e) {
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                ...
                requestLayout();//继续跟进
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
               ...
            }
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {
            ...
            performTraversals();
            ...
        }
    }

最终我们调用了ViewRootImpl的performTraversals方法,在这个方法中我们会分别调用performMeasure,performLayout和performDraw方法,这三个方法分别就是对应View中的measure,layout和draw方法。这样一来,View绘制是如何触发的就有一个大致的了解了。


下面我们从measure方法开始分析。

MeasureSpec

MeasureSpec封装了控件的布局需求。measure respec由size和mode组成。其中size是大小,mode是模式。mode一共有三种:

我们对MeasureSpec做了一个简单的介绍,下面我们来看看MeasureSpec的值是如何生成的。首先一个MeasureSpec是由父控件的MeasureSpec和子控件的LayoutParams一起生成的,所以有的说这个是父控件对子控件的约束是不完全正确的。那么有人肯定就会有一个疑问,那根布局如何生成这个MeasureSpec呢。下面我们就开看看根布局是如何生成MeasureSpec的

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
   + mWidth + " measuredWidth=" + host.getMeasuredWidth()
   + " mHeight=" + mHeight
   + " measuredHeight=" + host.getMeasuredHeight()
   + " coveredInsetsChanged=" + contentInsetsChanged);

这里系统调用了getRootMeasureSpec方法来获取MeasureSpec。下面我们来看看getRootMeasureSpec的内部实现

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

这里根据传进来的windowSize和rootDimension来生成对应的MeasureSpec。这里的windowSize一般为屏幕的高宽。

在知道了根布局是如何获取的MeasureSpec之后,我们来看看ViewGroup在测量子View时是如何生成相应的MeasureSpec的。因为ViewGroup中并没有具体实现onMeasure方法,所以我们来用LinearLayout来看看

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

LinearLayout的onMeasure方法分了垂直和水平两种情况,我们就看看垂直情况。

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        final int count = getVirtualChildCount();

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        ...
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }
           ...
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;

            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                // Optimization: don't bother measuring children who are only
                // laid out using excess space. These views will get measured
                // later if we have space to distribute.
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                ...
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                //这里是测量子View高宽的方法
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight);
                ...
            }
        ...
        }
        ...
    }

这里调用了measureChildBeforeLayout方法来测量子View的高宽,我们进入方法,看看是如何生成子View需要的MeasureSpec的

    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();
        //这里是获取子View宽的MeasureSpec,传入的参数以此为父控件MeasureSpec,已使用宽(为0)加上Margin和Padding的值,
       //子控件的LayoutParams宽的模式。
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
      //这里是获取子View宽的MeasureSpec,参数同上。
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
      //调用子View的measure方法。
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

在代码中我加入了注释,我们可以看到最终是调用getChildMeasureSpec方法来获取子View需要的MeasureSpec。下面就来看看getChildMeasureSpec方法的内部实现

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //根据方法getMode和getSize分别获取父控件的模式和大小。
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
       //计算出合适的大小。
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        //如果父控件的布局需求模式为EXACTLY。
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                //如果子View有指定的值,那么就直接等于这个值。
                resultSize = childDimension;
                //子View模式为EXACTLY。
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 如果子控件大小设置为MATCH_PARENT(childDimension = -1),那么就让他等于父控件的大小。
                resultSize = size;
                //子View模式为EXACTLY。
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                 // 如果子控件大小设置为WRAP_CONTENT(childDimension = -2),那么就让他小于等于父控件的大小。
                resultSize = size;
                //子View模式为AT_MOST。
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 如果父控件的模式为AT_MOST。
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                //如果子View有固定大小,那么就等于固定的大小。
                resultSize = childDimension;
                //子View模式为EXACTLY。
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //子View为MATCH_PARENT,那么就应该等于父控件的大小,所以将值设置成size。
                resultSize = size;
                //子View模式为AT_MOST。
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //子View的大小为小于等于父控件大小,所以将值设置成size。
                resultSize = size;
                //子View模式为AT_MOST。
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // UNSPECIFIED多为系统使用,这里我们就不具体分析了,代码比较简单,有兴趣的同学可以自己看看。
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

代码中加入了注释,大家可以仔细的看看,有问题的话可以留言相互交流下。


measure

介绍完MeasureSpec过后,我们从ViewGroup来开始分析是如何测量控件高宽的。因为各个布局的排列方式不一样,所以ViewGroup没有对测量方法有具体的实现,所以我们结合上面讲过的LinearLayout来详细分析下它是如何对子View进行高宽测量的。因为LinearLayout最终也是继承View的,它这里只实现了View的onMeasure方法,所以我们直接看他的onMeasure方法。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

我们可以看到方法中是分垂直排列和水平排列来分别测量的,我们就看垂直排列就好。水平排列大家有兴趣的可以自己去看看。

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;
        int maxWidth = 0;
        int childState = 0;
        int alternativeMaxWidth = 0;
        int weightedMaxWidth = 0;
        boolean allFillParent = true;
        float totalWeight = 0;
        //获取子View数量
        final int count = getVirtualChildCount();
        //获取父控件布局高宽的规则模式
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        boolean matchWidth = false;
        boolean skippedMeasure = false;

        final int baselineChildIndex = mBaselineAlignedChildIndex;
        final boolean useLargestChild = mUseLargestChild;

        int largestChildHeight = Integer.MIN_VALUE;
        int consumedExcessSpace = 0;

        int nonSkippedChildCount = 0;

        //开始循环测量子View的高宽
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                //如果子View为空,总高度加0
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               //如果子View为不可见,跳过
               i += getChildrenSkipCount(child, i);
               continue;
            }

            nonSkippedChildCount++;
            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }
            //获取子View的LayoutParams
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            //记录子View是否有设置weight属性
            totalWeight += lp.weight;

            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;

            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                //如果父控件布局模式为EXACTLY ,并且子View有设置weight 属性。先记录子View的topMargin和bottomMargin值。
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                if (useExcessSpace) {
                    //如果父控件布局不为EXACTLY ,先设置子View的height为 WRAP_CONTENT来测量子View高宽。
                    lp.height = LayoutParams.WRAP_CONTENT;
                }

                //测量子View的高宽
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);
                //获取子View测量后的高度
                final int childHeight = child.getMeasuredHeight();
                if (useExcessSpace) {
                    //如果子View的weight >0,并且height原来值为0,这里把height 改为0;
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }
                //累加高度
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }

            ...

            boolean matchWidthLocally = false;
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                //记录一下父控件布局规则不为EXACTLY 而子View的width为 MATCH_PARENT的情况
                matchWidth = true;
                matchWidthLocally = true;
            }
            //计算子View宽度
            final int margin = lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            //记录是否所有子View宽度都设置为MATCH_PARENT
            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
            if (lp.weight > 0) {
                /*
                 * Widths of weighted Views are bogus if we end up
                 * remeasuring, so keep them separate.
                 */
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }
        ...
        //总高度加入padding的值
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        //和背景高度对比
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        //测量高度和MeasureSpec中的高度对比
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
        //计算剩余高度
        int remainingExcess = heightSize - mTotalLength
                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
        if (skippedMeasure
                || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
            float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
            //如果前面有父控件布局模式为EXACTLY,并且子View有设置weight属性和height为0时会进入这个判断,重新测量子View高宽。
            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null || child.getVisibility() == View.GONE) {
                    continue;
                }
                //根据剩余高度来测量有weight属性的子View高度
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final float childWeight = lp.weight;
                if (childWeight > 0) {
                    final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                    remainingExcess -= share;
                    remainingWeightSum -= childWeight;

                    final int childHeight;
                    if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                        childHeight = largestChildHeight;
                    } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                            || heightMode == MeasureSpec.EXACTLY)) {
                        // This child needs to be laid out from scratch using
                        // only its share of excess space.
                        childHeight = share;
                    } else {
                        // This child had some intrinsic height to which we
                        // need to add its share of excess space.
                        childHeight = child.getMeasuredHeight() + share;
                    }

                    final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            Math.max(0, childHeight), MeasureSpec.EXACTLY);
                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                            lp.width);
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Child may now not fit in vertical dimension.
                    childState = combineMeasuredStates(childState, child.getMeasuredState()
                            & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                }
                //计算子View宽度
                final int margin =  lp.leftMargin + lp.rightMargin;
                final int measuredWidth = child.getMeasuredWidth() + margin;
                maxWidth = Math.max(maxWidth, measuredWidth);

                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                        lp.width == LayoutParams.MATCH_PARENT;

                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
                //记录是否所有子View宽度都设置为MATCH_PARENT
                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
                //累加所有子View高度
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }

            //总高度加入padding值
            mTotalLength += mPaddingTop + mPaddingBottom;
            // TODO: Should we recompute the heightSpec based on the new total length?
        } else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth, weightedMaxWidth);
            ...
        }

        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
            maxWidth = alternativeMaxWidth;
        }
        //宽度加入padding值
        maxWidth += mPaddingLeft + mPaddingRight;

        //对比宽度和背景宽度
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        //设置LinearLayout的高宽
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

代码中加入了注释,我省略掉了不常用的代码。流程比较复杂,大家可以慢慢的看几次,加深理解。


在看过LinearLayout的onMeasure方法过后,我们来看看View的是如何对自己进行测量的。

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
    }

View的measure方法中我们可以看到调用了onMeasure方法

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //调用setMeasuredDimension方法来设置View的高宽
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

    protected int getSuggestedMinimumWidth() {
        //对比宽和View背景的大小
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
    //根据传入的measureSpec来获取高宽的值
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

View的测量代码中加入了简单的注释,有问题的话可以留言讨论。


layout

measure分析完成过后,我们来分析一下layout的流程。
Layout的作用是ViewGroup用来确定子View的位置,当ViewGroup的位置确定过后,会在Onlayout方法中遍历所有子View并调用其layout方法来确定子View的位置。layout方法是确定View本身的位置,下面我们来看看View的layout方法

    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;

        //先调用setFrame方法,将l,t,r,b四个值传入,确定View在父控件中的位置。
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            //调用OnLayout方法来确定子View的位置
            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);
                }
            }
        }
    ...
    }

在代码中加入了关键方法的注释,其他代码我们先忽略,在确定自己位置之后,会去确定子View的位置,那么我们来看看OnLayout的实现

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

我们看到OnLayout是一个空实现,它和onMeasure方法一样,不同的布局会有不同的实现,所以我们还是在LinearLayout中来分析下是如何实现OnLayout方法的

    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);
        }
    }

因为LinearLayout的特性,这里分为水平和垂直两种,我们来看看垂直的layoutVertical

    void layoutVertical(int left, int top, int right, int bottom) {
        ...
        final int count = getVirtualChildCount();
        ...
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                //如果child为null,那么childTop加0
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                //获取child的宽和高
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
               ...
                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }

                childTop += lp.topMargin;
                //调用setChildFrame方法,传入子View距离父控件左边和顶部的距离,再传入子View的高宽
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                //childTop 累加子View高度,使下一个子View排列在改View下方刚好符合LinearLayout垂直排列的特性
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }

代码中主要是调用setChildFrame方法来确定子View的位置,我们进入setChildFrame方法看看

    private void setChildFrame(View child, int left, int top, int width, int height) {
        child.layout(left, top, left + width, top + height);
    }

我们看到在方法中调用了子View自己的layout方法来确定自己的位置,这下layout方法也就明了了。


draw

最后我们来看看View的draw方法,调用这个方法过后View的绘制也将完成。
draw的流程相对来说比较简单,主要是分了如下几个步骤:
1.绘制背景
2.绘制自己
3.绘制children
3.绘制装饰

下面我们来看看draw的代码

    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)
         */

        // 1.绘制背景
        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) {
            // 2.绘制自己
            if (!dirtyOpaque) onDraw(canvas);

            //3.绘制children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

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

            // 4.绘制装饰
            onDrawForeground(canvas);

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

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

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

代码中有简单的解释,在绘制子View是调用的是dispatchDraw方法,在ViewGroup中遍历所有子View,并最终调用子View的draw方法,这样绘制就一层层的传递下去。


到此为止整个View的绘制流程就分析完成了,其中没有很细节的去分析每一句代码,个人认为分析源码主要是为了了解Android机制的一些流程。

文中有错误的地方欢迎大家提出,我会及时修改。

谢谢观看!!!

上一篇下一篇

猜你喜欢

热点阅读