您的位置:首页 > 产品设计 > UI/UE

深入理解 Android 之 View 的绘制流程(五)_invalidate,postInvalidate和requestLayout

2017-07-26 08:54 706 查看
上几篇文章里,我们分别介绍了View的三大工作流Measure,layout,draw。在分析源码的过程中我们会发现View的绘制流程还会受到其他方法的影响。比如:requestLayoutinvalidatepostInvalidate。下面我们来分别解析下这三个方法的不同调用。

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

@Override
public 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

@Override
public 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中一样,这里就不再做分析。

到这里我们就完成了requestLayoutinvalidatepostInvalidate三个方法的分析。

总结:

1:如果View确定自身不再适合当前区域,比如说它的LayoutParams发生了改变,需要父布局对其进行重新测量、布局时,这时就会使用requestLayout。(执行requestLayout时, 不会调用draw流程)

2:如果需要刷新当前View的内容,使当前View进行重绘,不会调用测量、布局流程,那么选择调用invalidate会比requestLayout更加高效。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: