Android源码分析requestLayout和invalidate的区别:作用范围不同。requestLayout() 用于通知 View 进行重新布局,而 invalidate() 用于通知 View 进行重绘,仅仅是在原有的尺寸和位置上重新绘制 View,不会重新进行测量和布局。
一、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;
}
}
// ViewRootImpl
boolean 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,并且设置他们的布局属性。