invalidate()和requestLayout()方法调用过程

来源:互联网 发布:怎么截图给淘宝客服 编辑:程序博客网 时间:2024/05/01 00:16

在上一篇博客中我们开始就提到了两组方法,其中scrollTo()和scrollBy()在上一篇博客中已经说的比较详细了,但是对于另一组invalidate()系列的方法只是说明他们重新调用draw方法绘制界面,并没有说明他们究竟是怎样一步一步实现对界面进行重绘的。在这篇博客中就为大家分析一下invalidate()系列方法重绘界面的过程,同时也说明另一个在自定义控件中会用到而且和的方法invalidate()相似的方法requestLayout()方法。

首先看到invalidate()方法,不管我们是自定义继承至View还是继承至ViewGroup的控件,invalidate()方法都是调用的View类中的,查看invalidate()方法源码:

public void invalidate() {invalidate(true);}
接着查看:
// 参数表示是否跳过无效的步骤// 如果一个控件的大小和内容都未发生改变,可以设置为false的表示跳过无效步骤// 使用了默认修饰符,只有同一个包下的才可以调用,在不同包中即使是子类也不可以调用,所以开发者不能调用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()) {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;}// 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);// 调用ViewParent的方法重新绘制指定区域p.invalidateChild(this, damage);}...}}
ViewParent是一个接口,我们查看它的实现类ViewRootImpl类中的invalidateChild()方法:
@Overridepublic void invalidateChild(View child, Rect dirty) {invalidateChildInParent(null, dirty);}

接着往下走,查看invalidateChildInParent(int[] location, Rect dirty)方法代码:

@Overridepublic ViewParent invalidateChildInParent(int[] location, Rect dirty) {// 检查线程是否为主线程,如果不是就抛出 CalledFromWrongThreadException 异常checkThread();if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);if (dirty == null) {// 没有指定范围,全部重绘invalidate();return null;} else if (dirty.isEmpty() && !mIsAnimating) {// 指定了矩形,但是矩形为空并且mIsAnimating为false,直接返回// mIsAnimating成员变量在ViewRootImpl类的draw()方法中会被设置成falsereturn null;}// 变化需要重新绘制的矩形区域if (mCurScrollY != 0 || mTranslator != null) {// 先保存需要重绘的矩形mTempRect.set(dirty);dirty = mTempRect;// 通过Rect的方法变化重新绘制的矩形if (mCurScrollY != 0) {dirty.offset(0, -mCurScrollY);}if (mTranslator != null) {mTranslator.translateRectInAppWindowToScreen(dirty);}if (mAttachInfo.mScalingRequired) {dirty.inset(-1, -1);}}// 调用方法invalidateRectOnScreen(dirty);return null;}
接着查看invalidateRectOnScreen()方法:
private void invalidateRectOnScreen(Rect dirty) {... // 对dirty做一些运算(并集、交集等)if (!mWillDrawSoon && (intersected || mIsAnimating)) {// 调用scheduleTraversals()方法scheduleTraversals();}}void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();// 使用Handler发送一个消息,执行mTraversalRunnable任务mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);...}}
查看mTraversalRunnable任务的定义以及所做的事情:
// 初始化mTraversalRunnable任务final TraversalRunnable mTraversalRunnable = new TraversalRunnable();// 在这个任务中调用了一个方法final class TraversalRunnable implements Runnable {@Overridepublic void run() {// 调用doTraversal()方法doTraversal();}}

最后我们查看一下任务所调用的doTraversal()方法,我们发现了一个很熟悉的方法performTraversals()方法,在《Android自定义View之View的绘制流程》博客中我们就说到了View树的绘制过程就是从这个方法开始的:

void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);if (mProfile) {Debug.startMethodTracing("ViewAncestor");}// 调用performTraversals()方法performTraversals();if (mProfile) {Debug.stopMethodTracing();mProfile = false;}}}
当追踪代码一步一步来到这里的时候,我们发现在调用invalidate()方法的时候,它会一步一步的往上请求调用,一直到ViewRootImpl中,最后通过Handler发送一个消息执行一个任务,在任务中通过调用performTraversals()方法实现对视图的重绘。


invalidate()方法说明白了,接着看一下postInvalidate()方法,在上一篇博客中说到了它的作用和invalidate()方法是一样的,只是可以在非UI线程中调用,下面就来看一下可以在非UI线程中调用的原因:

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 windowfinal AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {// 调用ViewRootImpl的dispatchInvalidateDelayed()方法attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);}}
我们看到这个方法是一个被public修饰符修饰的,所以开发者也可以调用,也就是说我们自己可以给定一个时间值,让重绘这个过程在指定的时间后开始执行,如:
postInvalidateDelayed(20);// 在20毫秒之后开始执行重新绘制的流程
说回来,我们接着查看ViewRootImpl的dispatchInvalidateDelayed()方法:
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);mHandler.sendMessageDelayed(msg, delayMilliseconds);}
这个方法的代码很简单,就是创建了一个what为 MSG_INVALIDATE 的 Message消息对象,然后通过Handler发送消息,消息发送完成之后,我们找到处理消息的地方,也就是在定义mHander的地方重写了handleMessage()方法处理各种消息,我们这里就只是贴出来what为 MSG_INVALIDATE  这个消息的处理代码:
@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_INVALIDATE:// 调用View的invalidate()方法((View) msg.obj).invalidate();break;... // 处理其他消息}}

