invalidate和requestLayout区别
invalidate
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
最终调用的是invalidateInternal()。
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
if (skipInvalidate()) { //1
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();
mPrivateFlags &= ~PFLAG_DRAWN;
}
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
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);//2
}
......
}
}
1处首先会通过 skipInvalidate 方法判断是否要跳过 invalidate 过程,如果同时满足以下条件则跳过:
View 不可见
当前没有运行动画
父 View 不是 ViewGroup 类型或者父 ViewGoup 不处于过渡态
接下来再判断是否需要重绘,如果满足以下任意一个条件则进行重绘:
View 已经绘制完成且具有边界
invalidateCache == true 且设置了 PFLAG_DRAWING_CACHE_VALID 标志位,即绘制缓存可用没有设置 PFLAG_INVALIDATED 标志位,即没有被重绘过
fullInvalidate == true 且在 透明 和 不透明 之间发生了变化
在处理了一些标志位的逻辑后调用了父 View 的 invalidateChild 方法并将要重绘的区域 damage 传给父 View。于是接着看 2处。
2处一直往上调,最终会调到ViewRootImpl的invalidateChild()。
ViewRootImpl
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
//由前面可知,dirty != null且不为空
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
......
invalidateRectOnScreen(dirty);
return null;
}
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
先经过view的一层层调用,最后一直调用到ViewRootImpl的scheduleTraversals()。我们都知道,该方法最终会调用performTraversals(),而view的三大绘制流程就是在performTraversals()。其调用链为:scheduleTraversals()--》mTraversalRunnable--》doTraversal()--》performTraversals()。
我们都知道view的绘制流程如下:
performMeasure()--》measure()--》onMeasure()
performLayout() --> layout() --> onLayout()
performDraw() --> draw() --> onDraw()/dispatchDraw()
private void performTraversals() {
//measure
......
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
......
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
......
if (!mStopped || mReportNextDraw) {
......
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
......
}
//layout
......
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
//draw
......
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw) {
......
performDraw();
}
.....
}
上面是performMeasure,performLayout,performDraw三个流程的简化代码以及判定的条件。
首先我们来看走performMeasure()的条件:measureHierarchy()里面最终调用的是performMeasure()。
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
mLayoutRequested我们全局搜索发现在requestlayout()进行了赋值。而调用invalidate的时候没有对这个变量赋值,所以为false。
所以invalidate()不会走measureHierarchy()。所以在第一个判定条件下不会走performMeasure。
继续看第二个。
!mStopped || mReportNextDraw
mStopped与activity的生命周期有关,对应onStop,很明显mStopped == false,所以该判定条件为true。而第二个if,而只有满足上面的条件 —— mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null ... 才会调用 performMeasure 。
didLayout = layoutRequested && (!mStopped || mReportNextDraw);
由前面可知layoutRequested为false,所以不会走performLayout()。
cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
public final boolean dispatchOnPreDraw() {
boolean cancelDraw = false;
final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
if (listeners != null && listeners.size() > 0) {
CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
try {
int count = access.size();
for (int i = 0; i < count; i++) {
cancelDraw |= !(access.get(i).onPreDraw());
}
} finally {
listeners.end();
}
}
return cancelDraw;
}
由dispatchOnPreDraw()的注释可以知道,如果当前绘制应该被取消和重新调度,则为True,否则为false。
isViewVisible对应是否可见,所以可以得到cancelDraw为false。
则会走performDraw().
requestLayout
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;//1
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
这里重点看注释1,在后面有用。然后调用mParent.requestLayout(),和invalidate一样,一层层的调用,在ViewGroup没有实现这方法,所以直接看ViewRootImpl的requestLayout().
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
在requestLayout()里面先先检查线程,然后重点来了,将mLayoutRequested进行赋值,在介绍invalidate的时候我们知道,这个值影响是否进行performMeasure(),performLayout()的调用。然后又遇到这个熟悉的方法scheduleTraversals()。
private void performTraversals() {
//measure
......
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
......
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
......
if (!mStopped || mReportNextDraw) {
......
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
......
}
//layout
......
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
//draw
......
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw) {
......
performDraw();
}
.....
}
因为mLayoutRequested为true,mStopped为false,则!mStopped为true。则layoutRequested为true。讲一下mReportNextDraw这个值,默认为false,可以通过setReportNextDraw()进行赋值。
public void setReportNextDraw() {
reportNextDraw();
invalidate();
}
private void reportNextDraw() {
if (mReportNextDraw == false) {
drawPending();
}
mReportNextDraw = true;
}
然后进入measureHierarchy().
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
"Measuring " + host + " in display " + desiredWindowWidth
+ "x" + desiredWindowHeight + "...");
boolean goodMeasure = false;
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
+ ", desiredWindowWidth=" + desiredWindowWidth);
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
baseSize = (baseSize+desiredWindowWidth)/2;
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
+ baseSize);
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(mTag, "Good!");
goodMeasure = true;
}
}
}
}
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
return windowSizeMayChange;
}
对于这个方法可以重点看这篇文章 https://zhuanlan.zhihu.com/p/360405914
在这个方法里面会调用performMeasure();
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
然后调用measure()。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
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;//2
......
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;//3
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
因为在前面的注释1可知,mPrivateFlags设置为PFLAG_FORCE_LAYOUT,所以forceLayout为true.所以cacheIndex = -1,进入下面的那个if,从而走onMeasure(),然后将mPrivateFlags置为PFLAG_LAYOUT_REQUIRED。
然后第二步performlayout也会进去。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
......
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
performLayout中会调用layout();
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);
.......
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
......
}
在上面得注释3可以知道,走完onMeasure(),将mPrivateFlags置为PFLAG_LAYOUT_REQUIRED,所以changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED为true,然后走onLayout()。
最后吧PFLAG_FORCE_LAYOUT这个标志取消。所以走onMeasure一定会走onLayout。然后onDraw()的流程跟上面invalidate()分析的一样。但是很多的文献都说requestLayout不会走onDraw(),但是经过分析和测试打印,是会走onDraw()的。
文献引用: