彻底弄懂view的绘制

昨天花了一天的时间看了Androidview的绘制,了解了activity的生成到绘制到界面的过程,activityThread的attactToApplication会初始化一个phonewindows,phonewindows是管理所有的界面,然后activity的setconent会初始化一个installdecorn,会初始化decornViewi,初始化一个contentParents,将view加入到contentParent上,然后调用rootviewipilmer的addview,

最后会调用scheduleTraver,然后调用view onmeasude layout ,draw,

这是昨天看了一天的理解,肯定很粗糙有些忘记了,有些记错了,但是没有关系,每次看源码希望自己不是死记硬背,希望自己带着问题去理解,最好是写代码,写代码会记得深刻一点,

   @Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        // TODO Push resumeArgs into the activity for consideration
        // skip below steps for double-resume and r.mFinish = true case.
        if (!performResumeActivity(r, finalStateRequest, reason)) {
            return;
        }
        if (mActivitiesToBeDestroyed.containsKey(r.token)) {
            // Although the activity is resumed, it is going to be destroyed. So the following
            // UI operations are unnecessary and also prevents exception because its token may
            // be gone that window manager cannot recognize it. All necessary cleanup actions
            // performed below will be done while handling destruction.
            return;
        }

        final Activity a = r.activity;

        if (localLOGV) {
            Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity
                    + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished);
        }

        final int forwardBit = isForward
                ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

        // If the window hasn't yet been added to the window manager,
        // and this guy didn't finish itself or start another activity,
        // then go ahead and add the window.
        boolean willBeVisible = !a.mStartedActivity;
        if (!willBeVisible) {
            willBeVisible = ActivityClient.getInstance().willActivityBeVisible(
                    a.getActivityToken());
        }
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
        } else if (!willBeVisible) {
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }

        // Get rid of anything left hanging around.
        cleanUpPendingRemoveWindows(r, false /* force */);

        // The window is now visible if it has been added, we are not
        // simply finishing, and we are not starting another activity.
        if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
            if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
            ViewRootImpl impl = r.window.getDecorView().getViewRootImpl();
            WindowManager.LayoutParams l = impl != null
                    ? impl.mWindowAttributes : r.window.getAttributes();
            if ((l.softInputMode
                    & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
                    != forwardBit) {
                l.softInputMode = (l.softInputMode
                        & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
                        | forwardBit;
                if (r.activity.mVisibleFromClient) {
                    ViewManager wm = a.getWindowManager();
                    View decor = r.window.getDecorView();
                    wm.updateViewLayout(decor, l);
                }
            }

            r.activity.mVisibleFromServer = true;
            mNumVisibleActivities++;
            if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();
            }
        }

        r.nextIdle = mNewActivities;
        mNewActivities = r;
        if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
        Looper.myQueue().addIdleHandler(new Idler());
    }

这一段代码是在activity的启动流程中复习过一次了,现在看activity的启动流程又忘记了,所以自己总结东西要经常拿出来复习,巩固,

ViewRootImpl 是连接 WindowManager 和 DecorView 的纽带,测量、放C置和绘制三大流程都是通过 ViewRootImpl 实现的。

在 ActivityThread 的 handleResumeActivity() 方法中,会调用 WindowManager 的 addView() 方法,而具体添加 DecorView 的操作是在 WindowManagerGlobal 中。

这段代码是phonewindow里面的setConetnView

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

其中

installDecor(); 初始化DecorView,初始化了ContentParent,
generateLayout(mDecor); 是生成了ContentParent,

 mContentParent.addView(view, params); 是添加了view和参数。

Window 的实现类为 PhoneWindow,在 PhoneWindow 的 setContentView() 方法中,会调用 installDecor() 方法创建 DecorView ,然后调用 LayoutInflate 的 inflate() 方法把 ActionBarOverlayLayout 加入到 DecorView 中。

在 installDecor() 方法中,会调用 generateLayout() 方法生成 mContentParent。

在 generateLayout() 方法中,会根据不同的 feature 来生成不同的 DecorView,比如没有设定任何 feature 时,对应的 DecorView 的布局就是 screen_simple 。。

contentParent是一个viewGroup,addview 就是

mLayoutInflater.inflate(layoutResID, mContentParent);

就是这句将我们写布局加到了contentParent中去了,

 installDecor()方法的总结

installDecor()里面包含了大量的逻辑。
开始初始化了DecorView
然后初始化mContentParent
在初始化mContentParent时,又看到:
获取window的属性并赋值给PhoneWindow的逻辑。
看到了根据window的属性加载了不同的布局,并加载到给DecorView的逻辑。

最后看到了通过findViewById的方法获取到了mContentParent并且返回

public void addView(View child, int index, LayoutParams params) {
        if (DBG) {
            System.out.println(this + " addView");
        }

        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }

        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
        // therefore, we call requestLayout() on ourselves before, so that the child's request
        // will be blocked at our level
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }

这是网上复制的一张window的结构图

 mDecor

这是Window的顶层View,包含了Window的装饰。

DecorView是一个FrameLayout,是最顶层的View。包含Window(顶级窗口)的装饰(比如大小,是否透明等等属性)会体现在这个View上。这里可以大概知道顶级窗口Window(PhoneWindow)和顶级View(DecorView)的关系:顶级窗口Window(PhoneWindow)包含的状态属性,会在顶级View(DecorView)体现出来,比如窗口大小,背景等属性。(当然下面的代码也会体现出这点)

