欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android视图重绘,使用invalidate还是requestLayout

程序员文章站 2022-07-23 11:36:17
概述 在我们在进行自定义View的相关开发中,当我们更改了当前View的状态,比如大小,位置等,我们需要重新刷新整个界面,保证显示最新的状态。在Android中,让当前的视图重绘有两种方式,invalidate和requestLayout,今天我们看看这两种方式的原理以及区别。 分析 invalid ......

概述

在我们在进行自定义View的相关开发中,当我们更改了当前View的状态,比如大小,位置等,我们需要重新刷新整个界面,保证显示最新的状态。在Android中,让当前的视图重绘有两种方式,invalidate和requestLayout,今天我们看看这两种方式的原理以及区别。

分析

invalidate的原理

public void invalidate() {
        invalidate(true);
}

最后会调用到invalidateInternal这个方法

 1 void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
 2             boolean fullInvalidate) {
 3         if (mGhostView != null) {
 4             mGhostView.invalidate(true);
 5             return;
 6         }
 7 
 8         if (skipInvalidate()) {
 9             return;
10         }
11 
12         if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
13                 || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
14                 || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
15                 || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
16             if (fullInvalidate) {
17                 mLastIsOpaque = isOpaque();
18                 mPrivateFlags &= ~PFLAG_DRAWN;
19             }
20 
21             mPrivateFlags |= PFLAG_DIRTY;
22 
23             if (invalidateCache) {
24                 mPrivateFlags |= PFLAG_INVALIDATED;
25                 mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
26             }
27 
28             // Propagate the damage rectangle to the parent view.
29             final AttachInfo ai = mAttachInfo;
30             final ViewParent p = mParent;
31             if (p != null && ai != null && l < r && t < b) {
32                 final Rect damage = ai.mTmpInvalRect;
33                 damage.set(l, t, r, b);
34                 p.invalidateChild(this, damage);
35             }
36             .....

我们看到方法的最后调用了ViewParent的invalidateChild方法,因为ViewParent是个接口,invalidateChild是空实现,我们去看看它的实现类ViewRootImpl中的invalidateChild是如何做的

 @Override
    public void invalidateChild(View child, Rect dirty) {
        invalidateChildInParent(null, dirty);
    }

 

 1  @Override
 2     public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
 3         checkThread();
 4         if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
 5 
 6         if (dirty == null) {
 7             invalidate();
 8             return null;
 9         } else if (dirty.isEmpty() && !mIsAnimating) {
10             return null;
11         }
12 
13         if (mCurScrollY != 0 || mTranslator != null) {
14             mTempRect.set(dirty);
15             dirty = mTempRect;
16             if (mCurScrollY != 0) {
17                 dirty.offset(0, -mCurScrollY);
18             }
19             if (mTranslator != null) {
20                 mTranslator.translateRectInAppWindowToScreen(dirty);
21             }
22             if (mAttachInfo.mScalingRequired) {
23                 dirty.inset(-1, -1);
24             }
25         }
26 
27         invalidateRectOnScreen(dirty);
28 
29         return null;
30     }

又会调用ViewRootImpl中的invalidate方法

void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
            scheduleTraversals();
        }
    }

这里调用了scheduleTraversals重新开始了View的绘制,我们知道View的绘制是从ViewRootImpl的performTraversals方法开始的。我们看看scheduleTraversals是不是触发了performTraversals。

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

在scheduleTraversals方法中我们发现了一个mTraversalRunnable对象,这个对象就是我们要观察的重点

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

我们发现这个对象就是一个Runnable对象,我们在scheduleTraversals方法中传入mTraversalRunnable 就会执行run方法,其中又调用了doTraversal这个方法

 void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

最后我们发现在doTraversal方法中调用了performTraversals开始了View的重新绘制,这就是invalidate的整个过程。

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;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

其中会调用ViewParent的requestLayout方法,同样,我们去看ViewRootImpl中的requestLayout方法。

Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

这里调用了scheduleTraversals,后面的步骤就和上面invalidate时一样了。相对来说,requestLayout的流程还是比较简单的。

区别

既然两种方式都可以完成View的重绘,那么有什么区别呢? 
使用invalidate重绘当前视图是不会再次执行measure和layout流程的。因为视图没有强制重新测量的标志位,而且大小也没有发生过变化,所以这时只有draw流程可以得到执行。 
如果你希望视图的绘制流程可以完完整整地重新走一遍,就不能使用invalidate()方法,而应该调用requestLayout()了

Android视图重绘,使用invalidate还是requestLayout