美文网首页
Android View从源码的角度分析绘制流程

Android View从源码的角度分析绘制流程

作者: 走在路上的日子 | 来源:发表于2018-07-30 10:10 被阅读0次

    那么今天我们继续来深入学习View的绘制流程,接着上次的View绘制开始,同样使用的是Android 7.1源码。

    1、回顾addView方法

    上篇文章从addView方法一路分析到了performTraversals()方法,这个方法非常长,内部逻辑也很复杂,但是主体逻辑很清晰。主要调用了performMeasure方法、performLayout方法和performDraw方法:

        private void performTraversals() {
            // cache mView since it is used so much below...
            final View host = mView;
     
            if (DBG) {
                System.out.println("======================================");
                System.out.println("performTraversals");
                host.debug();
            }
     
            if (host == null || !mAdded)
                return;
     
            mIsInTraversal = true;
            mWillDrawSoon = true;
            boolean windowSizeMayChange = false;
            boolean newSurface = false;
            boolean surfaceChanged = false;
            WindowManager.LayoutParams lp = mWindowAttributes;
     
            int desiredWindowWidth;
            int desiredWindowHeight;
     
            final int viewVisibility = getHostVisibility();
            final boolean viewVisibilityChanged = !mFirst
                    && (mViewVisibility != viewVisibility || mNewSurfaceNeeded);
            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;
     
            Rect frame = mWinFrame;
            if (mFirst) {
                mFullRedrawNeeded = true;
                mLayoutRequested = 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 = mContext.getResources().getConfiguration();
                    desiredWindowWidth = dipToPx(config.screenWidthDp);
                    desiredWindowHeight = dipToPx(config.screenHeightDp);
                }
     
                // We used to use the following condition to choose 32 bits drawing caches:
                // PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
                // However, windows are now always 32 bits by default, so choose 32 bits
                mAttachInfo.mUse32BitDrawingCache = true;
                mAttachInfo.mHasWindowFocus = false;
                mAttachInfo.mWindowVisibility = viewVisibility;
                mAttachInfo.mRecomputeGlobalAttributes = false;
                mLastConfiguration.setTo(host.getResources().getConfiguration());
                mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
                // Set the layout direction if it has not been set before (inherit is the default)
                if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
                    host.setLayoutDirection(mLastConfiguration.getLayoutDirection());
                }
                host.dispatchAttachedToWindow(mAttachInfo, 0);
                mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
                dispatchApplyInsets(host);
                //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
     
            } else {
                desiredWindowWidth = frame.width();
                desiredWindowHeight = frame.height();
                if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                    if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
                    mFullRedrawNeeded = true;
                    mLayoutRequested = true;
                    windowSizeMayChange = true;
                }
            }
     
            if (viewVisibilityChanged) {
                mAttachInfo.mWindowVisibility = viewVisibility;
                host.dispatchWindowVisibilityChanged(viewVisibility);
                if (viewUserVisibilityChanged) {
                    host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
                }
                if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
                    endDragResizing();
                    destroyHardwareResources();
                }
                if (viewVisibility == View.GONE) {
                    // After making a window gone, we will count it as being
                    // shown for the first time the next time it gets focus.
                    mHasHadWindowFocus = false;
                }
            }
     
            // Non-visible windows can't hold accessibility focus.
            if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
                host.clearAccessibilityFocus();
            }
     
            // Execute enqueued actions on every traversal in case a detached view enqueued an action
            getRunQueue().executeActions(mAttachInfo.mHandler);
     
            boolean insetsChanged = false;
     
            boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
            if (layoutRequested) {
     
                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;
                    }
                    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);
                        }
                    }
                }
     
                // Ask host how big it wants to be
                windowSizeMayChange |= measureHierarchy(host, lp, res,
                        desiredWindowWidth, desiredWindowHeight);
            }
     
            if (collectViewAttributes()) {
                params = lp;
            }
            if (mAttachInfo.mForceReportNewAttributes) {
                mAttachInfo.mForceReportNewAttributes = false;
                params = lp;
            }
     
            if (mFirst || mAttachInfo.mViewVisibilityChanged) {
                mAttachInfo.mViewVisibilityChanged = false;
                int resizeMode = mSoftInputMode &
                        WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
                // If we are in auto resize mode, then we need to determine
                // what mode to use now.
                if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
                    final int N = mAttachInfo.mScrollContainers.size();
                    for (int i=0; i<N; i++) {
                        if (mAttachInfo.mScrollContainers.get(i).isShown()) {
                            resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
                        }
                    }
                    if (resizeMode == 0) {
                        resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
                    }
                    if ((lp.softInputMode &
                            WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) != resizeMode) {
                        lp.softInputMode = (lp.softInputMode &
                                ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) |
                                resizeMode;
                        params = lp;
                    }
                }
            }
     
            if (params != null) {
                if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
                    if (!PixelFormat.formatHasAlpha(params.format)) {
                        params.format = PixelFormat.TRANSLUCENT;
                    }
                }
                mAttachInfo.mOverscanRequested = (params.flags
                        & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0;
            }
     
            if (mApplyInsetsRequested) {
                mApplyInsetsRequested = false;
                mLastOverscanRequested = mAttachInfo.mOverscanRequested;
                dispatchApplyInsets(host);
                if (mLayoutRequested) {
                    // Short-circuit catching a new layout request here, so
                    // we don't need to go through two layout passes when things
                    // change due to fitting system windows, which can happen a lot.
                    windowSizeMayChange |= measureHierarchy(host, lp,
                            mView.getContext().getResources(),
                            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;
            if (mFirst || windowShouldResize || insetsChanged ||
                    viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
                mForceNextWindowRelayout = false;
     
                if (isViewVisible) {
                    // If this window is giving internal insets to the window
                    // manager, and it is being added or changing its visibility,
                    // then we want to first give the window manager "fake"
                    // insets to cause it to effectively ignore the content of
                    // the window during layout.  This avoids it briefly causing
                    // other windows to resize/move based on the raw frame of the
                    // window, waiting until we can finish laying out this window
                    // and get back to the window manager with the ultimately
                    // computed insets.
                    insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
                }
     
                if (mSurfaceHolder != null) {
                    mSurfaceHolder.mSurfaceLock.lock();
                    mDrawingAllowed = true;
                }
     
                boolean hwInitialized = false;
                boolean contentInsetsChanged = false;
                boolean hadSurface = mSurface.isValid();
     
                try {
                    if (DEBUG_LAYOUT) {
                        Log.i(mTag, "host=w:" + host.getMeasuredWidth() + ", h:" +
                                host.getMeasuredHeight() + ", params=" + params);
                    }
     
                    if (mAttachInfo.mHardwareRenderer != null) {
                        // relayoutWindow may decide to destroy mSurface. As that decision
                        // happens in WindowManager service, we need to be defensive here
                        // and stop using the surface in case it gets destroyed.
                        if (mAttachInfo.mHardwareRenderer.pauseSurface(mSurface)) {
                            // Animations were running so we need to push a frame
                            // to resume them
                            mDirty.set(0, 0, mWidth, mHeight);
                        }
                        mChoreographer.mFrameInfo.addFlags(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED);
                    }
                    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
     
                    if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
                            + " overscan=" + mPendingOverscanInsets.toShortString()
                            + " content=" + mPendingContentInsets.toShortString()
                            + " visible=" + mPendingVisibleInsets.toShortString()
                            + " visible=" + mPendingStableInsets.toShortString()
                            + " outsets=" + mPendingOutsets.toShortString()
                            + " surface=" + mSurface);
     
                    if (mPendingConfiguration.seq != 0) {
                        if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: "
                                + mPendingConfiguration);
                        updateConfiguration(new Configuration(mPendingConfiguration), !mFirst);
                        mPendingConfiguration.seq = 0;
                        updatedConfiguration = true;
                    }
     
                    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;
                    if (contentInsetsChanged) {
                        mAttachInfo.mContentInsets.set(mPendingContentInsets);
                        if (DEBUG_LAYOUT) Log.v(mTag, "Content insets changing to: "
                                + mAttachInfo.mContentInsets);
                    }
                    if (overscanInsetsChanged) {
                        mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);
                        if (DEBUG_LAYOUT) Log.v(mTag, "Overscan insets changing to: "
                                + mAttachInfo.mOverscanInsets);
                        // Need to relayout with content insets.
                        contentInsetsChanged = true;
                    }
                    if (stableInsetsChanged) {
                        mAttachInfo.mStableInsets.set(mPendingStableInsets);
                        if (DEBUG_LAYOUT) Log.v(mTag, "Decor insets changing to: "
                                + mAttachInfo.mStableInsets);
                        // Need to relayout with content insets.
                        contentInsetsChanged = true;
                    }
                    if (alwaysConsumeNavBarChanged) {
                        mAttachInfo.mAlwaysConsumeNavBar = mPendingAlwaysConsumeNavBar;
                        contentInsetsChanged = true;
                    }
                    if (contentInsetsChanged || mLastSystemUiVisibility !=
                            mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested
                            || mLastOverscanRequested != mAttachInfo.mOverscanRequested
                            || outsetsChanged) {
                        mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
                        mLastOverscanRequested = mAttachInfo.mOverscanRequested;
                        mAttachInfo.mOutsets.set(mPendingOutsets);
                        mApplyInsetsRequested = false;
                        dispatchApplyInsets(host);
                    }
                    if (visibleInsetsChanged) {
                        mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
                        if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                                + mAttachInfo.mVisibleInsets);
                    }
     
                    if (!hadSurface) {
                        if (mSurface.isValid()) {
                            // If we are creating a new surface, then we need to
                            // completely redraw it.  Also, when we get to the
                            // point of drawing it we will hold off and schedule
                            // a new traversal instead.  This is so we can tell the
                            // window manager about all of the windows being displayed
                            // before actually drawing them, so it can display then
                            // all at once.
                            newSurface = true;
                            mFullRedrawNeeded = true;
                            mPreviousTransparentRegion.setEmpty();
     
                            // Only initialize up-front if transparent regions are not
                            // requested, otherwise defer to see if the entire window
                            // will be transparent
                            if (mAttachInfo.mHardwareRenderer != null) {
                                try {
                                    hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
                                            mSurface);
                                    if (hwInitialized && (host.mPrivateFlags
                                            & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
                                        // Don't pre-allocate if transparent regions
                                        // are requested as they may not be needed
                                        mSurface.allocateBuffers();
                                    }
                                } catch (OutOfResourcesException e) {
                                    handleOutOfResourcesException(e);
                                    return;
                                }
                            }
                        }
                    } else if (!mSurface.isValid()) {
                        // If the surface has been removed, then reset the scroll
                        // positions.
                        if (mLastScrolledFocus != null) {
                            mLastScrolledFocus.clear();
                        }
                        mScrollY = mCurScrollY = 0;
                        if (mView instanceof RootViewSurfaceTaker) {
                            ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
                        }
                        if (mScroller != null) {
                            mScroller.abortAnimation();
                        }
                        // Our surface is gone
                        if (mAttachInfo.mHardwareRenderer != null &&
                                mAttachInfo.mHardwareRenderer.isEnabled()) {
                            mAttachInfo.mHardwareRenderer.destroy();
                        }
                    } else if ((surfaceGenerationId != mSurface.getGenerationId()
                            || surfaceSizeChanged)
                            && mSurfaceHolder == null
                            && mAttachInfo.mHardwareRenderer != null) {
                        mFullRedrawNeeded = true;
                        try {
                            // Need to do updateSurface (which leads to CanvasContext::setSurface and
                            // re-create the EGLSurface) if either the Surface changed (as indicated by
                            // generation id), or WindowManager changed the surface size. The latter is
                            // because on some chips, changing the consumer side's BufferQueue size may
                            // not take effect immediately unless we create a new EGLSurface.
                            // Note that frame size change doesn't always imply surface size change (eg.
                            // drag resizing uses fullscreen surface), need to check surfaceSizeChanged
                            // flag from WindowManager.
                            mAttachInfo.mHardwareRenderer.updateSurface(mSurface);
                        } catch (OutOfResourcesException e) {
                            handleOutOfResourcesException(e);
                            return;
                        }
                    }
     
                    final boolean freeformResizing = (relayoutResult
                            & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
                    final boolean dockedResizing = (relayoutResult
                            & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
                    final boolean dragResizing = freeformResizing || dockedResizing;
                    if (mDragResizing != dragResizing) {
                        if (dragResizing) {
                            mResizeMode = freeformResizing
                                    ? RESIZE_MODE_FREEFORM
                                    : RESIZE_MODE_DOCKED_DIVIDER;
                            startDragResizing(mPendingBackDropFrame,
                                    mWinFrame.equals(mPendingBackDropFrame), mPendingVisibleInsets,
                                    mPendingStableInsets, mResizeMode);
                        } else {
                            // We shouldn't come here, but if we come we should end the resize.
                            endDragResizing();
                        }
                    }
                    if (!USE_MT_RENDERER) {
                        if (dragResizing) {
                            mCanvasOffsetX = mWinFrame.left;
                            mCanvasOffsetY = mWinFrame.top;
                        } else {
                            mCanvasOffsetX = mCanvasOffsetY = 0;
                        }
                    }
                } catch (RemoteException e) {
                }
     
                if (DEBUG_ORIENTATION) Log.v(
                        TAG, "Relayout returned: frame=" + frame + ", surface=" + mSurface);
     
                mAttachInfo.mWindowLeft = frame.left;
                mAttachInfo.mWindowTop = frame.top;
     
                // !!FIXME!! This next section handles the case where we did not get the
                // window size we asked for. We should avoid this by getting a maximum size from
                // the window session beforehand.
                if (mWidth != frame.width() || mHeight != frame.height()) {
                    mWidth = frame.width();
                    mHeight = frame.height();
                }
     
                if (mSurfaceHolder != null) {
                    // The app owns the surface; tell it about what is going on.
                    if (mSurface.isValid()) {
                        // XXX .copyFrom() doesn't work!
                        //mSurfaceHolder.mSurface.copyFrom(mSurface);
                        mSurfaceHolder.mSurface = mSurface;
                    }
                    mSurfaceHolder.setSurfaceFrameSize(mWidth, mHeight);
                    mSurfaceHolder.mSurfaceLock.unlock();
                    if (mSurface.isValid()) {
                        if (!hadSurface) {
                            mSurfaceHolder.ungetCallbacks();
     
                            mIsCreating = true;
                            mSurfaceHolderCallback.surfaceCreated(mSurfaceHolder);
                            SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                            if (callbacks != null) {
                                for (SurfaceHolder.Callback c : callbacks) {
                                    c.surfaceCreated(mSurfaceHolder);
                                }
                            }
                            surfaceChanged = true;
                        }
                        if (surfaceChanged || surfaceGenerationId != mSurface.getGenerationId()) {
                            mSurfaceHolderCallback.surfaceChanged(mSurfaceHolder,
                                    lp.format, mWidth, mHeight);
                            SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                            if (callbacks != null) {
                                for (SurfaceHolder.Callback c : callbacks) {
                                    c.surfaceChanged(mSurfaceHolder, lp.format,
                                            mWidth, mHeight);
                                }
                            }
                        }
                        mIsCreating = false;
                    } else if (hadSurface) {
                        mSurfaceHolder.ungetCallbacks();
                        SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                        mSurfaceHolderCallback.surfaceDestroyed(mSurfaceHolder);
                        if (callbacks != null) {
                            for (SurfaceHolder.Callback c : callbacks) {
                                c.surfaceDestroyed(mSurfaceHolder);
                            }
                        }
                        mSurfaceHolder.mSurfaceLock.lock();
                        try {
                            mSurfaceHolder.mSurface = new Surface();
                        } finally {
                            mSurfaceHolder.mSurfaceLock.unlock();
                        }
                    }
                }
     
                final ThreadedRenderer hardwareRenderer = mAttachInfo.mHardwareRenderer;
                if (hardwareRenderer != null && hardwareRenderer.isEnabled()) {
                    if (hwInitialized
                            || mWidth != hardwareRenderer.getWidth()
                            || mHeight != hardwareRenderer.getHeight()
                            || mNeedsHwRendererSetup) {
                        hardwareRenderer.setup(mWidth, mHeight, mAttachInfo,
                                mWindowAttributes.surfaceInsets);
                        mNeedsHwRendererSetup = false;
                    }
                }
     
                if (!mStopped || mReportNextDraw) {
                    boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                            (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                    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);   // performMeasure
     
                        // 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.
                maybeHandleWindowMove(frame);
            }
     
            final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
            boolean triggerGlobalLayoutListener = didLayout
                    || mAttachInfo.mRecomputeGlobalAttributes;
            if (didLayout) {
                performLayout(lp, mWidth, mHeight);   // performLayout
     
                // 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();
                }
            }
     
            if (triggerGlobalLayoutListener) {
                mAttachInfo.mRecomputeGlobalAttributes = false;
                mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
            }
     
            if (computesInternalInsets) {
                // Clear the original insets.
                final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
                insets.reset();
     
                // Compute new insets in place.
                mAttachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
                mAttachInfo.mHasNonEmptyGivenInternalInsets = !insets.isEmpty();
     
                // Tell the window manager.
                if (insetsPending || !mLastGivenInsets.equals(insets)) {
                    mLastGivenInsets.set(insets);
     
                    // Translate insets to screen coordinates if needed.
                    final Rect contentInsets;
                    final Rect visibleInsets;
                    final Region touchableRegion;
                    if (mTranslator != null) {
                        contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
                        visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
                        touchableRegion = mTranslator.getTranslatedTouchableArea(insets.touchableRegion);
                    } else {
                        contentInsets = insets.contentInsets;
                        visibleInsets = insets.visibleInsets;
                        touchableRegion = insets.touchableRegion;
                    }
     
                    try {
                        mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
                                contentInsets, visibleInsets, touchableRegion);
                    } catch (RemoteException e) {
                    }
                }
            }
     
            if (mFirst) {
                // handle first focus request
                if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
                        + mView.hasFocus());
                if (mView != null) {
                    if (!mView.hasFocus()) {
                        mView.requestFocus(View.FOCUS_FORWARD);
                        if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
                                + mView.findFocus());
                    } else {
                        if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view="
                                + mView.findFocus());
                    }
                }
            }
     
            final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
            final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
            final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
            if (regainedFocus) {
                mLostWindowFocus = false;
            } else if (!hasWindowFocus && mHadWindowFocus) {
                mLostWindowFocus = true;
            }
     
            if (changedVisibility || regainedFocus) {
                // Toasts are presented as notifications - don't present them as windows as well
                boolean isToast = (mWindowAttributes == null) ? false
                        : (mWindowAttributes.type == WindowManager.LayoutParams.TYPE_TOAST);
                if (!isToast) {
                    host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
                }
            }
     
            mFirst = false;
            mWillDrawSoon = false;
            mNewSurfaceNeeded = false;
            mActivityRelaunched = false;
            mViewVisibility = viewVisibility;
            mHadWindowFocus = hasWindowFocus;
     
            if (hasWindowFocus && !isInLocalFocusMode()) {
                final boolean imTarget = WindowManager.LayoutParams
                        .mayUseInputMethod(mWindowAttributes.flags);
                if (imTarget != mLastWasImTarget) {
                    mLastWasImTarget = imTarget;
                    InputMethodManager imm = InputMethodManager.peekInstance();
                    if (imm != null && imTarget) {
                        imm.onPreWindowFocus(mView, hasWindowFocus);
                        imm.onPostWindowFocus(mView, mView.findFocus(),
                                mWindowAttributes.softInputMode,
                                !mHasHadWindowFocus, mWindowAttributes.flags);
                    }
                }
            }
     
            // Remember if we must report the next draw.
            if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                mReportNextDraw = true;
            }
     
            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();
                }
     
                performDraw();   // 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;
        }
    
    

    ​其执行的过程可简单的概括为:是否需要重新计算视图的大小(measure)、是否需要重新布局视图的位置(layout),以及是否需要重绘(Draw),也就是我们常说的View的绘制流程。

    那么接下来我们一同来详细分析一下。

    2、performMeasure

    ​调用performMeasure之前会先调用getRootMeasureSpec方法,通过getRootMeasureSpec方法获得顶层视图DecorView的测量规格。

        /**
         * Figures out the measure spec for the root view in a window based on it's
         * layout params.
         *
         * @param windowSize
         *            The available width or height of the window
         *
         * @param rootDimension
         *            The layout params for one dimension (width or height) of the
         *            window.
         *
         * @return The measure spec to use to measure the root view.
         */
        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;
        }
    
    

    该方法主要作用是在整个窗口的基础上计算出root view(顶层视图DecorView)的测量规格。传入的两个参数分别指:windowSize是当前手机窗口的有效宽和高,一般都是除了通知栏的屏幕宽和高;rootDimension是根布局DecorView请求的宽和高,DecorView根布局宽和高都是MATCH_PARENT。

    1. 当匹配父容器时,测量模式为MeasureSpec.EXACTLY,测量大小直接为屏幕的大小,也就是充满真个屏幕;

    2. 当包裹内容时,测量模式为MeasureSpec.AT_MOST,测量大小直接为屏幕大小,也就是充满真个屏幕;

    3. 其他情况时,测量模式为MeasureSpec.EXACTLY,测量大小为DecorView顶层视图布局设置的大小。

    因此DecorView根布局的测量模式就是MeasureSpec.EXACTLY,测量大小一般都是整个屏幕大小,所以一般我们的Activity窗口都是全屏的。所以上面代码走第一个分支,然后通过调用MeasureSpec.makeMeasureSpec方法将DecorView的测量模式和测量大小封装成DecorView的测量规格。

    回到performTraversals方法,直接来看调用的performMeasure方法:

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

    该方法调用了mView的measure()方法。其中mView是一个View对象,在ViewRootImpl类中的mView是整个UI的根节点,实际上也就是PhoneWindow中的mDecor对象,即一个Activity所对应的一个屏幕(不包括顶部的系统状态条)中的视图,包括可能存在也可能不存在的ActionBar。

    继续深入查看View的measure方法:

        /**
         * <p>
         * This is called to find out how big a view should be. The parent
         * supplies constraint information in the width and height parameters.
         * </p>
         *
         * <p>
         * The actual measurement work of a view is performed in
         * {@link #onMeasure(int, int)}, called by this method. Therefore, only
         * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
         * </p>
         *
         *
         * @param widthMeasureSpec Horizontal space requirements as imposed by the
         *        parent
         * @param heightMeasureSpec Vertical space requirements as imposed by the
         *        parent
         *
         * @see #onMeasure(int, int)
         */
        public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
            boolean optical = isLayoutModeOptical(this);
            if (optical != isLayoutModeOptical(mParent)) {
                Insets insets = getOpticalInsets();
                int oWidth  = insets.left + insets.right;
                int oHeight = insets.top  + insets.bottom;
                widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
                heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
            }
     
            // Suppress sign extension for the low bytes
            long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
            if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
     
            final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
     
            // Optimize layout by avoiding an extra EXACTLY pass when the view is
            // already measured as the correct size. In API 23 and below, this
            // extra pass is required to make LinearLayout re-distribute weight.
            final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                    || heightMeasureSpec != mOldHeightMeasureSpec;
            final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                    && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
            final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                    && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
            final boolean needsLayout = specChanged
                    && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
     
            if (forceLayout || needsLayout) {
                // first clears the measured dimension flag
                mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
     
                resolveRtlPropertiesIfNeeded();
     
                int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
                if (cacheIndex < 0 || sIgnoreMeasureCache) {
                    // measure ourselves, this should set the measured dimension flag back
                    onMeasure(widthMeasureSpec, heightMeasureSpec);   // onMeasure
                    mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
                } else {
                    long value = mMeasureCache.valueAt(cacheIndex);
                    // Casting a long to int drops the high 32 bits, no mask needed
                    setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                    mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
                }
     
                // flag not set, setMeasuredDimension() was not invoked, we raise
                // an exception to warn the developer
                if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                    throw new IllegalStateException("View with id " + getId() + ": "
                            + getClass().getName() + "#onMeasure() did not set the"
                            + " measured dimension by calling"
                            + " setMeasuredDimension()");
                }
     
                mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
            }
     
            mOldWidthMeasureSpec = widthMeasureSpec;
            mOldHeightMeasureSpec = heightMeasureSpec;
     
            mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                    (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
        }
    
    

    参数widthMeasureSpec和heightMeasureSpec用来描述当前正在处理的视图可以获得的最大宽度和高度。

    当ViewRoot类的成员变量mPrivateFlags的FORCE_LAYOUT位不等于0时,就表示当前视图正在请求执行一次布局操作,这时候方法就需要重新测量当前视图的宽度和高度。此外,当参数widthMeasureSpec和heightMeasureSpec的值不等于ViewRoot类的成员变量mldWidthMeasureSpec和mOldHeightMeasureSpec的值时,就表示当前视图上一次可以获得的最大宽度和高度已经失效了,这时候函数也需要重新测量当前视图的宽度和高度。

    当View类的measure方法决定要重新测量当前视图的宽度和高度之后,它就会首先将成员变量mPrivateFlags的MEASURED_DIMENSION_SET位设置为0,接着再调用onMeasure方法来真正执行测量宽度和高度的操作。View类的onMeasure方法执行完成之后,需要再调用setMeasuredDimension方法来将测量好的宽度和高度设置到View类的成员变量mMeasuredWidth和mMeasuredHeight中,并且将成员变量mPrivateFlags的EASURED_DIMENSION_SET位设置为1。这个操作是强制的,因为当前视图最终就是通过View类的成员变量mMeasuredWidth和mMeasuredHeight来获得它的宽度和高度的。

    继续查看View类的onMeasure()方法:

         /**
         * <p>
         * Measure the view and its content to determine the measured width and the
         * measured height. This method is invoked by {@link #measure(int, int)} and
         * should be overridden by subclasses to provide accurate and efficient
         * measurement of their contents.
         * </p>
         *
         * <p>
         * <strong>CONTRACT:</strong> When overriding this method, you
         * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
         * measured width and height of this view. Failure to do so will trigger an
         * <code>IllegalStateException</code>, thrown by
         * {@link #measure(int, int)}. Calling the superclass'
         * {@link #onMeasure(int, int)} is a valid use.
         * </p>
         *
         * <p>
         * The base class implementation of measure defaults to the background size,
         * unless a larger size is allowed by the MeasureSpec. Subclasses should
         * override {@link #onMeasure(int, int)} to provide better measurements of
         * their content.
         * </p>
         *
         * <p>
         * If this method is overridden, it is the subclass's responsibility to make
         * sure the measured height and width are at least the view's minimum height
         * and width ({@link #getSuggestedMinimumHeight()} and
         * {@link #getSuggestedMinimumWidth()}).
         * </p>
         *
         * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
         *                         The requirements are encoded with
         *                         {@link android.view.View.MeasureSpec}.
         * @param heightMeasureSpec vertical space requirements as imposed by the parent.
         *                         The requirements are encoded with
         *                         {@link android.view.View.MeasureSpec}.
         *
         * @see #getMeasuredWidth()
         * @see #getMeasuredHeight()
         * @see #setMeasuredDimension(int, int)
         * @see #getSuggestedMinimumHeight()
         * @see #getSuggestedMinimumWidth()
         * @see android.view.View.MeasureSpec#getMode(int)
         * @see android.view.View.MeasureSpec#getSize(int)
         */
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    
    

    其实View类的onMeasure方法一般是由其子类来重写的。如对于用来应用程序窗口的顶层视图的DecorView类来说,它是通过父类FrameLayout来重写祖父类View的onMeasure方法的,接下来我们就分析FrameLayout类的onMeasure方法的实现。

    分析onMeasure方法,我们先从子类DecorView的onMeasure方法入手,这个方法主要是调整了两个入参高度和宽度,然后调用其父类的onMeasure方法。

          @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
            final boolean isPortrait =
                    getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
     
            final int widthMode = getMode(widthMeasureSpec);
            final int heightMode = getMode(heightMeasureSpec);
     
            boolean fixedWidth = false;
            mApplyFloatingHorizontalInsets = false;
            if (widthMode == AT_MOST) {
                final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor;
                if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
                    final int w;
                    if (tvw.type == TypedValue.TYPE_DIMENSION) {
                        w = (int) tvw.getDimension(metrics);
                    } else if (tvw.type == TypedValue.TYPE_FRACTION) {
                        w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
                    } else {
                        w = 0;
                    }
                    if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed width: " + w);
                    final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
                    if (w > 0) {
                        widthMeasureSpec = MeasureSpec.makeMeasureSpec(
                                Math.min(w, widthSize), EXACTLY);
                        fixedWidth = true;
                    } else {
                        widthMeasureSpec = MeasureSpec.makeMeasureSpec(
                                widthSize - mFloatingInsets.left - mFloatingInsets.right,
                                AT_MOST);
                        mApplyFloatingHorizontalInsets = true;
                    }
                }
            }
     
            mApplyFloatingVerticalInsets = false;
            if (heightMode == AT_MOST) {
                final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
                        : mWindow.mFixedHeightMinor;
                if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
                    final int h;
                    if (tvh.type == TypedValue.TYPE_DIMENSION) {
                        h = (int) tvh.getDimension(metrics);
                    } else if (tvh.type == TypedValue.TYPE_FRACTION) {
                        h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
                    } else {
                        h = 0;
                    }
                    if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed height: " + h);
                    final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
                    if (h > 0) {
                        heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                                Math.min(h, heightSize), EXACTLY);
                    } else if ((mWindow.getAttributes().flags & FLAG_LAYOUT_IN_SCREEN) == 0) {
                        heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                                heightSize - mFloatingInsets.top - mFloatingInsets.bottom, AT_MOST);
                        mApplyFloatingVerticalInsets = true;
                    }
                }
            }
     
            getOutsets(mOutsets);
            if (mOutsets.top > 0 || mOutsets.bottom > 0) {
                int mode = MeasureSpec.getMode(heightMeasureSpec);
                if (mode != MeasureSpec.UNSPECIFIED) {
                    int height = MeasureSpec.getSize(heightMeasureSpec);
                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height + mOutsets.top + mOutsets.bottom, mode);
                }
            }
            if (mOutsets.left > 0 || mOutsets.right > 0) {
                int mode = MeasureSpec.getMode(widthMeasureSpec);
                if (mode != MeasureSpec.UNSPECIFIED) {
                    int width = MeasureSpec.getSize(widthMeasureSpec);
                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width + mOutsets.left + mOutsets.right, mode);
                }
            }
     
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);    // super
     
            int width = getMeasuredWidth();
            boolean measure = false;
     
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
     
            if (!fixedWidth && widthMode == AT_MOST) {
                final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
                if (tv.type != TypedValue.TYPE_NULL) {
                    final int min;
                    if (tv.type == TypedValue.TYPE_DIMENSION) {
                        min = (int)tv.getDimension(metrics);
                    } else if (tv.type == TypedValue.TYPE_FRACTION) {
                        min = (int)tv.getFraction(mAvailableWidth, mAvailableWidth);
                    } else {
                        min = 0;
                    }
                    if (DEBUG_MEASURE) Log.d(mLogTag, "Adjust for min width: " + min + ", value::"
                            + tv.coerceToString() + ", mAvailableWidth=" + mAvailableWidth);
     
                    if (width < min) {
                        widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
                        measure = true;
                    }
                }
            }
     
            // TODO: Support height?
     
            if (measure) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }
    
    

    再看FrameLayout的onMeasure方法,主要是遍历所有的子View进行测量,然后设置高度、宽度。

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int count = getChildCount();
     
            final boolean measureMatchParentChildren =
                    MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                    MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
            mMatchParentChildren.clear();
     
            int maxHeight = 0;
            int maxWidth = 0;
            int childState = 0;
     
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (mMeasureAllChildren || child.getVisibility() != GONE) {
                    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    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());
                    if (measureMatchParentChildren) {
                        if (lp.width == LayoutParams.MATCH_PARENT ||
                                lp.height == LayoutParams.MATCH_PARENT) {
                            mMatchParentChildren.add(child);
                        }
                    }
                }
            }
     
            // Account for padding too
            maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
            maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
     
            // Check against our minimum height and width
            maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
            maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
     
            // 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(maxWidth, widthMeasureSpec, childState),
                    resolveSizeAndState(maxHeight, heightMeasureSpec,
                            childState << MEASURED_HEIGHT_STATE_SHIFT));       // resolveSizeAndState
     
            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) {
                        final int width = Math.max(0, getMeasuredWidth()
                                - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                                - lp.leftMargin - lp.rightMargin);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                                width, MeasureSpec.EXACTLY);
                    } else {
                        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);
                    }
     
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                }
            }
        }
    
    

    首先是调用measureChildWithMargins方法来测量每一个子视图的宽度和高度,并且找到这些子视图的最大宽度和高度值,保存在变量maxWidth和maxHeight 中。

    接着再将前面得到的宽度maxWidth和高度maxHeight分别加上当前视图所设置的Padding值,得到的宽度maxWidth和高度maxHeight还不是最终的宽度和高度,还需要考虑以下两个因素:

    1. 当前视图是否设置有最小宽度和高度。如果设置有的话,并且它们比前面计算得到的宽度maxWidth和高度maxHeight还要大,那么就将它们作为当前视图的宽度和高度值。

    2. 当前视图是否设置有前景图。如果设置有的话,并且它们比前面计算得到的宽度maxWidth和高度maxHeight还要大,那么就将它们作为当前视图的宽度和高度值。

    经过上述两步检查之后,FrameLayout类的成员函数onMeasure就得到了当前视图的宽度maxWidth和高度maxHeight。由于得到的宽度和高度又必须要限制在参数widthMeasureSpec和heightMeasureSpec所描述的宽度和高度规范之内,因此会调用从View类继承下来的resolveSizeAndState方法来获得正确的大小。得到了当前视图的正确大小之后,FrameLayout类的onMeasure方法就可以调用从父类View继承下来的setMeasuredDimension方法来将它们为当前视图的大小了。

    我们首先来看一下resolveSizeAndState方法:

        /**
         * Utility to reconcile a desired size and state, with constraints imposed
         * by a MeasureSpec. Will take the desired size, unless a different size
         * is imposed by the constraints. The returned value is a compound integer,
         * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
         * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
         * resulting size is smaller than the size the view wants to be.
         *
         * @param size How big the view wants to be.
         * @param measureSpec Constraints imposed by the parent.
         * @param childMeasuredState Size information bit mask for the view's
         *                           children.
         * @return Size information bit mask as defined by
         *         {@link #MEASURED_SIZE_MASK} and
         *         {@link #MEASURED_STATE_TOO_SMALL}.
         */
        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) {
                case MeasureSpec.AT_MOST:
                    if (specSize < size) {
                        result = specSize | MEASURED_STATE_TOO_SMALL;
                    } else {
                        result = size;
                    }
                    break;
                case MeasureSpec.EXACTLY:
                    result = specSize;
                    break;
                case MeasureSpec.UNSPECIFIED:
                default:
                    result = size;
            }
            return result | (childMeasuredState & MEASURED_STATE_MASK);
        }
    
    

    该方法把measureSpec入参的mode和size解析出来,mode封装在高位中,然后根据mode来决定最后返回的size。

    回到FrameLayout的onMeasure方法,继续分析从父类View继承下来的setMeasuredDimension方法:

        /**
         * <p>This method must be called by {@link #onMeasure(int, int)} to store the
         * measured width and measured height. Failing to do so will trigger an
         * exception at measurement time.</p>
         *
         * @param measuredWidth The measured width of this view.  May be a complex
         * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
         * {@link #MEASURED_STATE_TOO_SMALL}.
         * @param measuredHeight The measured height of this view.  May be a complex
         * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
         * {@link #MEASURED_STATE_TOO_SMALL}.
         */
        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的两个成员变量进行一次赋值,设置自己所需要的大小。计算的根据是在xml文件或者代码中设置的宽度和高度的参数,参数指明了要求你是填充父控件(match_parent)还是包裹内容(wrap_content)还是精确的一个大小,但最终你的大小不应该超过父控件给你提供的空间。

    onMeasure()方法结束之前必须调用setMeasuredDimension()来设置View.mMeasuredWidth和View.mMeasuredHeight两个参数。

         * Sets the measured dimension without extra processing for things like optical bounds.
         * Useful for reapplying consistent values that have already been cooked with adjustments
         * for optical bounds, etc. such as those from the measurement cache.
         *
         * @param measuredWidth The measured width of this view.  May be a complex
         * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
         * {@link #MEASURED_STATE_TOO_SMALL}.
         * @param measuredHeight The measured height of this view.  May be a complex
         * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
         * {@link #MEASURED_STATE_TOO_SMALL}.
         */
        private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
            mMeasuredWidth = measuredWidth;
            mMeasuredHeight = measuredHeight;
     
            mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
        }
    
    

    而当这两个成员变量设置完成,也就是当前的View测量结束了。

    简单总结概括一下,measure的时序图如下:

    image.png

    3、performLayout

    继续分析ViewRootImpl的performLayout方法:

        private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                int desiredWindowHeight) {
            mLayoutRequested = false;
            mScrollMayChange = true;
            mInLayout = true;
     
            final View host = mView;
            if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
                Log.v(mTag, "Laying out " + host + " to (" +
                        host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
            }
     
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
            try {
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());    // layout
     
                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()方法,从传递的4个参数知道DecorView布局的位置是从屏幕最左最顶端开始布局,到屏幕最低最右结束。因此DecorView根布局是充满整个屏幕的。

    继续分析View类的layout方法:

         /**
         * Assign a size and position to a view and all of its
         * descendants
         *
         * <p>This is the second phase of the layout mechanism.
         * (The first is measuring). In this phase, each parent calls
         * layout on all of its children to position them.
         * This is typically done using the child measurements
         * that were stored in the measure pass().</p>
         *
         * <p>Derived classes should not override this method.
         * Derived classes with children should override
         * onLayout. In that method, they should
         * call layout on each of their children.</p>
         *
         * @param l Left position, relative to parent
         * @param t Top position, relative to parent
         * @param r Right position, relative to parent
         * @param b Bottom position, relative to parent
         */
        @SuppressWarnings({"unchecked"})
        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;
     
            boolean changed = isLayoutModeOptical(mParent) ?
                    setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);    // setFrame
     
            if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
                onLayout(changed, l, t, r, b);     // onLayout
     
                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);
                    }
                }
            }
     
            mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
            mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
        }
    
    

    layout()方法有四个参数,分别是left, top, right, bottom,它们是相对于父控件的位移距离。方法里面先调用了setFrame()方法,该方法非常重要:

        /**
         * Assign a size and position to this view.
         *
         * This is called from layout.
         *
         * @param left Left position, relative to parent
         * @param top Top position, relative to parent
         * @param right Right position, relative to parent
         * @param bottom Bottom position, relative to parent
         * @return true if the new size and position are different than the
         *         previous ones
         * {@hide}
         */
        protected boolean setFrame(int left, int top, int right, int bottom) {
            boolean changed = false;
     
            if (DBG) {
                Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                        + right + "," + bottom + ")");
            }
     
            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;
     
     
                if (sizeChanged) {
                    sizeChange(newWidth, newHeight, oldWidth, oldHeight);
                }
     
                if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
                    // If we are visible, force the DRAWN bit to on so that
                    // this invalidate will go through (at least to our parent).
                    // This is because someone may have invalidated this view
                    // before this call to setFrame came in, thereby clearing
                    // the DRAWN bit.
                    mPrivateFlags |= PFLAG_DRAWN;
                    invalidate(sizeChanged);
                    // parent display list may need to be recreated based on a change in the bounds
                    // of any child
                    invalidateParentCaches();
                }
     
                // Reset drawn bit to original value (invalidate turns it off)
                mPrivateFlags |= drawn;
     
                mBackgroundSizeChanged = true;
                if (mForegroundInfo != null) {
                    mForegroundInfo.mBoundsChanged = true;
                }
     
                notifySubtreeAccessibilityStateChangedIfNeeded();
            }
            return changed;
        }
    
    

    该方法先判断当前视图的大小或者位置是否发生变化,将参数保存起来。当前视图距离父视图的边距一旦设置好之后,它就是一个具有边界的视图了。接下来又会计算当前视图新的宽度newWidth和高度newHeight,如果它们与上一次的宽度oldWidth和oldHeight的值不相等,那么就说明当前视图的大小发生了变化,这时候就会调用onSizeChanged方法来让子类有机会处理这个变化事件。

    继续回到layout()方法,后面调用了onLayout()方法,实际上是给自己的子控件布局。从以上可以知道measure出来的宽度与高度,是该控件期望得到的尺寸,但是真正显示到屏幕上的位置与大小是由layout()方法来决定的。left, top决定位置,right,bottom决定frame渲染尺寸。

        /**
         * Called from layout when this view should
         * assign a size and position to each of its children.
         *
         * Derived classes with children should override
         * this method and call layout on each of
         * their children.
         * @param changed This is a new size or position for this view
         * @param left Left position, relative to parent
         * @param top Top position, relative to parent
         * @param right Right position, relative to parent
         * @param bottom Bottom position, relative to parent
         */
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        }
    
    

    发现onLayout方法是空的,直接看DecorView的onLayout方法:

        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);    // super
            getOutsets(mOutsets);
            if (mOutsets.left > 0) {
                offsetLeftAndRight(-mOutsets.left);
            }
            if (mOutsets.top > 0) {
                offsetTopAndBottom(-mOutsets.top);
            }
            if (mApplyFloatingVerticalInsets) {
                offsetTopAndBottom(mFloatingInsets.top);
            }
            if (mApplyFloatingHorizontalInsets) {
                offsetLeftAndRight(mFloatingInsets.left);
            }
     
            // If the application changed its SystemUI metrics, we might also have to adapt
            // our shadow elevation.
            updateElevation();
            mAllowUpdateElevation = true;
     
            if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
                getViewRootImpl().requestInvalidateRootRenderNode();
            }
        }
    
    

    这里先是调用了FrameLayout的onLayout方法,然后是调整个别参数。继续看父类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 */);
        }
    
    

    直接调用了调用了layoutChildren方法,继续分析:

        void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
            final int count = getChildCount();
     
            final int parentLeft = getPaddingLeftWithForeground();
            final int parentRight = right - left - getPaddingRightWithForeground();
     
            final int parentTop = getPaddingTopWithForeground();
            final int parentBottom = bottom - top - getPaddingBottomWithForeground();
     
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
     
                    final int width = child.getMeasuredWidth();
                    final int height = child.getMeasuredHeight();
     
                    int childLeft;
                    int childTop;
     
                    int gravity = lp.gravity;
                    if (gravity == -1) {
                        gravity = DEFAULT_CHILD_GRAVITY;
                    }
     
                    final int layoutDirection = getLayoutDirection();
                    final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                    final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
     
                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                        case Gravity.CENTER_HORIZONTAL:
                            childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                            lp.leftMargin - lp.rightMargin;
                            break;
                        case Gravity.RIGHT:
                            if (!forceLeftGravity) {
                                childLeft = parentRight - width - lp.rightMargin;
                                break;
                            }
                        case Gravity.LEFT:
                        default:
                            childLeft = parentLeft + lp.leftMargin;
                    }
     
                    switch (verticalGravity) {
                        case Gravity.TOP:
                            childTop = parentTop + lp.topMargin;
                            break;
                        case Gravity.CENTER_VERTICAL:
                            childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                            lp.topMargin - lp.bottomMargin;
                            break;
                        case Gravity.BOTTOM:
                            childTop = parentBottom - height - lp.bottomMargin;
                            break;
                        default:
                            childTop = parentTop + lp.topMargin;
                    }
     
                    child.layout(childLeft, childTop, childLeft + width, childTop + height);
                }
            }
        }
    
    

    该方法遍历各个子View,然后调用子View的layout方法。

    需要注意的是FrameLayout布局其实在View类中的layout方法中已经实现,布局的逻辑实现是在父视图中实现的,不像View视图的measure测量,通过子类实现onMeasure方法来实现测量逻辑。

    自定义View一般都无需重写onMeasure方法,但是如果自定义一个ViewGroup容器的话,就必须实现onLayout方法,因为该方法在ViewGroup是抽象的,所有ViewGroup的所有子类必须实现onLayout方法。

    简单总结概括一下,layout的时序图如下:

    image

    4、performDraw

    继续分析ViewRootImpl的performDraw方法:

        private void performDraw() {
            if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
                return;
            }
     
            final boolean fullRedrawNeeded = mFullRedrawNeeded;
            mFullRedrawNeeded = false;
     
            mIsDrawing = true;
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
            try {
                draw(fullRedrawNeeded);   // draw
            } finally {
                mIsDrawing = false;
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
     
            // For whatever reason we didn't create a HardwareRenderer, end any
            // hardware animations that are now dangling
            if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
                final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();
                for (int i = 0; i < count; i++) {
                    mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();
                }
                mAttachInfo.mPendingAnimatingRenderNodes.clear();
            }
     
            if (mReportNextDraw) {
                mReportNextDraw = false;
     
                // if we're using multi-thread renderer, wait for the window frame draws
                if (mWindowDrawCountDown != null) {
                    try {
                        mWindowDrawCountDown.await();
                    } catch (InterruptedException e) {
                        Log.e(mTag, "Window redraw count down interruped!");
                    }
                    mWindowDrawCountDown = null;
                }
     
                if (mAttachInfo.mHardwareRenderer != null) {
                    mAttachInfo.mHardwareRenderer.fence();
                    mAttachInfo.mHardwareRenderer.setStopped(mStopped);
                }
     
                if (LOCAL_LOGV) {
                    Log.v(mTag, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
                }
                if (mSurfaceHolder != null && mSurface.isValid()) {
                    mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);
                    SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                    if (callbacks != null) {
                        for (SurfaceHolder.Callback c : callbacks) {
                            if (c instanceof SurfaceHolder.Callback2) {
                                ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(mSurfaceHolder);
                            }
                        }
                    }
                }
                try {
                    mWindowSession.finishDrawing(mWindow);
                } catch (RemoteException e) {
                }
            }
        }
    
    

    这里面主要是调用draw方法:

        private void draw(boolean fullRedrawNeeded) {
            Surface surface = mSurface;
            if (!surface.isValid()) {
                return;
            }
     
            if (DEBUG_FPS) {
                trackFPS();
            }
     
            if (!sFirstDrawComplete) {
                synchronized (sFirstDrawHandlers) {
                    sFirstDrawComplete = true;
                    final int count = sFirstDrawHandlers.size();
                    for (int i = 0; i< count; i++) {
                        mHandler.post(sFirstDrawHandlers.get(i));
                    }
                }
            }
     
            scrollToRectOrFocus(null, false);
     
            if (mAttachInfo.mViewScrollChanged) {
                mAttachInfo.mViewScrollChanged = false;
                mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
            }
     
            boolean animating = mScroller != null && mScroller.computeScrollOffset();
            final int curScrollY;
            if (animating) {
                curScrollY = mScroller.getCurrY();
            } else {
                curScrollY = mScrollY;
            }
            if (mCurScrollY != curScrollY) {
                mCurScrollY = curScrollY;
                fullRedrawNeeded = true;
                if (mView instanceof RootViewSurfaceTaker) {
                    ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
                }
            }
     
            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 (DEBUG_ORIENTATION || DEBUG_DRAW) {
                Log.v(mTag, "Draw " + mView + "/"
                        + mWindowAttributes.getTitle()
                        + ": dirty={" + dirty.left + "," + dirty.top
                        + "," + dirty.right + "," + dirty.bottom + "} surface="
                        + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
                        appScale + ", width=" + mWidth + ", height=" + mHeight);
            }
     
            mAttachInfo.mTreeObserver.dispatchOnDraw();
     
            int xOffset = -mCanvasOffsetX;
            int yOffset = -mCanvasOffsetY + curScrollY;
            final WindowManager.LayoutParams params = mWindowAttributes;
            final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
            if (surfaceInsets != null) {
                xOffset -= surfaceInsets.left;
                yOffset -= surfaceInsets.top;
     
                // Offset dirty rect for surface insets.
                dirty.offset(surfaceInsets.left, surfaceInsets.right);
            }
     
            boolean accessibilityFocusDirty = false;
            final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
            if (drawable != null) {
                final Rect bounds = mAttachInfo.mTmpInvalRect;
                final boolean hasFocus = getAccessibilityFocusedRect(bounds);
                if (!hasFocus) {
                    bounds.setEmpty();
                }
                if (!bounds.equals(drawable.getBounds())) {
                    accessibilityFocusDirty = true;
                }
            }
     
            mAttachInfo.mDrawingTime =
                    mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
     
            if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
                if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
                    // If accessibility focus moved, always invalidate the root.
                    boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
                    mInvalidateRootRequested = false;
     
                    // Draw with hardware renderer.
                    mIsAnimating = false;
     
                    if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
                        mHardwareYOffset = yOffset;
                        mHardwareXOffset = xOffset;
                        invalidateRoot = true;
                    }
     
                    if (invalidateRoot) {
                        mAttachInfo.mHardwareRenderer.invalidateRoot();
                    }
     
                    dirty.setEmpty();
     
                    // Stage the content drawn size now. It will be transferred to the renderer
                    // shortly before the draw commands get send to the renderer.
                    final boolean updated = updateContentDrawBounds();
     
                    if (mReportNextDraw) {
                        // report next draw overrides setStopped()
                        // This value is re-sync'd to the value of mStopped
                        // in the handling of mReportNextDraw post-draw.
                        mAttachInfo.mHardwareRenderer.setStopped(false);
                    }
     
                    if (updated) {
                        requestDrawWindow();
                    }
     
                    mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
                } else {
                    // If we get here with a disabled & requested hardware renderer, something went
                    // wrong (an invalidate posted right before we destroyed the hardware surface
                    // for instance) so we should just bail out. Locking the surface with software
                    // rendering at this point would lock it forever and prevent hardware renderer
                    // from doing its job when it comes back.
                    // Before we request a new frame we must however attempt to reinitiliaze the
                    // hardware renderer if it's in requested state. This would happen after an
                    // eglTerminate() for instance.
                    if (mAttachInfo.mHardwareRenderer != null &&
                            !mAttachInfo.mHardwareRenderer.isEnabled() &&
                            mAttachInfo.mHardwareRenderer.isRequested()) {
     
                        try {
                            mAttachInfo.mHardwareRenderer.initializeIfNeeded(
                                    mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
                        } catch (OutOfResourcesException e) {
                            handleOutOfResourcesException(e);
                            return;
                        }
     
                        mFullRedrawNeeded = true;
                        scheduleTraversals();
                        return;
                    }
     
                    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {    // drawSoftware
                        return;
                    }
                }
            }
     
            if (animating) {
                mFullRedrawNeeded = true;
                scheduleTraversals();
            }
        }
    
    

    方法结束前执行了drawSoftware方法:

        /**
         * @return true if drawing was successful, false if an error occurred
         */
        private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                boolean scalingRequired, Rect dirty) {
     
            // Draw with software renderer.
            final Canvas canvas;
            try {
                final int left = dirty.left;
                final int top = dirty.top;
                final int right = dirty.right;
                final int bottom = dirty.bottom;
     
                canvas = mSurface.lockCanvas(dirty);
     
                // The dirty rectangle can be modified by Surface.lockCanvas()
                //noinspection ConstantConditions
                if (left != dirty.left || top != dirty.top || right != dirty.right
                        || bottom != dirty.bottom) {
                    attachInfo.mIgnoreDirtyState = true;
                }
     
                // TODO: Do this in native
                canvas.setDensity(mDensity);
            } catch (Surface.OutOfResourcesException e) {
                handleOutOfResourcesException(e);
                return false;
            } catch (IllegalArgumentException e) {
                Log.e(mTag, "Could not lock surface", e);
                // Don't assume this is due to out of memory, it could be
                // something else, and if it is something else then we could
                // kill stuff (or ourself) for no reason.
                mLayoutRequested = true;    // ask wm for a new surface next time.
                return false;
            }
     
            try {
                if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                    Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
                            + canvas.getWidth() + ", h=" + canvas.getHeight());
                    //canvas.drawARGB(255, 255, 0, 0);
                }
     
                // If this bitmap's format includes an alpha channel, we
                // need to clear it before drawing so that the child will
                // properly re-composite its drawing on a transparent
                // background. This automatically respects the clip/dirty region
                // or
                // If we are applying an offset, we need to clear the area
                // where the offset doesn't appear to avoid having garbage
                // left in the blank areas.
                if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                    canvas.drawColor(0, PorterDuff.Mode.CLEAR);
                }
     
                dirty.setEmpty();
                mIsAnimating = false;
                mView.mPrivateFlags |= View.PFLAG_DRAWN;
     
                if (DEBUG_DRAW) {
                    Context cxt = mView.getContext();
                    Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
                            ", metrics=" + cxt.getResources().getDisplayMetrics() +
                            ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
                }
                try {
                    canvas.translate(-xoff, -yoff);
                    if (mTranslator != null) {
                        mTranslator.translateCanvas(canvas);
                    }
                    canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                    attachInfo.mSetIgnoreDirtyState = false;
     
                    mView.draw(canvas);   // draw
     
                    drawAccessibilityFocusedDrawableIfNeeded(canvas);
                } finally {
                    if (!attachInfo.mSetIgnoreDirtyState) {
                        // Only clear the flag if it was not set during the mView.draw() call
                        attachInfo.mIgnoreDirtyState = false;
                    }
                }
            } finally {
                try {
                    surface.unlockCanvasAndPost(canvas);
                } catch (IllegalArgumentException e) {
                    Log.e(mTag, "Could not unlock surface", e);
                    mLayoutRequested = true;    // ask wm for a new surface next time.
                    //noinspection ReturnInsideFinallyBlock
                    return false;
                }
     
                if (LOCAL_LOGV) {
                    Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
                }
            }
            return true;
        }
    
    

    该方法首先获取需要重绘的位置,锁定并获取对应的canvas,最后调用了DecorView的draw方法。

         @Override
        public void draw(Canvas canvas) {
            super.draw(canvas);
     
            if (mMenuBackground != null) {
                mMenuBackground.draw(canvas);
            }
        }
    
    

    这里的代码非常简单,调用了父类的draw方法,以此查找最终定位到了View类的draw方法:

         /**
         * Manually render this view (and all of its children) to the given Canvas.
         * The view must have already done a full layout before this function is
         * called.  When implementing a view, implement
         * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
         * If you do need to override this method, call the superclass version.
         *
         * @param canvas The Canvas to which the View is rendered.
         */
        @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
                if (!dirtyOpaque) onDraw(canvas);
     
                // Step 4, draw the children
                dispatchDraw(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);
     
                // we're done...
                return;
            }
    
    

    该类非常重要,也是最后比较关键的绘制操作。代码比较多,但是注释解释的非常清楚,流程具体如下:

    1.绘制当前视图的背景。

    2.保存当前画布的堆栈状态,并且在当前画布上创建额外的图层,以便接下来可以用来绘制当前视图在滑动时的边框渐变效果。

    3.绘制当前视图的内容。

    4.绘制当前视图的子视图的内容。

    5.绘制当前视图在滑动时的边框渐变效果。

    6.绘制当前视图的滚动条。

    接下来分别分析这个流程,首先来看背景的绘制,接着是保存画布canvas的边框参数。获取当前视图View水平或者垂直方向是否需要绘制边框渐变效果,如果不需要绘制边框的渐变效果,就无需执行上面的2、5了,那么就直接执行上面的3、4、6步骤。假如我们需要绘制视图View的边框渐变效果,那么我们继续分析步骤2,3,4,5,6。

    接着后面的代码用来检查是否需要保存参数canvas所描述的一块画布的堆栈状态,并且创建额外的图层来绘制当前视图在滑动时的边框渐变效果。首先需要计算出当前视图的左、右、上以及下内边距的大小,以便得到边框所要绘制的区域。

    然后接着绘制当前视图的内容,调用了onDraw方法:

        /**
         * Implement this to do your drawing.
         *
         * @param canvas the canvas on which the background will be drawn
         */
        protected void onDraw(Canvas canvas) {
        }
    
    

    发现该方法为空,主要在子类中实现,继续看DecorView的onDraw方法:

         @Override
        public void onDraw(Canvas c) {
            super.onDraw(c);
            mBackgroundFallback.draw(mContentRoot, c, mWindow.mContentParent);
        }
    
    

    当前视图的内容绘制完成后,接着绘制子视图的内容,调用了dispatchDraw方法。

         /**
         * Called by draw to draw the child views. This may be overridden
         * by derived classes to gain control just before its children are drawn
         * (but after its own view has been drawn).
         * @param canvas the canvas on which to draw the view
         */
        protected void dispatchDraw(Canvas canvas) {
     
        }
    
    

    发现该方法为空,真正的实现在ViewGroup中:

        @Override
        protected void dispatchDraw(Canvas canvas) {
            boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
            final int childrenCount = mChildrenCount;
            final View[] children = mChildren;
            int flags = mGroupFlags;
     
            if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
                final boolean buildCache = !isHardwareAccelerated();
                for (int i = 0; i < childrenCount; i++) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                        final LayoutParams params = child.getLayoutParams();
                        attachLayoutAnimationParameters(child, params, i, childrenCount);
                        bindLayoutAnimation(child);
                    }
                }
     
                final LayoutAnimationController controller = mLayoutAnimationController;
                if (controller.willOverlap()) {
                    mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
                }
     
                controller.start();
     
                mGroupFlags &= ~FLAG_RUN_ANIMATION;
                mGroupFlags &= ~FLAG_ANIMATION_DONE;
     
                if (mAnimationListener != null) {
                    mAnimationListener.onAnimationStart(controller.getAnimation());
                }
            }
     
            int clipSaveCount = 0;
            final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
            if (clipToPadding) {
                clipSaveCount = canvas.save();
                canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                        mScrollX + mRight - mLeft - mPaddingRight,
                        mScrollY + mBottom - mTop - mPaddingBottom);
            }
     
            // We will draw our child's animation, let's reset the flag
            mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
            mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
     
            boolean more = false;
            final long drawingTime = getDrawingTime();
     
            if (usingRenderNodeProperties) canvas.insertReorderBarrier();
            final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
            int transientIndex = transientCount != 0 ? 0 : -1;
            // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
            // draw reordering internally
            final ArrayList<View> preorderedList = usingRenderNodeProperties
                    ? null : buildOrderedChildList();
            final boolean customOrder = preorderedList == null
                    && isChildrenDrawingOrderEnabled();
            for (int i = 0; i < childrenCount; i++) {
                while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                    final View transientChild = mTransientViews.get(transientIndex);
                    if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                            transientChild.getAnimation() != null) {
                        more |= drawChild(canvas, transientChild, drawingTime);   // drawChild
                    }
                    transientIndex++;
                    if (transientIndex >= transientCount) {
                        transientIndex = -1;
                    }
                }
     
                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
            while (transientIndex >= 0) {
                // there may be additional transient views after the normal views
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
                transientIndex++;
                if (transientIndex >= transientCount) {
                    break;
                }
            }
            if (preorderedList != null) preorderedList.clear();
     
            // Draw any disappearing views that have animations
            if (mDisappearingChildren != null) {
                final ArrayList<View> disappearingChildren = mDisappearingChildren;
                final int disappearingCount = disappearingChildren.size() - 1;
                // Go backwards -- we may delete as animations finish
                for (int i = disappearingCount; i >= 0; i--) {
                    final View child = disappearingChildren.get(i);
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
            if (usingRenderNodeProperties) canvas.insertInorderBarrier();
     
            if (debugDraw()) {
                onDebugDraw(canvas);
            }
     
            if (clipToPadding) {
                canvas.restoreToCount(clipSaveCount);
            }
     
            // mGroupFlags might have been updated by drawChild()
            flags = mGroupFlags;
     
            if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
                invalidate(true);
            }
     
            if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
                    mLayoutAnimationController.isDone() && !more) {
                // We want to erase the drawing cache and notify the listener after the
                // next frame is drawn because one extra invalidate() is caused by
                // drawChild() after the animation is over
                mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
                final Runnable end = new Runnable() {
                   @Override
                   public void run() {
                       notifyAnimationListener();
                   }
                };
                post(end);
            }
        }
    
    

    首先判断当前ViewGroup容器是否设置的布局动画,然后遍历给每个子视图View设置动画效果,接着获得布局动画的控制器,最后开始布局动画。

    接下来循环遍历每一个子View,并调用drawChild方法绘制当前视图的子视图View:

        /**
         * Draw one child of this View Group. This method is responsible for getting
         * the canvas in the right state. This includes clipping, translating so
         * that the child's scrolled origin is at 0, 0, and applying any animation
         * transformations.
         *
         * @param canvas The canvas on which to draw the child
         * @param child Who to draw
         * @param drawingTime The time at which draw is occurring
         * @return True if an invalidate() was issued
         */
        protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
            return child.draw(canvas, this, drawingTime);
        }
    
    

    ​这个draw()方法也是view里面的方法,被drawChild()方法调用:

         /**
         * This method is called by ViewGroup.drawChild() to have each child view draw itself.
         *
         * This is where the View specializes rendering behavior based on layer type,
         * and hardware acceleration.
         */
        boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
            final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
            /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
             *
             * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
             * HW accelerated, it can't handle drawing RenderNodes.
             */
            boolean drawingWithRenderNode = mAttachInfo != null
                    && mAttachInfo.mHardwareAccelerated
                    && hardwareAcceleratedCanvas;
     
            boolean more = false;
            final boolean childHasIdentityMatrix = hasIdentityMatrix();
            final int parentFlags = parent.mGroupFlags;
     
            if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {
                parent.getChildTransformation().clear();
                parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
            }
     
            Transformation transformToApply = null;
            boolean concatMatrix = false;
            final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
            final Animation a = getAnimation();
            if (a != null) {
                more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
                concatMatrix = a.willChangeTransformationMatrix();
                if (concatMatrix) {
                    mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
                }
                transformToApply = parent.getChildTransformation();
            } else {
                if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) != 0) {
                    // No longer animating: clear out old animation matrix
                    mRenderNode.setAnimationMatrix(null);
                    mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
                }
                if (!drawingWithRenderNode
                        && (parentFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
                    final Transformation t = parent.getChildTransformation();
                    final boolean hasTransform = parent.getChildStaticTransformation(this, t);
                    if (hasTransform) {
                        final int transformType = t.getTransformationType();
                        transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
                        concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
                    }
                }
            }
     
            concatMatrix |= !childHasIdentityMatrix;
     
            // Sets the flag as early as possible to allow draw() implementations
            // to call invalidate() successfully when doing animations
            mPrivateFlags |= PFLAG_DRAWN;
     
            if (!concatMatrix &&
                    (parentFlags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS |
                            ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN &&
                    canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) &&
                    (mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) {
                mPrivateFlags2 |= PFLAG2_VIEW_QUICK_REJECTED;
                return more;
            }
            mPrivateFlags2 &= ~PFLAG2_VIEW_QUICK_REJECTED;
     
            if (hardwareAcceleratedCanvas) {
                // Clear INVALIDATED flag to allow invalidation to occur during rendering, but
                // retain the flag's value temporarily in the mRecreateDisplayList flag
                mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
                mPrivateFlags &= ~PFLAG_INVALIDATED;
            }
     
            RenderNode renderNode = null;
            Bitmap cache = null;
            int layerType = getLayerType(); // TODO: signify cache state with just 'cache' local
            if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
                 if (layerType != LAYER_TYPE_NONE) {
                     // If not drawing with RenderNode, treat HW layers as SW
                     layerType = LAYER_TYPE_SOFTWARE;
                     buildDrawingCache(true);
                }
                cache = getDrawingCache(true);
            }
     
            if (drawingWithRenderNode) {
                // Delay getting the display list until animation-driven alpha values are
                // set up and possibly passed on to the view
                renderNode = updateDisplayListIfDirty();
                if (!renderNode.isValid()) {
                    // Uncommon, but possible. If a view is removed from the hierarchy during the call
                    // to getDisplayList(), the display list will be marked invalid and we should not
                    // try to use it again.
                    renderNode = null;
                    drawingWithRenderNode = false;
                }
            }
     
            int sx = 0;
            int sy = 0;
            if (!drawingWithRenderNode) {
                computeScroll();
                sx = mScrollX;
                sy = mScrollY;
            }
     
            final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
            final boolean offsetForScroll = cache == null && !drawingWithRenderNode;
     
            int restoreTo = -1;
            if (!drawingWithRenderNode || transformToApply != null) {
                restoreTo = canvas.save();
            }
            if (offsetForScroll) {
                canvas.translate(mLeft - sx, mTop - sy);
            } else {
                if (!drawingWithRenderNode) {
                    canvas.translate(mLeft, mTop);
                }
                if (scalingRequired) {
                    if (drawingWithRenderNode) {
                        // TODO: Might not need this if we put everything inside the DL
                        restoreTo = canvas.save();
                    }
                    // mAttachInfo cannot be null, otherwise scalingRequired == false
                    final float scale = 1.0f / mAttachInfo.mApplicationScale;
                    canvas.scale(scale, scale);
                }
            }
     
            float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
            if (transformToApply != null
                    || alpha < 1
                    || !hasIdentityMatrix()
                    || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
                if (transformToApply != null || !childHasIdentityMatrix) {
                    int transX = 0;
                    int transY = 0;
     
                    if (offsetForScroll) {
                        transX = -sx;
                        transY = -sy;
                    }
     
                    if (transformToApply != null) {
                        if (concatMatrix) {
                            if (drawingWithRenderNode) {
                                renderNode.setAnimationMatrix(transformToApply.getMatrix());
                            } else {
                                // Undo the scroll translation, apply the transformation matrix,
                                // then redo the scroll translate to get the correct result.
                                canvas.translate(-transX, -transY);
                                canvas.concat(transformToApply.getMatrix());
                                canvas.translate(transX, transY);
                            }
                            parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                        }
     
                        float transformAlpha = transformToApply.getAlpha();
                        if (transformAlpha < 1) {
                            alpha *= transformAlpha;
                            parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                        }
                    }
     
                    if (!childHasIdentityMatrix && !drawingWithRenderNode) {
                        canvas.translate(-transX, -transY);
                        canvas.concat(getMatrix());
                        canvas.translate(transX, transY);
                    }
                }
     
                // Deal with alpha if it is or used to be <1
                if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
                    if (alpha < 1) {
                        mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA;
                    } else {
                        mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA;
                    }
                    parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                    if (!drawingWithDrawingCache) {
                        final int multipliedAlpha = (int) (255 * alpha);
                        if (!onSetAlpha(multipliedAlpha)) {
                            if (drawingWithRenderNode) {
                                renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
                            } else if (layerType == LAYER_TYPE_NONE) {
                                canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),
                                        multipliedAlpha);
                            }
                        } else {
                            // Alpha is handled by the child directly, clobber the layer's alpha
                            mPrivateFlags |= PFLAG_ALPHA_SET;
                        }
                    }
                }
            } else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
                onSetAlpha(255);
                mPrivateFlags &= ~PFLAG_ALPHA_SET;
            }
     
            if (!drawingWithRenderNode) {
                // apply clips directly, since RenderNode won't do it for this draw
                if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
                    if (offsetForScroll) {
                        canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
                    } else {
                        if (!scalingRequired || cache == null) {
                            canvas.clipRect(0, 0, getWidth(), getHeight());
                        } else {
                            canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
                        }
                    }
                }
     
                if (mClipBounds != null) {
                    // clip bounds ignore scroll
                    canvas.clipRect(mClipBounds);
                }
            }
     
            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) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
                    // no layer paint, use temporary paint to draw bitmap
                    Paint cachePaint = parent.mCachePaint;
                    if (cachePaint == null) {
                        cachePaint = new Paint();
                        cachePaint.setDither(false);
                        parent.mCachePaint = cachePaint;
                    }
                    cachePaint.setAlpha((int) (alpha * 255));
                    canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
                } else {
                    // use layer paint to draw the bitmap, merging the two alphas, but also restore
                    int layerPaintAlpha = mLayerPaint.getAlpha();
                    if (alpha < 1) {
                        mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                    }
                    canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
                    if (alpha < 1) {
                        mLayerPaint.setAlpha(layerPaintAlpha);
                    }
                }
            }
     
            if (restoreTo >= 0) {
                canvas.restoreToCount(restoreTo);
            }
     
            if (a != null && !more) {
                if (!hardwareAcceleratedCanvas && !a.getFillAfter()) {
                    onSetAlpha(255);
                }
                parent.finishAnimatingView(this, a);
            }
     
            if (more && hardwareAcceleratedCanvas) {
                if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
                    // alpha animations should cause the child to recreate its display list
                    invalidate(true);
                }
            }
     
            mRecreateDisplayList = false;
     
            return more;
        }
    
    

    该方法主要判断是否有绘制缓存,如果有直接使用缓存,如果没有重复调用上面的draw()方法。

    然后是第五步绘制滑动时的渐变效果,最后在绘制滚动条。

    至此,所有的View对象都绘制出来了。

    需要注意的是:View绘制的画布参数canvas是由surface对象获得,意味着View视图绘制最终会绘制到Surface对象去。父类View绘制主要是绘制背景、边框渐变效果、进度条,View具体的内容绘制调用了onDraw方法,通过该方法把View内容的绘制逻辑留给子类去实现。因此在自定义View的时候都一般都需要重写父类的onDraw方法来实现View内容绘制。

    简单总结概括一下,draw的时序图如下:

    image

    总结

    View的绘制流程是从 ViewRoot 的 performTraversals 方法开始的,它经过 measure、layout、draw三个过程才最终将一个View绘制出来,performTraversals会依次调用 performMeasure,performLayout和 performDraw 三个方法,这三个方法分别会完成 View 的 measure、layout、draw的流程。

    image

    View的绘制主流程

    在measure方法中,会调用onMeasure方法,在onMeasure方法中会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递给子容器,这样就完成了一次测量,接着子元素会重复父容器的measure的测量过程,如此反复的完成整个View树的过程。同理performLayout的执行原理和performDraw的执行原理与performMeasure的原理类似。

    关于View的绘制流程,经常出现在Android面试过程中,同时会严重影响到界面开发。这一块理清了,无论是掌握系统View,还是自定义View,也或者是解决一些bug,都有不小的帮助。

    原文链接:手把手教你读懂源码,View的绘制流程详细剖析

    View系列文章:
    Android View从源码的角度分析加载流程
    Android View从源码的角度分析Touch事件传递流程
    Android View从源码的角度分析事件的注册和接收

    相关文章

      网友评论

          本文标题:Android View从源码的角度分析绘制流程

          本文链接:https://www.haomeiwen.com/subject/pxmxvftx.html