View绘制流程(三)

在调用requestLayout之后,最终调用到了performTraversals
《深入理解Android卷III》将performTraversals分为了四个过程:应用程序的预测量过程、WMS布局窗口阶段、最终测量阶段、最终布局控件树阶段、绘制阶段

// ViewRootImpl#performTraversals
    private void performTraversals() {
        // cache mView since it is used so much below...
        // mView为DecorView对象
        final View host = mView;

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals");
            host.debug();
        }

        if (host == null || !mAdded)
            return;
        // 是否正在遍历view
        mIsInTraversal = true;
        // 是否需要绘制
        mWillDrawSoon = true;
        // 界面尺寸是否发生变化
        boolean windowSizeMayChange = false;
        // 是否是新界面
        boolean newSurface = false;
        // 界面是否发生变化(看着好像与上面的变量冲突了)
        boolean surfaceChanged = false;
        // DecorView的layoutParams
        WindowManager.LayoutParams lp = mWindowAttributes;
        // DecorView的宽度与高度
        int desiredWindowWidth;
        int desiredWindowHeight;

        final int viewVisibility = getHostVisibility();
        final boolean viewVisibilityChanged = !mFirst
                && (mViewVisibility != viewVisibility || mNewSurfaceNeeded
                // Also check for possible double visibility update, which will make current
                // viewVisibility value equal to mViewVisibility and we may miss it.
                || mAppVisibilityChanged);
        mAppVisibilityChanged = false;
        final boolean viewUserVisibilityChanged = !mFirst &&
                ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));

        WindowManager.LayoutParams params = null;
        if (mWindowAttributesChanged) {
            mWindowAttributesChanged = false;
            surfaceChanged = true;
            params = lp;
        }
        CompatibilityInfo compatibilityInfo =
                mDisplay.getDisplayAdjustments().getCompatibilityInfo();
        if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
            params = lp;
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            if (mLastInCompatMode) {
                params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
                mLastInCompatMode = false;
            } else {
                params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
                mLastInCompatMode = true;
            }
        }

        mWindowAttributesChangesFlag = 0;
        // mWinFrame用来Activity窗口的宽度和高度
        Rect frame = mWinFrame;
        // 如果是第一次执行界面的测量绘制工作的话
        if (mFirst) {
            // 是否全部绘制
            mFullRedrawNeeded = true;
            // 是否重新布局view的位置即layout
            mLayoutRequested = true;

            final Configuration config = mContext.getResources().getConfiguration();
            //  判断界面是否有状态栏和输入键盘
            if (shouldUseDisplaySize(lp)) {
                // NOTE -- system code, won't try to do compat mode.
                // 如果是的话,那么就设置当前activity的宽度和高度为减去状态栏和键盘弹的
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {
                // 如果没有的话,就是整个屏幕的高度
                desiredWindowWidth = dipToPx(config.screenWidthDp);
                desiredWindowHeight = dipToPx(config.screenHeightDp);
            }
                // ....代码省略
        } else {
              // 如果不是第一次执行测量工作的话,就会从上一次执行完并保存到mWinFrame/frame中的宽高
              // 度中进行获取
            desiredWindowWidth = frame.width();
            desiredWindowHeight = frame.height();
              // mWidth也是上一次执行完测量并进行保存的值,但是与frame的不同之处下面再说
            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
                // 如果两值不同 则说明界面发生了变化,设置界面发生变化标识,
                mFullRedrawNeeded = true;
                mLayoutRequested = true;
                windowSizeMayChange = true;
            }
        }

        // .... 代码省略

        // Execute enqueued actions on every traversal in case a detached view enqueued an action
        // 这里执行RunQueue中的回调。当我们在子线程中是,可以通过View.post()切换到主线程
        // 为什么可以切换到主线程呢,就是因为RunQueue,通过post将Runnable添加到RunQueue队列中。
        // 然后RunQueue将Runnable发送到主线程的Handler。
        getRunQueue().executeActions(mAttachInfo.mHandler);

        boolean insetsChanged = false;
        //  是否需要重新重新layout布局,mLayoutRequested在第一次进行绘制为true,非第一次
        // 但是界面尺寸发生变化时也会为true,所以简单推测就是当前界面需不需要重新绘制。
        boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
        if (layoutRequested) {
            // 这里就是预测量阶段,遍历整个view树,得到顶层DecorView的尺寸,主要是通过measureHierarchy()
            final Resources res = mView.getContext().getResources();
            // 如果界面需要重新绘制 并且是第一次绘制的话
            if (mFirst) {
                // make sure touch mode code executes by setting cached value
                // to opposite of the added touch mode.
                // 设置触摸模式
                mAttachInfo.mInTouchMode = !mAddedTouchMode;
                ensureTouchModeLocally(mAddedTouchMode);
            } else {
                if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
                    insetsChanged = true;
                }
                if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
                    insetsChanged = true;
                }
                if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
                    insetsChanged = true;
                }
                if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
                    if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                            + mAttachInfo.mVisibleInsets);
                }
                if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
                    insetsChanged = true;
                }
                if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) {
                    insetsChanged = true;
                }
                // 在非第一次绘制窗口 并且窗口的宽高是wrap_content时,则需要重新为窗口设置尺寸
                if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
                        || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    windowSizeMayChange = true;

                    if (shouldUseDisplaySize(lp)) {
                        // NOTE -- system code, won't try to do compat mode.
                        Point size = new Point();
                        mDisplay.getRealSize(size);
                        desiredWindowWidth = size.x;
                        desiredWindowHeight = size.y;
                    } else {
                        Configuration config = res.getConfiguration();
                        desiredWindowWidth = dipToPx(config.screenWidthDp);
                        desiredWindowHeight = dipToPx(config.screenHeightDp);
                    }
                }
            }
            // 因为上面已经做过判断了 如果是第一次执行刷新,那么一定会去计算屏幕的尺寸,所以只有在
            // 非第一次的时候会做测量,如果屏幕的layoutparams是MATCH_PARENT类型的,说明尺寸是
            // 确定的,(其实WARP_CONTENT和MATCH_PARENT对于顶层view是一样的),而如果是WRAP_CONTENT类型的话,
            // 网上说的是悬浮窗类型的,因为如果一个activity的顶层view是WRAP_CONTENT,也可以理解为
            // 非全屏的,那么此Activity的会覆盖到上一个Activity上,并且上个Activity的界面会显示一部分出来
            // 所以下面的测量也是简单的将顶层view的尺寸设置为全屏,具体多大,会在下面再根据子view测量
            // Ask host how big it wants to be
            // 无论第一次还是非第一次,但凡窗口发生了变化都会通过measureHierarchy对view树进行预测量,
            // 这里的目的主要是测量DecorView的尺寸
            windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
        }
        // ...代码省略
        if (layoutRequested) {
            // Clear this now, so that if anything requests a layout in the
            // rest of this function we will catch it and re-run a full
            // layout pass.
            mLayoutRequested = false;
        }
        // 这里是判断窗口是否需要重新测量,
        boolean windowShouldResize = layoutRequested && windowSizeMayChange
            && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
                || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
                        frame.width() < desiredWindowWidth && frame.width() != mWidth)
                || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
                        frame.height() < desiredWindowHeight && frame.height() != mHeight));
        windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;

        // If the activity was just relaunched, it might have unfrozen the task bounds (while
        // relaunching), so we need to force a call into window manager to pick up the latest
        // bounds.
        windowShouldResize |= mActivityRelaunched;

        // Determine whether to compute insets.
        // If there are no inset listeners remaining then we may still need to compute
        // insets in case the old insets were non-empty and must be reset.
        final boolean computesInternalInsets =
                mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
                || mAttachInfo.mHasNonEmptyGivenInternalInsets;

        boolean insetsPending = false;
        int relayoutResult = 0;
        boolean updatedConfiguration = false;

        final int surfaceGenerationId = mSurface.getGenerationId();

        final boolean isViewVisible = viewVisibility == View.VISIBLE;
        final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
        // 标识一
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            mForceNextWindowRelayout = false;

             // ...代码省略  

            try {
               //  .....
                // 这里就是布局窗口阶段,通过WMS来计算窗口的大小,并最终保存到入参里面
                // relayoutWindow是用来请求WindowManagerService服务计算Activity窗口大小的
                // 计算完毕之后,Activity窗口的大小就会保存在ViewRootImpl类的成员变量mWinFrame中
                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

                //  ....

                final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
                        mAttachInfo.mOverscanInsets);
                contentInsetsChanged = !mPendingContentInsets.equals(
                        mAttachInfo.mContentInsets);
                final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
                        mAttachInfo.mVisibleInsets);
                final boolean stableInsetsChanged = !mPendingStableInsets.equals(
                        mAttachInfo.mStableInsets);
                final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
                final boolean surfaceSizeChanged = (relayoutResult
                        & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
                final boolean alwaysConsumeNavBarChanged =
                        mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar;

             // .... 代码省略
            // 如果Activity没有处于暂停状态
            if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                // WMS测量出来的宽高尺寸与上次测量的不一致,则说明界面发生了变化,需要重新绘制
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    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);

                     // Ask host how big it wants to be
                    //   这里就是最终测量阶段 
                    // 真正执行测量的地方
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;

                    if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    // 这里是进行重新测量的的地方
                    if (measureAgain) {
                        if (DEBUG_LAYOUT) Log.v(mTag,
                                "And hey let's measure once more: width=" + width
                                + " height=" + height);
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;
                }
            }
        } else {
            // Not the first pass and no window/insets/visibility change but the window
            // may have moved and we need check that and if so to update the left and right
            // in the attach info. We translate only the window frame since on window move
            // the window manager tells us only for the new frame but the insets are the
            // same and we do not want to translate them more than once.
            // 这里是对应 标识一的if,如果不需要重新绘制,这里会判断窗口是否发生了 移动,
            // 如果移动了则会执行移动动画
            maybeHandleWindowMove(frame);
        }
        // 这里判断是否需要重新布局view树
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            //  布局view树阶段
            // 执行onLayout
            performLayout(lp, mWidth, mHeight);

            // By this point all views have been sized and positioned
            // We can compute the transparent area

            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
                // start out transparent
                // TODO: AVOID THAT CALL BY CACHING THE RESULT?
                host.getLocationInWindow(mTmpLocation);
                mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
                        mTmpLocation[0] + host.mRight - host.mLeft,
                        mTmpLocation[1] + host.mBottom - host.mTop);

                host.gatherTransparentRegion(mTransparentRegion);
                if (mTranslator != null) {
                    mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
                }

                if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
                    mPreviousTransparentRegion.set(mTransparentRegion);
                    mFullRedrawNeeded = true;
                    // reconfigure window manager
                    try {
                        mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
                    } catch (RemoteException e) {
                    }
                }
            }

            if (DBG) {
                System.out.println("======================================");
                System.out.println("performTraversals -- after setFrame");
                host.debug();
            }
        }
        // 这里判断是否取消绘制
        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

        if (!cancelDraw && !newSurface) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }
            // 绘制阶段  
            // 如果不取消 回调onDraw
            performDraw();
        } else {
            if (isViewVisible) {
                // Try again,这里是重新绘制
                scheduleTraversals();
            } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }
        }

        mIsInTraversal = false;
    }

注意的地方就只有一点就是mWidth、mHeight和mWinFrame中的宽高值的不同,前者是应用程序主动向WMS请求绘制,然后绘制的结果保存到mWinFrame,并赋值给mWidth/mHeight,此时mWinFrame中的宽高值是与mWidth/mHeight一样的;而如果由于某些原因,WMS主动绘制计算,然后应用程序被动接收绘制结果并保存到mWinFrame,此时mWinFrame中的宽高度和mWidth/mHeight是不同的
performTraversals是绘制流程的入口,从测量到布局最后再到绘制

// FrameLayout#onMeasure

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        //  首先明确一点widthMeasureSpec/heightMeasureSpec是父view传递过来的
        // 根据父view的宽高规格和子view的宽高规格共同确定的(下面会细说,影响的主要是规格)
        // 而下面则是判断父view的宽高是否是不确定的,也就是宽高是否是wrap_content的
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();
        // 本方法的作用就是用来测量当前view的宽高的,并用maxHeight和maxWidth储存宽高,
        // 而此方法是FrameLayout的方法,根据FrameLayout的特征,如果FrameLayout是wrap_content
        // 类型的,那么FrameLayout的宽高则根据子view中最大的view的宽高来设定自己的宽高(可能还有padding和margin)
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
        //  这里遍历子view
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                //  这里测量子view的宽高(measureChildWithMargins下面再细说)
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //  根据测量的当前view 的宽高与上一次遍历测量的子view的宽高来取较大值
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                // 如果当前view(FrameLayot)的宽高是AS_MOST类型的,并且当前子view是的宽高是
                // MATCH_PARENT 类型的,则将当前子view先保存到mMatchParentChildren中。
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        // Account for padding too
        // 为当前view已经测量出来的宽高再添加背景的padding
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // Check against our minimum height and width
        // 已经测量出来的宽高与设置的最小宽高对比,并取最大值。如果当前为当前view设置了图片背景,则
        // 与背景的宽高对比,并取最大值。
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        // 当前view的背景的宽高对比(如果当前背景不为空的话),并取最大值。
        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }
        // 将测量完的宽高通过setMeasuredDimension保存起来(resolveSizeAndState下面在细说)
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
        // 上面说,如果当前view的宽高类型是AS_MOST类型的,并且ziview是MATCH_PARENT类型的,会
        // 将此子view保存到mMatchParentChildren
        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

                final int childWidthMeasureSpec;
                if (lp.width == LayoutParams.MATCH_PARENT) {
                    // 获取当前view的内容区域宽度(如果ziview是MATCH_PARENT类型的,那么其宽高就是父         
                    // view的内容宽高---对于FrameLayout来说)
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
                    // 为子view重新设置宽度MeasureSpec的规格和尺寸
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else {
                    // getChildMeasureSpec,根据当前view以及当前子view的规格,返回当前子view合适的规格和尺寸。下面会在细说
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }
                // 高度同宽度一样,这里不在多说
                final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }
                // 这里重新测量ziview的宽高
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