mContentParent:这是一个放置Window内容的View。它不是mDecor。它是mDecor的子View。(这里先不管mDecor是什么,后面会提到。

 

AppCompatDelegate到底用来干什么的呢?
AppCompatDelegate是一个可以放在任意Activity中,并且回调相应生命周期的类,在不使用AppCompatActivity的情况下,也可以得到一致的主题与颜色,用于兼容不同版本的控件

   public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        //View树正在进行布局流程
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                //ViewRootImpl是否会截获处理布局(已经在不居过程中),后面会有说明
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        //View设置标记位,需要重新布局
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        //向父容器请求布局,即调用父容器的requestLayout方法,为父容器添加PFLAG_FORCE_LAYOUT标记位
        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }
//返回值决定请求布局发起方是否进行递归布局申请,fase:中断布局请求;ture:布局请求继续执行。
boolean requestLayoutDuringLayout(final View view) {
        if (view.mParent == null || view.mAttachInfo == null) {
            // Would not normally trigger another layout, so just let it pass through as usual
            return true;
        }
        //正在布局的视图包含请求view,则添加
        if (!mLayoutRequesters.contains(view)) {
            mLayoutRequesters.add(view);
        }
        //是否允许在不居中申请布局请求
        if (!mHandlingLayoutInLayoutRequest) {
            // Let the request proceed normally; it will be processed in a second layout pass
            // if necessary
            return true;
        } else {
            // Don't let the request proceed during the second layout pass.
            // It will post to the next frame instead.
            return false;
        }
    }

 

 

 ViewGroup 中没有复写 requestLayout 方法,所以 ViewGroup 也是调用和 View 同样的方法

我们来看 ViewRootImpl 中的 requestLayout() 方法

 

