一、requestLayout和invalidate的区别
requestLayout() 和 invalidate() 的区别在于它们作用的范围不同。requestLayout() 用于通知 View 进行重新布局,即测量、布局和绘制三个步骤都会重新进行;而 invalidate() 用于通知 View 进行重绘,仅仅是在原有的尺寸和位置上重新绘制 View,不会重新进行测量和布局。
requestLayout:调用 View.requestLayout 方法后会依次调用 performMeasure, performLayout 和 performDraw 方法,调用者 View 及其父 View 会重新从上往下进行 measure, layout 流程,一般情况下不会执行 draw 流程(子 View 会通过判断其尺寸/顶点是否发生改变而决定是否重新 measure/layout/draw 流程)。因此,当只需要进行重绘时可以使用 invalidate 方法,如果需要重新测量和布局则可以使用 requestLayout 方法,而 requestLayout 方法不一定会重绘,因此如果要进行重绘可以再手动调用 invalidate 方法。invalidate:调用 View.invalidate() 方法后会逐级往上调用父 View 的相关方法,最终在 Choreographer 的控制下调用 ViewRootImpl.performTraversals() 方法。只有满足 mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null … 等条件才会执行 measure 和 layout 流程,否则只执行 draw 流程,draw 流程的执行过程与是否开启硬件加速有关:关闭硬件加速则从 DecorView 开始往下的所有子 View 都会被重新绘制。开启硬件加速则只有调用 invalidate 方法的 View 才会重新绘制。二、requestLayout方法介绍
1、View.requestLayout
Java复制代码public void requestLayout() { if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { // 如果处于 Layout 则将该请求加入 ViewRootImpl 中的任务队列中 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; }}// ViewRootImplboolean requestLayoutDuringLayout(final View view) { if (!mLayoutRequesters.contains(view)) { mLayoutRequesters.add(view); } // ...}
如果此时处于 Layout 则将该请求加入 ViewRootImpl 中的任务队列中,否则向上调用父 View 的 requestLayout 方法,直到 ViewRootImpl 中:
Java复制代码public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }}
ViewRootImpl.requestLayout 方法在 check 了线程后将 mLayoutRequested 置为 true 且调用 scheduleTraversals 方法,于是在 Vsync 信号到来后会调用 performTraversals 方法。由于 mLayoutRequested == true,因此会依次执行 performMeasure, performLayout 以及 performDraw 方法开始 View 的绘制流程。
2、绘制过程
measure:
接下来看看 View.requestLayout 方法对整个 View 树的影响。首先看一下 View.measure 方法:
Java复制代码public final void measure(int widthMeasureSpec, int heightMeasureSpec) { // ... final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; 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) { // ... onMeasure(widthMeasureSpec, heightMeasureSpec); // 设置 PFLAG_LAYOUT_REQUIRED 标志位 mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; }}
在 View.requestLayout 方法中已经看到给当前 View 及其父 View 都添加了 PFLAG_FORCE_LAYOUT 标志位,因此其 forceLayout == ture,即会执行 onMeasure 方法测量。而对于未设置 PFLAG_FORCE_LAYOUT 标志位的 View 则需要判断其尺寸是否发生改变才会决定调用 onMeasure 与否。我们看到调用 onMeasure 后又设置了 PFLAG_LAYOUT_REQUIRED 标志位。
layout:
接着看 View.layout 方法:
Java复制代码public void layout(int l, int t, int r, int b) { boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); // ... } // ...}
由于调用 onMeasure 后设置了 PFLAG_LAYOUT_REQUIRED 标志位,因此也会跟着执行 onLayout 方法。另外看一下 setOpticalFrame 和 setFrame 方法,其中 setOpticalFrame 方法中最终也会调用到 setFrame 方法:
Java复制代码protected boolean setFrame(int left, int 较好, int right, int bottom) { boolean changed = false; if (mLeft != left || mRight != right || mTop != 较好 || mBottom != bottom) { int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; int newWidth = right - left; int newHeight = bottom - 较好; boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); invalidate(sizeChanged); // ... }}
因此可以看到当 View 四个顶点发生变化时也会调用 onLayout 方法,且会调用 View.invalidate 方法,并将 View 的宽高是否发生变化传给 invalidateCache 参数。
draw:
ViewRootImpl.performDraw 会调用到 ViewRootImpl.draw 方法:
Java复制代码private boolean draw(boolean fullRedrawNeeded) { final Rect dirty = mDirty; if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { // 硬件绘制/软件绘制 }}
dirty 是脏区,在 ViewRootImpl.invalidate 方法中会调用 mDirty.set() 方法为其设置边界值,如果上面 View 的顶点没有发生变化则不会调用 invalidate 方法,则 dirty.isEmpty()
返回 true,因此整个 View 树都不会重绘。
3、小结
调用 View.requestLayout 方法后会依次调用 performMeasure, performLayout 和 performDraw 方法,调用者 View 及其父 View 会从上往下重新进行 measure, layout 流程,一般情况下不会执行 draw 流程(子 View 会通过判断其尺寸/顶点是否发生改变而决定是否重新 measure/layout/draw 流程)。
三、invalidate方法介绍
1、View.invalidate
先看一下 invalidate 这个方法:
Java复制代码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) { if (skipInvalidate()) { // 判断是否需要跳过 invalidate return; } 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(); // 重新设置 Opaque mPrivateFlags &= ~PFLAG_DRAWN; // 移除 PFLAG_DRAWN 标志位 } mPrivateFlags |= PFLAG_DIRTY; // 设置 PFLAG_DIRTY 脏区标志位 if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; // 设置 PFLAG_INVALIDATED 标志位 mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; // 移除 PFLAG_DRAWING_CACHE_VALID 标志位 } final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if (p != null && ai != null && l < r && t < b) { // damage 表示要重绘的脏区 final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); p.invalidateChild(this, damage); } // ... }}private boolean skipInvalidate() { return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null && (!(mParent instanceof ViewGroup) || !((ViewGroup) mParent).isViewTransitioning(this));}
首先会通过 skipInvalidate 方法判断是否要跳过 invalidate 过程,如果同时满足以下条件则跳过:
View 不可见当前没有运行动画父 View 不是 ViewGroup 类型或者父 ViewGoup 不处于过渡态接下来再判断是否需要重绘,如果满足以下任意一个条件则进行重绘:
View 已经绘制完成且具有边界invalidateCache == true 且设置了 PFLAG_DRAWING_CACHE_VALID 标志位,即绘制缓存可用没有设置 PFLAG_INVALIDATED 标志位,即没有被重绘过fullInvalidate == true 且在 透明 和 不透明 之间发生了变化在处理了一些标志位的逻辑后调用了父 View 的 invalidateChild 方法并将要重绘的区域 damage 传给父 View。于是接着看 ViewGroup.invalidateChild 方法:
Java复制代码public final void invalidateChild(View child, final Rect dirty) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null && attachInfo.mHardwareAccelerated) { // 开启了硬件加速 onDescendantInvalidated(child, child); return; } // 未开启硬件加速 ViewParent parent = this; if (attachInfo != null) { // ... do { // ... parent = parent.invalidateChildInParent(location, dirty); // 重新设置脏区 // ... } while (parent != null); }}
可以看到这里会根据是否开启了硬件加速而走不同的逻辑。
2、小结
调用 View.invalidate() 方法后会逐级往上调用父 View 的相关方法,最终在 Choreographer 的控制下调用 ViewRootImpl.performTraversals() 方法。由于 mLayoutRequested == false,因此只有满足 mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null … 等条件才会执行 measure 和 layout 流程,否则只执行 draw 流程,draw 流程的执行过程与是否开启硬件加速有关:
关闭硬件加速则从 DecorView 开始往下的所有子 View 都会被重新绘制。开启硬件加速则只有调用 invalidate 方法的 View 才会重新绘制。View 在绘制后会设置 PFLAG_DRAWN 标志位。
延伸阅读1:Android中的View类简介
Android中的View类代表用户界面中基本的构建块。一个View在屏幕中占据一个矩形区域、并且负责绘制和事件处理。View是所有widgets的基础类,widgets是我们通常用于创建和用户交互的组件,比如按钮、文本输入框等等。子类ViewGroup是所有布局(layout)的基础类。layout是一个不看见的容器,里面堆放着其他的view或者ViewGroup,并且设置他们的布局属性。