上面的代码我都有写注释,细节就不在说了,onMeasure本身的作用就是测量当前view的宽高,并将值进行保存,而如果当前view是ViewGroup类型的,那么就会先去遍历子view,先去测量子view的宽高,因为有的父view的宽高会收子view的影响,比如wrap_content类型的FrameLayout,然后通过子view的宽高最终会得到一个合适的当前父view的宽高。
上面有几个方法没有细说,
measureChildWithMargins测量子view的宽高,涉及view树的遍历;
resolveSizeAndState根据当前view的规格返回相应的尺寸(子view的状态暂且不考虑);
getChildMeasureSpec根据当前view以及当前子view的规格,返回当前子view合适的规格和尺寸。

// measureChildWithMargins
// child:当前子view的对象;
// parentWidthMeasureSpec/parentHeightMeasureSpec:当前(父)view的宽高的规格和尺寸;
// widthUsed/heightUsed::这里为0,好像是权重有关,这里不再细究,按0处理
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        // 获取到子view的LayoutParams
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        // 根据父view的规格尺寸、父view的padding和子view的marging、子view的规格尺寸,得到一个合适的规格尺寸
        // 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测量子view的宽高,因为measure最终会调用
        // onMeasure,所以最后又回到了onMeasure方法,但是此时的onMeasure是child的,如果child也是
        // 一个ViewGroup类型的,也会开始进行遍历,最终调用的最小的View的onMeasure()
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