public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //线程检查
            checkThread();
            mLayoutRequested = true;
            //请求注册Vsync信号触发绘制
            scheduleTraversals();
        }
    }
  void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

 

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

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

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

 

 

 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;
        WindowManager.LayoutParams lp = mWindowAttributes;

        int desiredWindowWidth;
        int desiredWindowHeight;

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

        WindowManager.LayoutParams params = null;
        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;
            }
        }

        Rect frame = mWinFrame;
        if (mFirst) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;

            final Configuration config = getConfiguration();
            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 if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
                    || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                // For wrap content, we have to remeasure later on anyways. Use size consistent with
                // below so we get best use of the measure cache.
                final Rect bounds = getWindowBoundsInsetSystemBars();
                desiredWindowWidth = bounds.width();
                desiredWindowHeight = bounds.height();
            } else {
                // After addToDisplay, the frame contains the frameHint from window manager, which
                // for most windows is going to be the same size as the result of relayoutWindow.
                // Using this here allows us to avoid remeasuring after relayoutWindow
                desiredWindowWidth = frame.width();
                desiredWindowHeight = frame.height();
            }

            // 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.mWindowVisibility = viewVisibility;
            mAttachInfo.mRecomputeGlobalAttributes = false;
            mLastConfigurationFromResources.setTo(config);
            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(config.getLayoutDirection());
            }
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            dispatchApplyInsets(host);
        } 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();
            }
        }

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

        if (mFirst) {
            // make sure touch mode code executes by setting cached value
            // to opposite of the added touch mode.
            mAttachInfo.mInTouchMode = !mAddedTouchMode;
            ensureTouchModeLocally(mAddedTouchMode);
        }

        boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
        if (layoutRequested) {
            if (!mFirst) {
                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 {
                        final Rect bounds = getWindowBoundsInsetSystemBars();
                        desiredWindowWidth = bounds.width();
                        desiredWindowHeight = bounds.height();
                    }
                }
            }

            // Ask host how big it wants to be
            windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(),
                    desiredWindowWidth, desiredWindowHeight);
        }

        if (collectViewAttributes()) {
            params = lp;
        }
        if (mAttachInfo.mForceReportNewAttributes) {
            mAttachInfo.mForceReportNewAttributes = false;
            params = lp;
        }

        if (mFirst || mAttachInfo.mViewVisibilityChanged) {
            mAttachInfo.mViewVisibilityChanged = false;
            int resizeMode = mSoftInputMode & 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 & SOFT_INPUT_MASK_ADJUST) != resizeMode) {
                    lp.softInputMode = (lp.softInputMode & ~SOFT_INPUT_MASK_ADJUST) | resizeMode;
                    params = lp;
                }
            }
        }

        if (mApplyInsetsRequested) {
            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;
        final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
        boolean surfaceSizeChanged = false;
        boolean surfaceCreated = false;
        boolean surfaceDestroyed = false;
        // True if surface generation id changes or relayout result is RELAYOUT_RES_SURFACE_CHANGED.
        boolean surfaceReplaced = false;

        final boolean windowAttributesChanged = mWindowAttributesChanged;
        if (windowAttributesChanged) {
            mWindowAttributesChanged = false;
            params = lp;
        }

        if (params != null) {
            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0
                    && !PixelFormat.formatHasAlpha(params.format)) {
                params.format = PixelFormat.TRANSLUCENT;
            }
            adjustLayoutParamsForCompatibility(params);
            controlInsetsForCompatibility(params);
            if (mDispatchedSystemBarAppearance != params.insetsFlags.appearance) {
                mDispatchedSystemBarAppearance = params.insetsFlags.appearance;
                mView.onSystemBarAppearanceChanged(mDispatchedSystemBarAppearance);
            }
        }

        if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
                || mForceNextWindowRelayout) {
            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                        TextUtils.formatSimple("relayoutWindow#"
                                        + "first=%b/resize=%b/vis=%b/params=%b/force=%b",
                                mFirst, windowShouldResize, viewVisibilityChanged, params != null,
                                mForceNextWindowRelayout));
            }

            mForceNextWindowRelayout = false;

            // If this window is giving internal insets to the window manager, then we want to first
            // make the provided insets unchanged 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;

            if (mSurfaceHolder != null) {
                mSurfaceHolder.mSurfaceLock.lock();
                mDrawingAllowed = true;
            }

            boolean hwInitialized = false;
            boolean dispatchApplyInsets = false;
            boolean hadSurface = mSurface.isValid();

            try {
                if (DEBUG_LAYOUT) {
                    Log.i(mTag, "host=w:" + host.getMeasuredWidth() + ", h:" +
                            host.getMeasuredHeight() + ", params=" + params);
                }

                if (mFirst || viewVisibilityChanged) {
                    mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
                }
                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
                final boolean dragResizing = mPendingDragResizing;
                if (mSyncSeqId > mLastSyncSeqId) {
                    mLastSyncSeqId = mSyncSeqId;
                    if (DEBUG_BLAST) {
                        Log.d(mTag, "Relayout called with blastSync");
                    }
                    reportNextDraw();
                    mSyncBuffer = true;
                }

                final boolean surfaceControlChanged =
                        (relayoutResult & RELAYOUT_RES_SURFACE_CHANGED)
                                == RELAYOUT_RES_SURFACE_CHANGED;

                if (mSurfaceControl.isValid()) {
                    updateOpacity(mWindowAttributes, dragResizing,
                            surfaceControlChanged /*forceUpdate */);
                    // No need to updateDisplayDecoration if it's a new SurfaceControl and
                    // mDisplayDecorationCached is false, since that's the default for a new
                    // SurfaceControl.
                    if (surfaceControlChanged && mDisplayDecorationCached) {
                        updateDisplayDecoration();
                    }
                }

                if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
                        + " surface=" + mSurface);

                // If the pending {@link MergedConfiguration} handed back from
                // {@link #relayoutWindow} does not match the one last reported,
                // WindowManagerService has reported back a frame from a configuration not yet
                // handled by the client. In this case, we need to accept the configuration so we
                // do not lay out and draw with the wrong configuration.
                if (!mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {
                    if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: "
                            + mPendingMergedConfiguration.getMergedConfiguration());
                    performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration),
                            !mFirst, INVALID_DISPLAY /* same display */);
                    updatedConfiguration = true;
                }

                surfaceSizeChanged = false;
                if (!mLastSurfaceSize.equals(mSurfaceSize)) {
                    surfaceSizeChanged = true;
                    mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y);
                }
                final boolean alwaysConsumeSystemBarsChanged =
                        mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars;
                updateColorModeIfNeeded(lp.getColorMode());
                surfaceCreated = !hadSurface && mSurface.isValid();
                surfaceDestroyed = hadSurface && !mSurface.isValid();

                // When using Blast, the surface generation id may not change when there's a new
                // SurfaceControl. In that case, we also check relayout flag
                // RELAYOUT_RES_SURFACE_CHANGED since it should indicate that WMS created a new
                // SurfaceControl.
                surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId()
                        || surfaceControlChanged) && mSurface.isValid();
                if (surfaceReplaced) {
                    mSurfaceSequenceId++;
                }

                if (alwaysConsumeSystemBarsChanged) {
                    mAttachInfo.mAlwaysConsumeSystemBars = mPendingAlwaysConsumeSystemBars;
                    dispatchApplyInsets = true;
                }
                if (updateCaptionInsets()) {
                    dispatchApplyInsets = true;
                }
                if (dispatchApplyInsets || mLastSystemUiVisibility !=
                        mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested) {
                    mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
                    dispatchApplyInsets(host);
                    // We applied insets so force contentInsetsChanged to ensure the
                    // hierarchy is measured below.
                    dispatchApplyInsets = true;
                }

                if (surfaceCreated) {
                    // If we are creating a new surface, then we need to
                    // completely redraw it.
                    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.mThreadedRenderer != null) {
                        try {
                            hwInitialized = mAttachInfo.mThreadedRenderer.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
                                mAttachInfo.mThreadedRenderer.allocateBuffers();
                            }
                        } catch (OutOfResourcesException e) {
                            handleOutOfResourcesException(e);
                            return;
                        }
                    }
                } else if (surfaceDestroyed) {
                    // 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 (isHardwareEnabled()) {
                        mAttachInfo.mThreadedRenderer.destroy();
                    }
                } else if ((surfaceReplaced
                        || surfaceSizeChanged || windowRelayoutWasForced)
                        && mSurfaceHolder == null
                        && mAttachInfo.mThreadedRenderer != null
                        && mSurface.isValid()) {
                    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.mThreadedRenderer.updateSurface(mSurface);
                    } catch (OutOfResourcesException e) {
                        handleOutOfResourcesException(e);
                        return;
                    }
                }

                if (mDragResizing != dragResizing) {
                    if (dragResizing) {
                        final boolean backdropSizeMatchesFrame =
                                mWinFrame.width() == mPendingBackDropFrame.width()
                                        && mWinFrame.height() == mPendingBackDropFrame.height();
                        // TODO: Need cutout?
                        startDragResizing(mPendingBackDropFrame, !backdropSizeMatchesFrame,
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mResizeMode);
                    } else {
                        // We shouldn't come here, but if we come we should end the resize.
                        endDragResizing();
                    }
                }
                if (!mUseMTRenderer) {
                    if (dragResizing) {
                        mCanvasOffsetX = mWinFrame.left;
                        mCanvasOffsetY = mWinFrame.top;
                    } else {
                        mCanvasOffsetX = mCanvasOffsetY = 0;
                    }
                }
            } catch (RemoteException e) {
            } finally {
                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
            }

            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 (surfaceCreated) {
                    mSurfaceHolder.ungetCallbacks();

                    mIsCreating = true;
                    SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
                    if (callbacks != null) {
                        for (SurfaceHolder.Callback c : callbacks) {
                            c.surfaceCreated(mSurfaceHolder);
                        }
                    }
                }

                if ((surfaceCreated || surfaceReplaced || surfaceSizeChanged
                        || windowAttributesChanged) && mSurface.isValid()) {
                    SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
                    if (callbacks != null) {
                        for (SurfaceHolder.Callback c : callbacks) {
                            c.surfaceChanged(mSurfaceHolder, lp.format,
                                    mWidth, mHeight);
                        }
                    }
                    mIsCreating = false;
                }

                if (surfaceDestroyed) {
                    notifyHolderSurfaceDestroyed();
                    mSurfaceHolder.mSurfaceLock.lock();
                    try {
                        mSurfaceHolder.mSurface = new Surface();
                    } finally {
                        mSurfaceHolder.mSurfaceLock.unlock();
                    }
                }
            }

            final ThreadedRenderer threadedRenderer = mAttachInfo.mThreadedRenderer;
            if (threadedRenderer != null && threadedRenderer.isEnabled()) {
                if (hwInitialized
                        || mWidth != threadedRenderer.getWidth()
                        || mHeight != threadedRenderer.getHeight()
                        || mNeedsRendererSetup) {
                    threadedRenderer.setup(mWidth, mHeight, mAttachInfo,
                            mWindowAttributes.surfaceInsets);
                    mNeedsRendererSetup = false;
                }
            }

            // TODO: In the CL "ViewRootImpl: Fix issue with early draw report in
            // seamless rotation". We moved processing of RELAYOUT_RES_BLAST_SYNC
            // earlier in the function, potentially triggering a call to
            // reportNextDraw(). That same CL changed this and the next reference
            // to wasReportNextDraw, such that this logic would remain undisturbed
            // (it continues to operate as if the code was never moved). This was
            // done to achieve a more hermetic fix for S, but it's entirely
            // possible that checking the most recent value is actually more
            // correct here.
            if (!mStopped || mReportNextDraw) {
                if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()
                        || dispatchApplyInsets || updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,
                            lp.privateFlags);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height,
                            lp.privateFlags);

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

                     // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

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

                    if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }

                    if (measureAgain) {
                        if (DEBUG_LAYOUT) Log.v(mTag,
                                "And hey let's measure once more: width=" + width
                                + " height=" + height);
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;
                }
            }
        } else {
            // Not the first pass and no window/insets/visibility change but the window
            // may have moved and we need check that and if so to update the left and right
            // in the attach info. We translate only the window frame since on window move
            // the window manager tells us only for the new frame but the insets are the
            // same and we do not want to translate them more than once.
            maybeHandleWindowMove(frame);
        }

        if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
            // If the surface has been replaced, there's a chance the bounds layer is not parented
            // to the new layer. When updating bounds layer, also reparent to the main VRI
            // SurfaceControl to ensure it's correctly placed in the hierarchy.
            //
            // This needs to be done on the client side since WMS won't reparent the children to the
            // new surface if it thinks the app is closing. WMS gets the signal that the app is
            // stopping, but on the client side it doesn't get stopped since it's restarted quick
            // enough. WMS doesn't want to keep around old children since they will leak when the
            // client creates new children.
            prepareSurfaces();
        }

        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            performLayout(lp, mWidth, mHeight);

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

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

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

                if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
                    mPreviousTransparentRegion.set(mTransparentRegion);
                    mFullRedrawNeeded = true;
                    // TODO: Ideally we would do this in prepareSurfaces,
                    // but prepareSurfaces is currently working under
                    // the assumption that we paused the render thread
                    // via the WM relayout code path. We probably eventually
                    // want to synchronize transparent region hint changes
                    // with draws.
                    SurfaceControl sc = getSurfaceControl();
                    if (sc.isValid()) {
                        mTransaction.setTransparentRegionHint(sc, mTransparentRegion).apply();
                    }
                }
            }

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

        // These callbacks will trigger SurfaceView SurfaceHolder.Callbacks and must be invoked
        // after the measure pass. If its invoked before the measure pass and the app modifies
        // the view hierarchy in the callbacks, we could leave the views in a broken state.
        if (surfaceCreated) {
            notifySurfaceCreated();
        } else if (surfaceReplaced) {
            notifySurfaceReplaced();
        } else if (surfaceDestroyed)  {
            notifySurfaceDestroyed();
        }

        if (triggerGlobalLayoutListener) {
            mAttachInfo.mRecomputeGlobalAttributes = false;
            mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
        }

        Rect contentInsets = null;
        Rect visibleInsets = null;
        Region touchableRegion = null;
        int touchableInsetMode = TOUCHABLE_INSETS_REGION;
        boolean computedInternalInsets = false;
        if (computesInternalInsets) {
            final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;

            // Clear the original insets.
            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.
                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;
                }
                computedInternalInsets = true;
            }
            touchableInsetMode = insets.mTouchableInsets;
        }
        boolean needsSetInsets = computedInternalInsets;
        needsSetInsets |= !Objects.equals(mPreviousTouchableRegion, mTouchableRegion) &&
            (mTouchableRegion != null);
        if (needsSetInsets) {
            if (mTouchableRegion != null) {
                if (mPreviousTouchableRegion == null) {
                    mPreviousTouchableRegion = new Region();
                }
                mPreviousTouchableRegion.set(mTouchableRegion);
                if (touchableInsetMode != TOUCHABLE_INSETS_REGION) {
                    Log.e(mTag, "Setting touchableInsetMode to non TOUCHABLE_INSETS_REGION" +
                          " from OnComputeInternalInsets, while also using setTouchableRegion" +
                          " causes setTouchableRegion to be ignored");
                }
            } else {
                mPreviousTouchableRegion = null;
            }
            if (contentInsets == null) contentInsets = new Rect(0,0,0,0);
            if (visibleInsets == null) visibleInsets = new Rect(0,0,0,0);
            if (touchableRegion == null) {
                touchableRegion = mTouchableRegion;
            } else if (touchableRegion != null && mTouchableRegion != null) {
                touchableRegion.op(touchableRegion, mTouchableRegion, Region.Op.UNION);
            }
            try {
                mWindowSession.setInsets(mWindow, touchableInsetMode,
                                         contentInsets, visibleInsets, touchableRegion);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } else if (mTouchableRegion == null && mPreviousTouchableRegion != null) {
            mPreviousTouchableRegion = null;
            try {
                mWindowSession.clearTouchableRegion(mWindow);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        if (mFirst) {
            if (sAlwaysAssignFocus || !isInTouchMode()) {
                // handle first focus request
                if (DEBUG_INPUT_RESIZE) {
                    Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus());
                }
                if (mView != null) {
                    if (!mView.hasFocus()) {
                        mView.restoreDefaultFocus();
                        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());
                        }
                    }
                }
            } else {
                // Some views (like ScrollView) won't hand focus to descendants that aren't within
                // their viewport. Before layout, there's a good change these views are size 0
                // which means no children can get focus. After layout, this view now has size, but
                // is not guaranteed to hand-off focus to a focusable child (specifically, the edge-
                // case where the child has a size prior to layout and thus won't trigger
                // focusableViewAvailable).
                View focused = mView.findFocus();
                if (focused instanceof ViewGroup
                        && ((ViewGroup) focused).getDescendantFocusability()
                                == ViewGroup.FOCUS_AFTER_DESCENDANTS) {
                    focused.restoreDefaultFocus();
                }
            }
        }

        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.type == TYPE_TOAST;
            if (!isToast) {
                host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
            }
        }

        mFirst = false;
        mWillDrawSoon = false;
        mNewSurfaceNeeded = false;
        mActivityRelaunched = false;
        mViewVisibility = viewVisibility;
        mHadWindowFocus = hasWindowFocus;

        mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);

        if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
            reportNextDraw();
        }

        boolean cancelAndRedraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
        if (!cancelAndRedraw) {
            createSyncIfNeeded();
        }

        if (!isViewVisible) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }

            if (mSyncBufferCallback != null) {
                mSyncBufferCallback.onBufferReady(null);
            }
        } else if (cancelAndRedraw) {
            // Try again
            scheduleTraversals();
        } else {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }
            if (!performDraw() && mSyncBufferCallback != null) {
                mSyncBufferCallback.onBufferReady(null);
            }
        }

        if (mAttachInfo.mContentCaptureEvents != null) {
            notifyContentCatpureEvents();
        }

        mIsInTraversal = false;
        mRelayoutRequested = false;

        if (!cancelAndRedraw) {
            mReportNextDraw = false;
            mSyncBufferCallback = null;
            mSyncBuffer = false;
            if (isInLocalSync()) {
                mSurfaceSyncer.markSyncReady(mSyncId);
                mSyncId = UNSET_SYNC_ID;
            }
        }
    }

 requestLayout 事件层层向上传递,直到 DecorView(即根 View),又会传递给 ViewRootImpl(ViewRootImpl 接管了 DecorView 的 ViewParent 功能),也即是说子 View 的requestLayout 事件,最终会被 ViewRootImpl 接收并得到处理。纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,作为 View 的外交大管家,只有 ViewRootImpl 能够处理 requestLayout 事件。

 invalidate 过程概览

public void invalidate() {
        invalidate(true);
    }

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

 

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        //代码省略……

        //这里判断该子View是否可见或者是否处于动画中
        if (skipInvalidate()) {
            return;
        }

        // Reset content capture caches
        mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
        mCachedContentCaptureSession = null;

        //根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
            if (fullInvalidate) {
                mLastIsOpaque = isOpaque();
                mPrivateFlags &= ~PFLAG_DRAWN;
            }

            //设置PFLAG_DIRTY标记位(增加脏区标记位)
            mPrivateFlags |= PFLAG_DIRTY;

            // 如果缓存失效,则清空对应标记
            if (invalidateCache) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }

            // Propagate the damage rectangle to the parent view.
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            //把需要重绘的区域传递给父容器
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                p.invalidateChild(this, damage);
            }

            //代码省略……
        }
    }

我们来看 ViewGroup 中的 invalidateChild() 方法 

@Deprecated(请注意这个废弃的标识,这个方法以后可能要废弃)
    @Override
    public final void invalidateChild(View child, final Rect dirty) {
        final AttachInfo attachInfo = mAttachInfo;
        //如果硬件加速开启(默认硬件加速是开启的)
        //现在能理解为什么这个方法标记未废弃方法了吧,因为进行了硬件加速的优化,具体细节以后在讲。
        if (attachInfo != null && attachInfo.mHardwareAccelerated) {
            // HW accelerated fast path
            //进一步处理,用的是新的代替废弃方法的执行方法。
            onDescendantInvalidated(child, child);
            return;
        }

        //代码省略……

        //如果硬件加速关闭,才会之前版本的递归遍历过程(此过程不讲了,网上大部分的博客还停留在这个版本阶段)
        parent = parent.invalidateChildInParent(location, dirty);

       //代码省略……

    }

