0%

Android-requestLayout与invalidate方法解析

概述

注:本文基于Android 10源码,为了文章的简洁性,引用源码的地方可能有所删减。

在更新 View 时我们常用到 requestLayout 和 invalidate 这两个方法,本文会根据源码分析一下这两个方法的区别和工作逻辑。在开始阅读之前可以先看看 Android-View绘制原理Android-Choreographer工作原理 对 Android View 绘制流程有一个大概的理解。

VRImpl.performTraversals

无论是 requestLayout 还是 invalidate 方法最后都会调用到 ViewRootImpl.performTraversals 方法开始 View 的更新,因此先看一下这个方法的部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void performTraversals() {
// DecorView 实例
final View host = mView;
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
// 此处可能会调用 performMeasure 方法
windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight);
}
if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
// ...
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 如果 lp.horizontalWeight 或 lp.verticalWeight 大于 0 则重新调用 performMeasure 测量
// ...
layoutRequested = true;
}
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
performDraw();
}
}

这个方法贼长,抽取关键部分可以大致明白 performTraversals 里会根据一些判断条件来执行 View 的 Measure, Layout, Draw 三大流程。

invalidate

View.invalidate

先看一下 invalidate 这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}
}

可以看到这里会根据是否开启了硬件加速而走不同的逻辑。

软件绘制

关闭硬件加速时会循环调用 parent.invalidateChildInParent 方法并将返回值赋给 parent 直到其为 null 时退出循环,于是可以看看 ViewGroup.invalidateChildInParent 方法:

1
2
3
4
5
6
7
8
9
10
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
// 处理运算 dirty 区域
// ...
// 移除 PFLAG_DRAWING_CACHE_VALID 标志位
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
return mParent;
}
return null;
}

invalidateChildInParent 方法主要是对子 View 传递的 dirty 区域进行处理与运算并返回 mParent 对象。因此在 invalidateChild 方法中会循环逐层调用父 View 的 invalidateChildInParent 方法,最终来到顶层的 DecorView.invalidateChild 方法,其 mParent 是 ViewRootImpl, 根据 Android-Window机制源码解读 可以重新看看 ViewRootImpl.setView 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) { // 这里的 view 即是 DecorView 对象
if (mView == null) {
mView = view;
// ...
requestLayout();
// ...
view.assignParent(this);
}
}
}

// View
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but" + " it already has a parent");
}
}

因此可以看出 DecorView 的 mParent 就是 ViewRootImpl 对象,于是再看看 ViewRootImpl.invalidateChildInParent 方法:

1
2
3
4
5
6
7
8
9
10
11
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread(); // 检查当前线程是否是主线程
// ...
invalidateRectOnScreen(dirty);
return null;
}

private void invalidateRectOnScreen(Rect dirty) {
// ...
scheduleTraversals();
}

可以看到 ViewRootImpl.invalidateChildInParent 方法返回 null,因此 ViewGroup.invalidateChild 会退出循环。至于 scheduleTraversals 方法应该很熟悉了,在 Vsync 信号到来后便会执行 performTraversals 方法(参考 Android-Choreographer工作原理),由于 mLayoutRequested 默认值是 false 且 invalidate 过程并没有给它赋值,于是不会调用 measureHierarchy 方法,而只有满足上面的条件 —— mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null ... 才会调用 performMeasure 和 performLayout,否则只会调用 performDraw 去重新 draw,并在其中给 View 设置 PFLAG_DRAWN 标志位。

硬件绘制

开启了硬件绘制后会走 ViewGroup.onDescendantInvalidated 方法:

1
2
3
4
5
6
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
// ...
if (mParent != null) {
mParent.onDescendantInvalidated(this, target);
}
}

和软件绘制类似,接下来会逐级调用父 View 的 onDescendantInvalidated 方法,最后走到 ViewRootImpl.onDescendantInvalidated 方法:

1
2
3
4
5
6
7
8
9
10
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
invalidate();
}

void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}

可以看到跟软件绘制类似调用了 scheduleTraversals 方法,在 Vsync 信号到来后执行 performTraversals 方法,由于 mLayoutRequested 默认值是 false 且 invalidate 过程并没有给它赋值,于是不会调用 measureHierarchy 方法,而只有满足上面的条件 —— mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null ... 才会调用 performMeasure 和 performLayout,否则只会调用 performDraw 去重新 draw。

根据之前 Android-Surface原理解析及软硬件绘制 的解析,开启硬件加速后 performDraw 方法会通过 mAttachInfo.mThreadedRenderer.draw 来绘制,接着调用到 ThreadedRenderer.updateRootDisplayList 方法:

1
2
3
4
5
6
7
8
9
10
11
12
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
updateViewTreeDisplayList(view);
// ...
}

private void updateViewTreeDisplayList(View view) {
view.mPrivateFlags |= View.PFLAG_DRAWN;
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) == View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
view.updateDisplayListIfDirty();
view.mRecreateDisplayList = false;
}

注意到在调用 View.invalidate 方法时对该 View 设置了 PFLAG_INVALIDATED 标志位,因此调用 invalidate 方法的 View 的 mRecreateDisplayList 属性为 true,其它 View 为 false。接下来会调用到 view.updateDisplayListIfDirty 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public RenderNode updateDisplayListIfDirty() {
// 调用 invalidate 时 PFLAG_DRAWING_CACHE_VALID 标志位已经被清除,因此该条件为 true
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || !renderNode.isValid() || (mRecreateDisplayList)) {
if (renderNode.isValid() && !mRecreateDisplayList) {
// 不需要重绘
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
return renderNode; // no work needed
}

// 需要重绘
mRecreateDisplayList = true;
// 重绘...
}
return renderNode;
}

这里根据 mRecreateDisplayList 的值会判断是走 重绘 还是 dispatchGetDisplayList 的逻辑:

  • 对于调用 View.invalidate 方法的 View 来说其 mRecreateDisplayList 值为 true,因此走重绘逻辑。
  • 其余 View 如 DecorView 等会走 dispatchGetDisplayList 逻辑。

最开始的根 View 是 DecorView 类型,来看看 dispatchGetDisplayList 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void dispatchGetDisplayList() {
for (int i = 0; i < count; i++) {
final View child = children[i];
if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
recreateChildDisplayList(child);
}
}
// ...
}

private void recreateChildDisplayList(View child) {
child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
child.mPrivateFlags &= ~PFLAG_INVALIDATED;
child.updateDisplayListIfDirty();
child.mRecreateDisplayList = false;
}

可以看到 recreateChildDisplayList 和 updateViewTreeDisplayList 方法有些类似,都会设置 mRecreateDisplayList 的值,于是从 DecorView 开始会遍历其子 View 依次调用 updateDisplayListIfDirty 方法,对于没有设置 PFLAG_INVALIDATED 标志位的 View 会调用 dispatchGetDisplayList 接着往下分发,而设置了 PFLAG_INVALIDATED 标志位的 View 则会执行重绘逻辑,根据 Android-View绘制原理 可知当该 View 执行 View.draw 开始重绘时,如果它是 ViewGroup 类型,则会调用 ViewGroup.dispatchDraw 方法分发 draw 事件,dispatchDraw 内部遍历子 View 然后调用 drawChild 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ViewGroup
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}

// View
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
// 是否是硬件加速
boolean drawingWithRenderNode = mAttachInfo != null && mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas;
if (drawingWithRenderNode) {
// 又会重复上面的逻辑,对于没有设置 PFLAG_INVALIDATED 标志位的 View 不会重绘
renderNode = updateDisplayListIfDirty();
}
// ...
}

小结

调用 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 标志位。

requestLayout

View.requestLayout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;
}
}

// ViewRootImpl
boolean requestLayoutDuringLayout(final View view) {
if (!mLayoutRequesters.contains(view)) {
mLayoutRequesters.add(view);
}
// ...
}

如果此时处于 Layout 则将该请求加入 ViewRootImpl 中的任务队列中,否则向上调用父 View 的 requestLayout 方法,直到 ViewRootImpl 中:

1
2
3
4
5
6
7
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}

ViewRootImpl.requestLayout 方法在 check 了线程后将 mLayoutRequested 置为 true 且调用 scheduleTraversals 方法,于是在 Vsync 信号到来后会调用 performTraversals 方法。由于 mLayoutRequested == true,因此会依次执行 performMeasure, performLayout 以及 performDraw 方法开始 View 的绘制流程。

绘制过程

measure

接下来看看 View.requestLayout 方法对整个 View 树的影响。首先看一下 View.measure 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 方法:

1
2
3
4
5
6
7
8
9
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 方法:

1
2
3
4
5
6
7
8
9
10
11
12
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
invalidate(sizeChanged);
// ...
}
}

因此可以看到当 View 四个顶点发生变化时也会调用 onLayout 方法,且会调用 View.invalidate 方法,并将 View 的宽高是否发生变化传给 invalidateCache 参数。

draw

ViewRootImpl.performDraw 会调用到 ViewRootImpl.draw 方法:

1
2
3
4
5
6
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 树都不会重绘。

小结

调用 View.requestLayout 方法后会依次调用 performMeasure, performLayout 和 performDraw 方法,调用者 View 及其父 View 会从上往下重新进行 measure, layout 流程,一般情况下不会执行 draw 流程(子 View 会通过判断其尺寸/顶点是否发生改变而决定是否重新 measure/layout/draw 流程)。

示例

代码

接下来看一个示例,首先自定义两个 ViewGroup:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyViewGroup1(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
constructor(context: Context?) : this(context, null, 0)
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
Log.d("LLL", "MyViewGroup1 -- onMeasure")
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
Log.d("LLL", "MyViewGroup1 -- onLayout")
super.onLayout(changed, l, t, r, b)
}