// View#onMeasure
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
// View#getDefaultSize
   public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        // 获取到测量出来的当前view的规格尺寸
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        // 这个case可以忽略
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        // 下面两个感觉好敷衍,最终返回的就是传递过来的尺寸(如果没有重写onMeasure而是使用默认值的话)
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
// View#setMeasuredDimension
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
// View#setMeasuredDimensionRaw
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        // 将最终尺寸赋值给mMeasuredWidth / mMeasuredHeight 
        // 而在上面的onMeasure方法里面遍历子view的时候,通过child.getMeasuredWidth()得到的测量值,
        // 其实就是这两个属性值
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

所以measureChildWithMargins会测量完整个View树中的view的宽高尺寸;然后我们来看前面出现两次的方法getChildMeasureSpec,再说onMeasure开头的时候,onMeasure的参数是从父view传递过来,是通过父view以及子view的规格共同处理得到的 ,但是这个方法是ViewGroup独有的

// ViewGroup#getChildMeasureSpec
// spec是父view的规格尺寸;
// padding是父view的padding值+子view的margin值
// childDimension :子view的宽高尺寸  
 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        // 首先获取到父view的规格及尺寸
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        // 父view的尺寸 - 相对间距  = 可以给子view的最大尺寸
        int size = Math.max(0, specSize - padding);
        // 最终尺寸和规格值
        int resultSize = 0;
        int resultMode = 0;
        // 首先判断父view的规格
        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            // 如果父view的规格是确定的,并且子view也指定了一个尺寸,则设置子view的尺寸值为指定的
            // 尺寸值,规格为精确值规格,即 MeasureSpec.EXACTLY    
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                // 如果父view的规格是确定的,虽然没有为子view指定特定的值,但是设置了子view尺寸为
                // MATCH_PARENT,则为子view指定父view最大可以给的值,并设置子view的规格为MeasureSpec.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.
                // 如果父view的规格是确定的,不但没有为子view指定特定的值,还设置子view尺寸为
                // WRAP_CONTENT,则为子view指定父view最大可以给的值,并设置子view的规格为MeasureSpec.AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } 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的规格是不确定的,但是子view尺寸为MATCH_PARENT,
                // 则为子view指定父view最大可以给的值,并设置子view的规格为MeasureSpec.EXACTLY
                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.
                // 如果父view的规格是不确定的,并且子view尺寸为WRAP_CONTENT,
                // 则为子view指定父view最大可以给的值,并设置子view的规格为MeasureSpec.AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        // 这个不考虑
        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);
    }