我们发现,这个处理过程也很简单,就是调用了View的invalidate()方法,那后面的过程我们是不是已经明白了呢。

所以,postInvalidate()方法可以在非UI线程中调用的原因,是因为它使用Handler机制发送一个消息,然后回到UI线程调用View的invalidate()方法实现重绘的过程。


明白了invalidate()这一系列方法的调用过程,我们接下来说一下与它们有些类似的方法:requestLayout()方法,这个方法的作用是当我们控件的位置或者大小发生改变的时候请求重新布局。

和invalidate()方法一样,不管我们是自定义继承至View还是继承至ViewGroup的控件,requestLayout()方法都是调用的View类中的。

废话不多说,直接看代码,首先来看到View中的requestLayout()方法:

public void requestLayout() {// 清除测量缓存if (mMeasureCache != null) mMeasureCache.clear();...if (mParent != null && !mParent.isLayoutRequested()) {// 调用mParent的requestLayout(),调用的是实现类ViewRootImpl的requestLayout()方法mParent.requestLayout();}if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {mAttachInfo.mViewRequestingLayout = null;}}
他也是调用ViewParent接口的方法,所以看到实现类ViewRootImpl的requestLayout()方法:
@Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread(); // 检查线程mLayoutRequested = true; // 将请求重新布局的变量设置为true// 调用scheduleTraversals()方法,和invalidate()方法后面的流程一样了,// 最终调用了performTraversals()方法实现重新布局过程scheduleTraversals();}}
我们看到在这个方法中调用scheduleTraversals()方法,如果看了上面invalidate()方法的调用过程,那么对于这个方法就一定不会感到陌生了,所以后面的调用过程就不在重复了,忘了的话可以直接查看上面invalidate()方法的调用过程。


明白了invalidate()方法和requestLaout()方法的调用过程,他们到最后都调用了ViewRootImpl类中的performTraversals()方法实现重新布局或绘制的过程,最后说一下这两个方法的区别:

★  requestLayout()方法请求重新布局,会调用measure过程和layout过程,但不会调用draw过程,也不会重新绘制任何View包括该调用者本身。
★  invalidate()系列方法请求重绘视图(View树),如果View大小没有发生变化就不会调用measure和layout过程,相反,View的大小发生改变了就会调用measure和layout的过程,并且哪个View请求invalidate()系列方法,就绘制该View。


0 0
原创粉丝点击