override fun onDraw(canvas: Canvas?) {
Log.d("LLL", "MyViewGroup1 -- onDraw")
super.onDraw(canvas)
}
}

MyViewGroup2 跟 MyViewGroup1 的代码类似。然后自定义一个 View:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : View(context, attrs, defStyleAttr) {

constructor(context: Context?) : this(context, null, 0)
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
Log.d("LLL", "MyView -- onMeasure")
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
Log.d("LLL", "MyView -- onLayout")
super.onLayout(changed, left, top, right, bottom)
}

override fun onDraw(canvas: Canvas?) {
Log.d("LLL", "MyView -- onDraw")
super.onDraw(canvas)
}
}

接下来看一下布局文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<com.hearing.demo.MyViewGroup1 xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000"
android:onClick="group1">

<com.hearing.demo.MyViewGroup2
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#00FF00"
android:onClick="group2">

<com.hearing.demo.MyView
android:layout_width="150dp"
android:layout_height="150dp"
android:background="#0000FF"
android:onClick="view1"/>
</com.hearing.demo.MyViewGroup2>
</com.hearing.demo.MyViewGroup1>

给这三个控件添加点击事件,分别调用其 invalidate 和 requestLayout 方法。

invalidate

开启硬件加速

1
2
3
4
5
6
7
8
// 点击 MyViewGroup1 
D/LLL: MyViewGroup1 -- onDraw

// 点击 MyViewGroup2
D/LLL: MyViewGroup2 -- onDraw

// 点击 MyView
D/LLL: MyView -- onDraw

关闭硬件加速

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 点击 MyViewGroup1 
D/LLL: MyViewGroup1 -- onDraw
D/LLL: MyViewGroup2 -- onDraw
D/LLL: MyView -- onDraw

// 点击 MyViewGroup2
D/LLL: MyViewGroup1 -- onDraw
D/LLL: MyViewGroup2 -- onDraw
D/LLL: MyView -- onDraw

// 点击 MyView
D/LLL: MyViewGroup1 -- onDraw
D/LLL: MyViewGroup2 -- onDraw
D/LLL: MyView -- onDraw

requestLayout

开启硬件加速

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 点击 MyViewGroup1
D/LLL: MyViewGroup1 -- onMeasure
D/LLL: MyViewGroup1 -- onLayout

// 点击 MyViewGroup2
D/LLL: MyViewGroup1 -- onMeasure
D/LLL: MyViewGroup2 -- onMeasure
D/LLL: MyViewGroup1 -- onLayout
D/LLL: MyViewGroup2 -- onLayout

// 点击 MyView
D/LLL: MyViewGroup1 -- onMeasure
D/LLL: MyViewGroup2 -- onMeasure
D/LLL: MyView -- onMeasure
D/LLL: MyViewGroup1 -- onLayout
D/LLL: MyViewGroup2 -- onLayout
D/LLL: MyView -- onLayout

关闭硬件加速

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 点击 MyViewGroup1
D/LLL: MyViewGroup1 -- onMeasure
D/LLL: MyViewGroup1 -- onLayout

// 点击 MyViewGroup2
D/LLL: MyViewGroup1 -- onMeasure
D/LLL: MyViewGroup2 -- onMeasure
D/LLL: MyViewGroup1 -- onLayout
D/LLL: MyViewGroup2 -- onLayout

// 点击 MyView
D/LLL: MyViewGroup1 -- onMeasure
D/LLL: MyViewGroup2 -- onMeasure
D/LLL: MyView -- onMeasure
D/LLL: MyViewGroup1 -- onLayout
D/LLL: MyViewGroup2 -- onLayout
D/LLL: MyView -- onLayout

总结

invalidate

调用 View.invalidate() 方法后会逐级往上调用父 View 的相关方法,最终在 Choreographer 的控制下调用 ViewRootImpl.performTraversals() 方法。只有满足 mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null ... 等条件才会执行 measure 和 layout 流程,否则只执行 draw 流程,draw 流程的执行过程与是否开启硬件加速有关:

  • 关闭硬件加速则从 DecorView 开始往下的所有子 View 都会被重新绘制。
  • 开启硬件加速则只有调用 invalidate 方法的 View 才会重新绘制。

requestLayout

调用 View.requestLayout 方法后会依次调用 performMeasure, performLayout 和 performDraw 方法,调用者 View 及其父 View 会重新从上往下进行 measure, layout 流程,一般情况下不会执行 draw 流程(子 View 会通过判断其尺寸/顶点是否发生改变而决定是否重新 measure/layout/draw 流程)。

因此,当只需要进行重绘时可以使用 invalidate 方法,如果需要重新测量和布局则可以使用 requestLayout 方法,而 requestLayout 方法不一定会重绘,因此如果要进行重绘可以再手动调用 invalidate 方法。