所以上面的方法是根据父view以及子view的规格,最终得出了子view的规格以及尺寸,小结一下就是:

1、如果为子view指定了一个精确值,则不管父view的规格,直接设置子view的尺寸为指定的值,规格为EXACTLY
2、
3、如果父view的规格不确定,
2、不管父view的规格到底确不确定,子view的尺寸始终是父view可以给子view的最大尺寸值
3、如果父view的规格是确定的,即EXACTLY,那么规格则分两种情况(父view规格确定,那么只需要判
断子view的规格即可):
1)如果子view的规格确定,即MATCH_PARENT,则设置MeasureSpec.EXACTLY
2)如果子view的规格不确定,即WRAP_CONTENT,则设置MeasureSpec.AT_MOST
4、如果父view的规格是不确定,那就不用再分情况了,子view的规格都是AT_MOST  ,主要是因为父view的
尺寸不确定,子view也没办法去掉具体的尺寸值,所以先按照AT_MOST来设置。
(还记不记得在onMeasure中会将父view是AT_MOST情况下的子view是MATCH_PARENT的情况,都会保
存到一个集合中,等到整个view树测量完毕并且为父view设置了宽高尺寸之后,会在重新测量这些子view,
就是这个原因)

然后我们看resolveSizeAndState

// View#resolveSizeAndState
// size:预测量的当前view的最终尺寸
// measureSpec:当前view宽高的的规格尺寸
    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            // 如果当前view的规格是AT_MOST即没有设置精确值的话
            case MeasureSpec.AT_MOST:
                // 其实两个值都可以用,相对来说使用比较小的值,为了在绘制时少更快一些 
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                // 如果是确定值的话,则使用specSize
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

resolveSizeAndState看完,感觉有一种前功尽弃感,为什么,前面写了好多的代码都是为了计算确定当前view的宽高,而这里简简单单的一句判断,可能就会不再需要前面计算出的宽高了,当然这也是没有办法,都是为了防止AT_MOST这种情况的出现。

我们来看布局的方法

ViewRootImpl#performLayout
 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        if (host == null) {
            return;
        }
        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
            Log.v(mTag, "Laying out " + host + " to (" +
                    host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
        }

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        try {
            // 调用DecorView的layout方法,其中左侧相对于父view左侧的距离为0,顶部
            // 相对于父view顶部的距离为0
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

            mInLayout = false;
            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {
                // requestLayout() was called during layout.
                // If no layout-request flags are set on the requesting views, there is no problem.
                // If some requests are still pending, then we need to clear those flags and do
                // a full request/measure/layout pass to handle this situation.
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
                if (validLayoutRequesters != null) {
                    // Set this flag to indicate that any further requests are happening during
                    // the second pass, which may result in posting those requests to the next
                    // frame instead
                    mHandlingLayoutInLayoutRequest = true;

                    // Process fresh layout requests, then measure and layout
                    int numValidRequests = validLayoutRequesters.size();
                    for (int i = 0; i < numValidRequests; ++i) {
                        final View view = validLayoutRequesters.get(i);
                        Log.w("View", "requestLayout() improperly called by " + view +
                                " during layout: running second layout pass");
                        view.requestLayout();
                    }
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                    mHandlingLayoutInLayoutRequest = false;

                    // Check the valid requests again, this time without checking/clearing the
                    // layout flags, since requests happening during the second pass get noop'd
                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                    if (validLayoutRequesters != null) {
                        final ArrayList<View> finalRequesters = validLayoutRequesters;
                        // Post second-pass requests to the next frame
                        getRunQueue().post(new Runnable() {
                            @Override
                            public void run() {
                                int numValidRequests = finalRequesters.size();
                                for (int i = 0; i < numValidRequests; ++i) {
                                    final View view = finalRequesters.get(i);
                                    Log.w("View", "requestLayout() improperly called by " + view +
                                            " during second layout pass: posting in next frame");
                                    view.requestLayout();
                                }
                            }
                        });
                    }
                }

            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
    }

我们接下来看layout布局
ViewRootImpl#performLayout -> ViewGroup#layout -> 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;
        }
        // 将原先的view的坐标进行保存
        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);
          // .. 代码省略 
        }

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

这里调用到了onLayout,onLayout的作用是为当前视图的子视图分配位置,所以,在view中的onLayout是一个空实现,而只有在ViewGroup类型的父视图中才会重写。关于setOpticalFrame和setOpticalFrame我们下面会再说
我们以DecorView为例,DecorView继承于FrameLayout

// 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();
        // 因为我们是要为子view分配位置,但是位置是相对于父view的,所以要先拿到父view(当前view)的
        // 位置信息
        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);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                // 获取到子view的测量宽高
                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) {
                        // 根据子view的居中、靠左、靠右等为子view分配合适的位置
                    case Gravity.CENTER_HORIZONTAL:
                        // 如果是水平居中,
                        // 子view的左边位置 = 父view的左边距 +(父view的宽带 - 子view的宽度)/ 2  + 子view的左margin  - 子view的margin(这里可以看出 margin和居中是会叠加的)
                        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;
                }
                 // 上面的计算就不在一一叙述了,这里调用了子view的layout,
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

上面我们知道,在onLayout里面会遍历子view并调用子view的layout(),如果子view是viewGroup类型的, 最终也是会走到这里,然后在继续向下遍历,直到找到最底层的view,最后又回到了View#layout();
在View#layout()中我们有两个方法还没有说到,setOpticalFrame()最终调用的也是setFrame(),因此我们只看setFrame()。

  protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
        // 判断新位置与旧位置不是完全相同,则认为是发生了变化,需要重新刷新界面
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;

            // Remember our drawn bit
            int drawn = mPrivateFlags & PFLAG_DRAWN;

            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            // Invalidate our old position
            invalidate(sizeChanged);
            // 将传递进来的新位置信息进行存储
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            mPrivateFlags |= PFLAG_HAS_BOUNDS;

            // 如果布局发生变化,会回调onSizeChanged()
            if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }
            // ....
        }
        return changed;
    }

我们来看invalidata()

    public void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {

        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
     
            // Propagate the damage rectangle to the parent view.
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                // 将坐标保存到damage中,并调用父view的invalidateChild
                damage.set(l, t, r, b);
                p.invalidateChild(this, damage);
            }
        }
    }
// ViewGourp#invalidataChild
    @Override
    public final void invalidateChild(View child, final Rect dirty) {
        final AttachInfo attachInfo = mAttachInfo;
        ViewParent parent = this;
        if (attachInfo != null) {
          //  .... 代码省略
            do {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }
                parent = parent.invalidateChildInParent(location, dirty);
                if (view != null) {
                    // Account for transform on current parent
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        dirty.set((int) Math.floor(boundingRect.left),
                                (int) Math.floor(boundingRect.top),
                                (int) Math.ceil(boundingRect.right),
                                (int) Math.ceil(boundingRect.bottom));
                    }
                }
            } while (parent != null);
        }
    }

上面是一个do-while循环,直到找到最顶层的view,也就是DecorView,在上一章说过,DecorView的父view是ViewRootImpl,

// ViewGroup#invalidateChildInParent
    @Override
    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
            // either DRAWN, or DRAWING_CACHE_VALID
            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
                    != FLAG_OPTIMIZE_INVALIDATE) {
                dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                        location[CHILD_TOP_INDEX] - mScrollY);
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }

                final int left = mLeft;
                final int top = mTop;

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
                        dirty.setEmpty();
                    }
                }

                location[CHILD_LEFT_INDEX] = left;
                location[CHILD_TOP_INDEX] = top;
            } else {

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
                } else {
                    // in case the dirty rect extends outside the bounds of this container
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }
                location[CHILD_LEFT_INDEX] = mLeft;
                location[CHILD_TOP_INDEX] = mTop;

                mPrivateFlags &= ~PFLAG_DRAWN;
            }
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            if (mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
            }

            return mParent;
        }

        return null;
    }

// ViewRootImpl#invalidateChildInParent
   @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);

        if (dirty == null) {
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }

        if (mCurScrollY != 0 || mTranslator != null) {
            mTempRect.set(dirty);
            dirty = mTempRect;
            if (mCurScrollY != 0) {
                dirty.offset(0, -mCurScrollY);
            }
            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(dirty);
            }
            if (mAttachInfo.mScalingRequired) {
                dirty.inset(-1, -1);
            }
        }
        // 这里最终将布局发生变化的区域作为重新绘制的区域,等待重新绘制
        invalidateRectOnScreen(dirty);

        return null;
    }

最后我们来看绘制

// ViewRootImpl#performDraw:
    private void performDraw() {

// ... 代码省略

        try {
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
// ViewRootImpl#draw()
  private void draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;

        final float appScale = mAttachInfo.mApplicationScale;
        final boolean scalingRequired = mAttachInfo.mScalingRequired;

        int resizeAlpha = 0;
        // 获取到我们的绘制区域
        final Rect dirty = mDirty;
        if (mSurfaceHolder != null) {
            // The app owns the surface, we won't draw.
            dirty.setEmpty();
            if (animating && mScroller != null) {
                mScroller.abortAnimation();
            }
            return;
        }
        // 这里的判断是是否需要绘制整个屏幕,
        if (fullRedrawNeeded) {
            mAttachInfo.mIgnoreDirtyState = true;
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        }

        // .... 代码省略

                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
    }

// ViewRootImpl#drawSoftware
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {

            // .... 代码省略 
  
            try {
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;
                // 最终调用view.draw
                mView.draw(canvas);

                drawAccessibilityFocusedDrawableIfNeeded(canvas);
            } finally {
                if (!attachInfo.mSetIgnoreDirtyState) {
                    // Only clear the flag if it was not set during the mView.draw() call
                    attachInfo.mIgnoreDirtyState = false;
                }
            }
        return true;
    }

    @CallSuper
    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
            // 通过onDraw绘制主体
            if (!dirtyOpaque) onDraw(canvas);
  
            // Step 4, draw the children   绘制子view
            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;
        }

        // ...代码省略

    }

主要是两个地方,一个是绘制主体,一个是绘制子view
onDraw在view里面并没有实现,具体的逻辑是根据view的派生类的自身情况具体实现的,比如TextView,这里我们不再多说;dispatchDraw只有ViewGroup的派生类中才会实现

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

      // .... 代码省略

      int clipSaveCount = 0;
      final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
      if (clipToPadding) {
          // 这里去掉padding的区域,只绘制内容区域
          clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
          canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                  mScrollX + mRight - mLeft - mPaddingRight,
                  mScrollY + mBottom - mTop - mPaddingBottom);
      }
      // 遍历子view
      for (int i = 0; i < childrenCount; i++) {
          while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
              final View transientChild = mTransientViews.get(transientIndex);
              if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                      transientChild.getAnimation() != null) {
                  // 绘制子view
                  more |= drawChild(canvas, transientChild, drawingTime);
              }
              transientIndex++;
              if (transientIndex >= transientCount) {
                  transientIndex = -1;
              }
          }

      if (clipToPadding) {
          canvas.restoreToCount(clipSaveCount);
      }

      // .... 代码省略

  }

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

  boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
      final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
      // 开启硬件加速
      boolean drawingWithRenderNode = mAttachInfo != null
              && mAttachInfo.mHardwareAccelerated
              && hardwareAcceleratedCanvas;
      
      int sx = 0;
      int sy = 0;
      if (!drawingWithRenderNode) {
          //  配合Scroller可以对滚动进行计算
          computeScroll();
          sx = mScrollX;
          sy = mScrollY;
      }

       // ...代码省略 

      if (!drawingWithDrawingCache) {
          if (drawingWithRenderNode) {
              mPrivateFlags &= ~PFLAG_DIRTY_MASK;
              ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
          } else {
              // Fast path for layouts with no backgrounds
              if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                  mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                  dispatchDraw(canvas);
              } else {
                  // 绘制自身
                  draw(canvas);
              }
          }
      } else if (cache != null) {

      }

      return more;
  }

绘制到这里就结束,小结一下
调用requestLayout之后,然后调用到了ViewRootImpl的performTraversals方法,在这个方法中,先是通过PerformMeasure对整个view树进行了一次预测量,方便对比窗口是否发生了变化,需要重新测量绘制等操作,然后通过WMS对窗口进行测量,并返回测量结果,拿到测量结果之后就开始进行一系列的测量、布局、绘制的工作

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容