我们来看 ViewGroup 中的 onDescendantInvalidated() 方法

//经过针对硬件加速优化后的代码,就显得清新脱俗了
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
        /*
         * HW-only, Rect-ignoring damage codepath
         *
         * We don't deal with rectangles here, since RenderThread native code computes damage for
         * everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
         */

        // if set, combine the animation flag into the parent
        mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);

        if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
            // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
            // optimization in provides in a DisplayList world.
            mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;

            // simplified invalidateChildInParent behavior: clear cache validity to be safe...
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        // ... and mark inval if in software layer that needs to repaint (hw handled in native)
        if (mLayerType == LAYER_TYPE_SOFTWARE) {
            // Layered parents should be invalidated. Escalate to a full invalidate (and note that
            // we do this after consuming any relevant flags from the originating descendant)
            mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
            target = this;
        }

        //递归调用父类的onDescendantInvalidated,最终会调用到ViewRootImpl中的这个方法。
        if (mParent != null) {
            mParent.onDescendantInvalidated(this, target);
        }
    }

我们来看 ViewRootImpl 中的 onDescendantInvalidated() 方法 

 

public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
        // TODO: Re-enable after camera is fixed or consider targetSdk checking this
        // checkThread();
        if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
            mIsAnimating = true;
        }
        //绘制方法
        invalidate();
    }

 我们来看 ViewRootImpl 中的 invalidate() 方法

 

void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
            //绘制请求,注册Vsync信号,等待上报绘制刷新。
            scheduleTraversals();
        }
    }

2.2.7 invalidate 小结

 当子 View 调用了 invalidate 方法后,会为该 View 添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到 ViewRootImpl 中,最终触发 performTraversals 方法,进行开始 View 树重绘流程(只绘制需要重绘的视图)。

2.2.8 postInvalidate 简介

 postInvalidate 和 invalidate 的作用是一样的,唯一的区别是,postInvalidate 可以在子线程中调用请求刷新 UI。为什么呢?因为请求重新布局和绘制,最终都会在 ViewRootImpl 中处理,而 ViewRootImpl 会在请求方法中,进行线程检查(是否是 UI 线程)。当子 view 中有请求绘制的需求怎么办,那么就用 postInvalidate。其实 postInvalidate 的功能就是 线程切换 + invalidate 调用。

requestLayout 和 invaldate 有什么区别?

  • requestLayout 和 invalidate 都会触发整个绘制流程。但是在 measure 和 layout 过程中,只会对 flag 设置为 FORCE_LAYOUT 的情况进行重新测量和布局,而 draw 只会重绘 flag 为 dirty 的区域。
  • requestLayout 是用来设置 FORCE_LAYOUT 标志,invalidate 用来设置 dirty 标志。所以 requestLayout 只会触发 measure 和 layout,invalidate 只会触发 draw。
  • 所以一般都是组合使用。比如:只要刷新的时候就调用 invalidate,需要重新 measure 就调用 requestLayout,后面再跟个 invalidate(为了保证重绘

 

    *
     * <p>This method can be invoked from outside of the UI thread
     * only when this View is attached to a window.</p>
     *
     * @param left The left coordinate of the rectangle to invalidate.
     * @param top The top coordinate of the rectangle to invalidate.
     * @param right The right coordinate of the rectangle to invalidate.
     * @param bottom The bottom coordinate of the rectangle to invalidate.
     *
     * @see #invalidate(int, int, int, int)
     * @see #invalidate(Rect)
     * @see #postInvalidateDelayed(long, int, int, int, int)
     */
    public void postInvalidate(int left, int top, int right, int bottom) {
        postInvalidateDelayed(0, left, top, right, bottom);
    }
  public void postInvalidateDelayed(long delayMilliseconds, int left, int top,
            int right, int bottom) {

        // We try only with the AttachInfo because there's no point in invalidating
        // if we are not attached to our window
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.obtain();
            info.target = this;
            info.left = left;
            info.top = top;
            info.right = right;
            info.bottom = bottom;

            attachInfo.mViewRootImpl.dispatchInvalidateRectDelayed(info, delayMilliseconds);
        }
    }

 

现在到了view的三个动作了measure,layout ,draw,看了别人博客上的一张图片

 

   private static int getRootMeasureSpec(int windowSize, int measurement, int privateFlags) {
        int measureSpec;
        final int rootDimension = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
                ? MATCH_PARENT : measurement;
        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;
    }

 最顶层分发的 Measure 的只有两种模式:EXACTLY / AT_MOST。

 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let them have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

 别人注释代码

//计算子View的MeasureSpec
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //父view的测量模式
        int specMode = MeasureSpec.getMode(spec);

        //父view的大小
        int specSize = MeasureSpec.getSize(spec);

        //父view出去padding能给到子View的最大值
        int size = Math.max(0, specSize - padding);
        
        //子view想要的实际大小和模式
        int resultSize = 0;
        int resultMode = 0;

        //父View的策略模式
        switch (specMode) {
        // Parent has imposed an exact size on us
         //父View的是精确模式
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        //父View的是最大模式
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        //父View的是未指定模式
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

 

 我们来看 View 中的 getDefaultSize() 方法

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        //策略模式为UNSPECIFIED时,用自己入参的大小(入参取值简单,不做说明),一般默认为0
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        //策略模式为AT_MOST/EXACTLY时,用父视图的大小
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

 

由上面的子 View 的 MeasureSpec 计算,可以引出一个问题:

自定义 View 中 如果 onMeasure 方法没有对 wrap_content 做处理,会发生什么?为什么?怎么解决?

  • 如果 View 布局参数设置为 wrap_content,而父视图为 AT_MOST/EXACTLY 时,对应的 View 的 mode 为 AT_MOST。
  • 此时 View 的宽高都返回从 MeasureSpec 中获取到的 size 值(也就是父视图的 size)。
  • 那么 View 的 wrap_content 效果和 match_parent 是一样的。
  • 解决方案就是重写 onMeasure,对 AT_MOST 进行特殊处理,比如给定默认宽高等

 

2.3.5 MeasureSpec 小结

View 的 measure 分发,说白了就是不停的计算最终的 MeasureSpec,确定最终的视图尺寸。所以会多次进行measure 计算。这个大家在开发过程中想必也会从 log 中有所体会

3.1 View 和 ViewGroup 的区别

View:测量自己尺寸,然后保存。 ViewGroup:递归遍历所有子 View,测量子 View 尺寸,然后保存;根据所有子 View 的尺寸计算保存自己的尺寸。

 

 View 的 measure 方法和 onMeasure 方法有什么区别和关系

   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);
///如果缓存中没有取到值就onMeasure去测量
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                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
    }
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

 

  • measure 方法使用了 final 来修饰,说明是不可修改的,onMeasure 方法则是可以让子类按需重写。
  • measure 方法用于检测缓存数据,对比是否要重新测量。
  • onMeasure 方法用于读取父布局的测量规则并按需求定制自己的测量规则,调用 setMeasuredDimension 确定自己的尺寸。

 

ViewGroup 里面有重写 onMeasure 方法吗?为什么?

  • ViewGroup 默认是没有重写 onMeasure 的,重写 onMeasure 方法这个任务是交给 ViewGroup 的子类的。
  • 不同的 ViewGroup 子类(LinearLayout、FrameLayout 等),它们的布局要求往往都不一样,那 onMeasure 方法就交给他们自己重写好了

为什么ViewGroup的measure过程不像单一View的measure过程那样对onMeasure做统一的实现?

  • onMeasure()的作用 = 测量View的宽/高值。
  • 因为不同的ViewGroup子类(LinearLayout、RelativeLayout / 自定义ViewGroup子类等)具备不同的布局特性,这导致他们子View的测量方法各有不同。
  • 在单一View measure过程中,getDefaultSize()只是简单的测量了宽高值,在实际使用时有时需更精细的测量。所以有时候也需重写onMeasure()。
  • 在自定义ViewGroup中,关键在于:根据需求复写onMeasure()从而实现你的子View测量逻辑。

View 的测量方法为什么会给多次调用? View 在什么情况下 getMeasuredWidht/Height() 和 getWidht/Height(),结果是不一致?

  • View 的测量方法会多次调用是因为前后的测量结果可能并不一致,比分说 LinearLayout 的权重,View 的第一次测量结果和最终的测量结果肯定是不一样的。
  • View 的 getMeasuredWidht/Height() 和 getWidht/Height() ,它们大多数情况下是一致的,但是在一种情况下是例外的,那就是在 layout 方法中重新设置 View 的位置大小,从而改变 View 的宽高,因为最终确定 View 的实际尺寸和位置信息的就是 setFrame 方法。
  • getMeasuredWidth方法获得的值是setMeasuredDimension方法设置的值,它的值在measure方法运行后就会确定。
  • getWidth方法获得是layout方法中传递的四个参数中的mRight-mLeft,它的值是在layout方法运行后确定的。
  • 一般情况下在onLayout方法中使用getMeasuredWidth方法,而在除onLayout方法之外的地方用getWidth方法。

第二步是到view的layout

同样在别人博客上扣的一张图

 

2.1 View 和 ViewGroup 的区别

  • View:View主要执行layout方法,使用 serFrame 方法来设置本身 View 的四个顶点的位置,确定View本身的位置。
  • ViewGroup:ViewGroup主要执行onLayout方法,递归遍历所有子View,确定子View的位置。

 

 2.3 layout 自顶向下分发

2.3.1 DecorView 分发

  • 最顶层(DecorView)分发的 layout
  • 我们来看ViewRootImpl中的 performLayout() 方法
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
  //代码省略……

  //mView即DecorView
  final View host = mView;

  //代码省略……

  //进行布局分发
  host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

  //代码省略……
}

看到这里,那 host.getMeasuredWidth() / host.getMeasuredHeight() 是什么?它是直接调用 View 中的方法,其实就是经过 measure 后的 DecorView 的测量宽度和高度。 

.3.2 ViewGroup 分发

ViewGroup(举例:LinearLayout)分发的 layout

2.3.2.1 我们先来看 ViewGroup 中的 layout() 方法

@Override
    public final void layout(int l, int t, int r, int b) {
        //ViewGoup的LayoutTransition动画,动画相关,可忽略
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            //调用父类(View)的layout方法
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }

 

 ViewGroup 里面的 layout 最终会调入到父类 View 中的 layout ,View 的 layout 后面讲解。这里可以先告诉大家,最终会调用 View 的 onLayout 方法,而 ViewGroup 的 onLayout 是抽象方法,所以它的子类 LinearLayout 必须要实现。

2.3.2.2 我们再来看 LinearLayout 中的 onLayout() 方法。
java复制代码@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            //纵向布局,对应xml中的:android:orientation="vertical"
            layoutVertical(l, t, r, b);
        } else {
            //横向布局,对应xml中的:android:orientation="horizontal"
            layoutHorizontal(l, t, r, b);
        }
    }


 2.3.2.3 挑一个纵向的吧,我们再来看 LinearLayout 中的 layoutVertical() 方法。

 

