深入理解 Android 之 View 的绘制流程(五)_invalidate,postInvalidate和requestLayout
来源:互联网 发布:更改手机图标软件 编辑:程序博客网 时间:2024/06/05 08:33
上几篇文章里,我们分别介绍了View的三大工作流Measure,layout,draw。在分析源码的过程中我们会发现View的绘制流程还会受到其他方法的影响。比如:requestLayout,invalidate,postInvalidate。下面我们来分别解析下这三个方法的不同调用。
requestLayout的源码分析
View#requestLayout
/*** view的layout发生改变的时候调用该方法。* 如果当前View在请求布局的时候,View树正在进行布* 局流程时,该请求会延迟到布局流程完成后或者绘制* 流程完成且下一次布局发现的时候再执行。*/public void requestLayout() { //清除缓存的测量值,以便可以重新执行onMeasure 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; } //设置view的标志位:PFLAG_FORCE_LAYOUT mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { //向父容器请求布局,最终到达ViewRootImpl的requestLayout mParent.requestLayout(); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; }}
可以看到执行方法requestLayout后,首先会清除掉测量缓存中保存的数据。然后判断当前View树是否正在布局流程,接着就为当前View设置标记位。最后向父容器请求布局,调用父容器的requestLayout方法。而父容器会又会调用它的父容器的requestLayout方法,直到ViewRootImpl中。在ViewRootImpl中重写了requestLayout的方法。
ViewRootImpl#requestLayout
@Overridepublic void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }}
方法中,调用了scheduleTraversals,在前面文章中,我们知道该方法是后面会调用performTraversals方法,这是开始View工作流的核心方法。从这里开始分别调用了measure,layout,draw方法。我们这里再回顾之前的measure方法:
View#measure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ..... //实例化一个对象用来保存测量的值 if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); //判断mPrivateFlags标记为:PFLAG_FORCE_LAYOUT if ((mPrivateFlags & PFLAG_FORCE_LAPFLAG_FORCE_LAYOUTYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { ..... int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } ..... mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; .....}
从上面可以看出,判断View标志位为:PFLAG_FORCE_LAYOUT,这时才会进入方法onMeasure开始测量。完成测量之后,再次设置View标志位为:PFLAG_LAYOUT_REQUIRED,这个标志作用于View的layout的流程中,标志了状态后才会进行layout流程。下面我们再看layout的源码:
View#layout
public void layout(int l, int t, int r, int b) { ..... //判断标记位为:PFLAG_LAYOUT_REQUIRED if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); //清除PFLAG_LAYOUT_REQUIRED标记位 mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size(); for (int i = 0; i < numListeners; ++i) { listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); } } } //清除requestLayout中设置的PFLAG_FORCE_LAYOUT标记位 mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }
所以到这里,requestLayout的流程便完成了。
总结:
View调用requestLayout后,首先会标记当前View和父容器的mPrivateFlags,然后到ViewRootImpl中。通过ViewRootImpl调用requestLayout方法,开始执行View的measure和layout流程,不会调用draw流程。
invalidate源码分析
invalidate主要是用来让View树进行重绘,如果需要刷新View的当前界面时在UI Thread中调用它,非UI Thread 中调用postInvalidate方法。
View#invalidate
public void invalidate() { invalidate(true);}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 (mGhostView != null) { mGhostView.invalidate(true); return; } //判断view状态是否可见,是否处于动画中 if (skipInvalidate()) { return; } //判断mPrivateFlags标志是否是需要重绘,如果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; } //设置mPrivateFlags标志为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); } ..... }}
从上面可以看到调用invalidate后,最终调用的是invalidateInternal方法。在该方法中首先判断View的mPrivateFlags是否需要重绘,如果是的话,接着为View设置标志位:PFLAG_DIRTY,然后把需要绘制的区域传递给父容器,调用invalidateChild的方法。
ViewGroup#invalidateChild
public final void invalidateChild(View child, final Rect dirty) { ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { ..... if (child.mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } //保存child的left和top的值 final int[] location = attachInfo.mInvalidateChildLocation; location[CHILD_LEFT_INDEX] = child.mLeft; location[CHILD_TOP_INDEX] = child.mTop; ..... do { View view = null; if (parent instanceof View) { view = (View) parent; } ..... // If the parent is dirty opaque or not dirty, mark it dirty with the opaque // flag coming from the child that initiated the invalidate if (view != null) { if ((view.mViewFlags & FADING_EDGE_MASK) != 0 && view.getSolidColor() == 0) { opaqueFlag = PFLAG_DIRTY; } if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) { //对当前View的标记位进行设置 view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag; } } //调用ViewGrup的invalidateChildInParent,如果已经达到最顶层view,则调用ViewRootImpl的invalidateChildInParent。 parent = parent.invalidateChildInParent(location, dirty); if (view != null) { // Account for transform on current parent Matrix m = view.getMatrix(); if (!m.isIdentity()) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); m.mapRect(boundingRect); dirty.set((int) (boundingRect.left - 0.5f), (int) (boundingRect.top - 0.5f), (int) (boundingRect.right + 0.5f), (int) (boundingRect.bottom + 0.5f)); } } } while (parent != null); }}
该方法中设置先设置当前标志位,然后在do while 循环中执行ViewGroup中的invalidateChildInParent方法。
ViewGroup#invalidateChildInParent
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN || (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) { if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) != FLAG_OPTIMIZE_INVALIDATE) { //将dirty中的坐标转化为父容器中的坐标,考虑mScrollX和mScrollY的影响 dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX, location[CHILD_TOP_INDEX] - mScrollY); if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) { //求并集,结果是把子视图的dirty区域转化为父容器的dirty区域 dirty.union(0, 0, mRight - mLeft, mBottom - mTop); } final int left = mLeft; final int top = mTop; if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) { dirty.setEmpty(); } } mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; //记录当前视图的mLeft和mTop值,在下一次循环中会把当前值再向父容器的坐标转化 location[CHILD_LEFT_INDEX] = left; location[CHILD_TOP_INDEX] = top; if (mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; } //返回当前视图的父容器 return mParent; } else { mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID; location[CHILD_LEFT_INDEX] = mLeft; location[CHILD_TOP_INDEX] = mTop; if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { dirty.set(0, 0, mRight - mLeft, mBottom - mTop); } else { // in case the dirty rect extends outside the bounds of this container dirty.union(0, 0, mRight - mLeft, mBottom - mTop); } if (mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; } return mParent; } } return null;}
在do while中不断循环该方法,最后都返回当前View的父容器,最后会调用到ViewRootImpl的invalidateChildInParent方法。
ViewRootImpl#invalidateChildInParent
@Overridepublic ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty); if (dirty == null) { invalidate(); return null; } else if (dirty.isEmpty() && !mIsAnimating) { return null; } if (mCurScrollY != 0 || mTranslator != null) { mTempRect.set(dirty); dirty = mTempRect; if (mCurScrollY != 0) { dirty.offset(0, -mCurScrollY); } if (mTranslator != null) { mTranslator.translateRectInAppWindowToScreen(dirty); } if (mAttachInfo.mScalingRequired) { dirty.inset(-1, -1); } } invalidateRectOnScreen(dirty); return null;}private void invalidateRectOnScreen(Rect dirty) { final Rect localDirty = mDirty; if (!localDirty.isEmpty() && !localDirty.contains(dirty)) { mAttachInfo.mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } // Add the new dirty rect to the current one localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); // Intersect with the bounds of the window to skip // updates that lie outside of the visible region 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(); }}
从上可以看到invalidateChildInParent方法中,调用了方法invalidateRectOnScreen,而在invalidateRectOnScreen中又执行了scheduleTraversals。后面的流程就和requestLayout的中一样了。都是通过View的标记位,不断的刷新父容器需要重绘的区域,知道传递到ViewRootImpl中,最终还是执行performTraversals方法。然后整个View树重新开始按照上面分析的View绘制流程进行重绘任务。
postInvalidate源码分析
在分析invalidate中我们说过,invalidate方法主要是执行在UI Thread中,而postInvalidate用于在其他线程中执行。我们来分析这个方法:
View#postInvalidate
public void postInvalidate() { postInvalidateDelayed(0);}public void postInvalidateDelayed(long delayMilliseconds) { // 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) { attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); }}
从上面分析,View执行postInvalidate时,调用的是postInvalidateDelayed的方法。而该方法中调用的是ViewRootImpl中的dispatchInvalidateDelayed方法。所有我们去ViewRootImpl类中查看该方法。
ViewRootImpl#dispatchInvalidateDelayed
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds);}.....final ViewRootHandler mHandler = new ViewRootHandler();.....final class ViewRootHandler extends Handler { @Override public String getMessageName(Message message) { ..... return super.getMessageName(message); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; ..... }}
从上面可以知道,原来在dispatchInvalidateDelayed方法中使用的handler来处理线程间的通讯,这样就避免了在子线程中进行刷新UI的操作。在handler中的handleMessage方法里,调用了当前View的invalidate,之后的流程与直接调用invalidate中一样,这里就不再做分析。
到这里我们就完成了requestLayout,invalidate,postInvalidate三个方法的分析。
总结:
1:如果View确定自身不再适合当前区域,比如说它的LayoutParams发生了改变,需要父布局对其进行重新测量、布局时,这时就会使用requestLayout。(执行requestLayout时, 不会调用draw流程)
2:如果需要刷新当前View的内容,使当前View进行重绘,不会调用测量、布局流程,那么选择调用invalidate会比requestLayout更加高效。
- 深入理解 Android 之 View 的绘制流程(五)_invalidate,postInvalidate和requestLayout
- Android绘制中requestLayout,invalidate和postInvalidate的异同
- 深入理解 Android 之 View 的绘制流程
- 深入理解 Android 之 View 的绘制流程(一)
- 深入理解 Android 之 View 的绘制流程(二)_Measure
- 深入理解 Android 之 View 的绘制流程(三)_Layout
- 深入理解 Android 之 View 的绘制流程(四)_Draw
- 深入理解 Android 之 View 的绘制流程
- 深入理解Android之View的绘制流程
- Android 深入理解 View 的绘制流程和机制
- 自定义View重绘使requestLayout, invalidate和postInvalidate的异同
- Android invalidate() 、postinvalidate()和requestLayout()
- Android Custom View ---->invalidate() 、postInvalidate() and requestLayout()
- Android View 分析requestLayout、invalidate与postInvalidate
- View 中requestLayout 和 invalidate,postinvalidate() 区别
- Android中Invalidate和postInvalidate和requestLayout的区别
- Android中Invalidate和postInvalidate和requestLayout的区别
- Android中Invalidate和postInvalidate和requestLayout的区别
- POJ 2456 Aggressive cows<二分贪心>
- 7.26
- 关于HttpSession前台无值后台有值的解决方法
- config.properties的配置
- Session和Cookie
- 深入理解 Android 之 View 的绘制流程(五)_invalidate,postInvalidate和requestLayout
- jquery的鼠标访问事件
- MySQL not launching on XAMPP
- java设计模式总共有23种设计模式
- 记Dorado7学习(1)
- 百度编辑器ueditor上传图片顺序乱掉修改方法
- KVM地址翻译流程及EPT页表的建立过程
- JAVA中内部类的继承和覆盖问题
- 编写可维护的javascript(二):注释