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

来源:互联网 发布:更改手机图标软件 编辑:程序博客网 时间:2024/06/05 08:33

上几篇文章里,我们分别介绍了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

@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中一样,这里就不再做分析。
到这里我们就完成了requestLayoutinvalidatepostInvalidate三个方法的分析。

总结:

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

阅读全文
0 0
原创粉丝点击