void layoutVertical(int left, int top, int right, int bottom) {
         //代码省略……

        //每个子View经过测量,都确定了尺寸,即宽和高,那么布局的确定只需要 left和top的位置。
        int childTop;
        int childLeft;
        
        //代码省略……
        
        //遍历子View
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                 //计算子View的测量宽 / 高值
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();

                //代码省略……
            
                //确定自身子View的位置(递归调用子View的setChildFrame(),实际上是调用了子View的layout)
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                
                //代码省略……
            }
        }
    }

 我们再来看 LinearLayout 中的 setChildFrame() 方法

private void setChildFrame(View child, int left, int top, int width, int height) {
        //开始递归调用View的layout方法
        child.layout(left, top, left + width, top + height);
    }

又一次回到了 View 的 layout 方法,接下来就看 View 分发的 layout。 

2.3.3 View 分发

 我们先来看 View 中的 layout() 方法。

public void layout(int l, int t, int r, int b) {
        //代码省略……

        // 当前视图的四个顶点
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        
        // 初始化四个顶点的值、判断当前View大小和位置是否发生了变化
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        
        //若视图的大小 & 位置发生变化。会重新确定该View所有的子View在父容器的位置:onLayout()
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            //重新确定该View所有的子View在父容器的位置
            onLayout(changed, l, t, r, b);

            //代码省略……
        }
    }

 第三部到了view的绘制步骤了

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== 编辑

2.1 View 和 ViewGroup 的区别

  • View:View不执行dispatchDraw(子View的绘制)。
  • ViewGroup:ViewGroup执行dispatchDraw(子View的递归绘制)。
  • 这是viewgroup的dispatchDraw 方法
    
        @Override
        protected void dispatchDraw(Canvas canvas) {
            final int childrenCount = mChildrenCount;
            final View[] children = mChildren;
            int flags = mGroupFlags;
    
            if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
                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.CLIP_SAVE_FLAG);
                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();
    
            canvas.enableZ();
            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 = drawsWithRenderNode(canvas)
                    ? 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);
                    }
                    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);
                }
            }
            canvas.disableZ();
    
            if (isShowingLayoutBounds()) {
                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);
            }
        }

     

     2.2.3 draw 各个过程的含义

 

2.3 draw 自顶向下分发

2.3.1 DecorView 分发

最顶层(DecorView)分发的 draw

2.3.1.1 我们来看 ViewRootImpl 中的 performDraw() 方法。

private void performDraw() {
        //代码省略……

        try {
            //绘制分发
            boolean canUseAsync = draw(fullRedrawNeeded);
            if (usingAsyncReport && !canUseAsync) {
                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
                usingAsyncReport = false;
            }
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        
        //代码省略……
    }

 我们来看 ViewRootImpl 中的 draw() 方法。

private boolean draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        
        //代码省略……

                //绘制分发
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
            }
        }

        //代码省略……
    }

我们来看 ViewRootImpl 中的 drawSoftware() 方法。 

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

        //代码省略……

            //canvas获取
            canvas = mSurface.lockCanvas(dirty);

        //代码省略……
            //绘制分发,终于到了DecorView的绘制分发
            mView.draw(canvas);

        //代码省略……
    }

我们来看 ViewGroup 中的 dispatchDraw() 方法。 

@Override
    protected void dispatchDraw(Canvas canvas) {
        //代码省略……
        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    //遍历子View绘制
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
                transientIndex++;
                if (transientIndex >= transientCount) {
                    transientIndex = -1;
                }
            }
      //代码省略……
    }

我们来看 ViewGroup 中的 drawChild() 方法。

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        //递归遍历子View的绘制
        return child.draw(canvas, this, drawingTime);
    }

 

3 View 分发

2.3.3.1 我们来看 View 中的 draw() 方法。

 

@CallSuper
    public void draw(Canvas canvas) {
        //代码省略……

        // Step 1, draw the background, if needed
        int saveCount;

        drawBackground(canvas);

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

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

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

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

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

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

        //代码省略……

        // Step 3, draw the content
        onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;

        //代码省略……

        drawAutofilledHighlight(canvas);

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

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

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

 View 中的 drawBackground() 方法:绘制背景。

我们来看 View 中的 onDraw() 方法。

/**
     * //需要具体控件自己实现,比如TextView
     * Implement this to do your drawing. 
     *
     * @param canvas the canvas on which the background will be drawn
     */
    protected void onDraw(Canvas canvas) {
    }

 我们来看 View 中的 dispatchDraw() 方法。

/**
     * //由draw调用以绘制子视图。这可能会被派生类重写,以便在绘制其子对象之前(但在绘制其自己的视图之后)获得控制权
     * 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) {

    }

2.3.3.5 View 中的 drawAutofilledHighlight() 方法:绘制自动填充视图的高亮。

2.3.3.6 View 中的 ViewOverlay::dispatchDraw() 方法:当覆盖层存在时,则绘制覆盖层。

2.3.3.8 View 中的 drawDefaultFocusHighlight 方法:绘制默认焦点的高亮。

2.3.3.9 View 中的 debugDrawFocus 方法:实就是开发者选项中的显示布局边界的效果。

ViewGroup 为什么无法通过 onDraw 方法绘制自定义内容?如果我希望重写 onDraw 来在 ViewGroup 进行一些绘制操作,怎样才能看到效果?

  • ViewGroup 无法显示绘制内容是因为默认设置的标记位关闭执行了它的 drawBackground 和 onDraw 方法。ViewGroup 一般就是管理布局相关的事项,不处理自身的绘制有助于提高执行效率。
  • 解决思路:调用 View.setWillNotDraw(false),为 ViewGroup 设置背景图。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值