Android(安卓)View相关-View的常用方法及使用区别
经过上一章的摸索,我们已经了解了Android中View的绘制流程分别是measure、layout和draw,那么对Android有一些了解的话,一定知道View中有这样几个方法invalidate、postInvalidate以及requestLayout,我们知道这些方法调用后会触发View的重绘(不一定正确的说法),那么它们的用法是什么,有什么区别以及使用时候有哪些注意事项,这就是我们这一篇文章力求弄明白的东西,好了,废话不多说,开始今天的分析。
invalidate
我们从View源码中追本溯源,可以看到在View中方法的调用情况是这样的:
/** * 使当前View失效,若View是可见的那么onDraw方法会在未来的某个时间点被调用 * 该方法必须在UI线程被调用,若需要在非UI线程调用,调用postInvalidate */public void invalidate() { invalidate(true);}/** * 这个方法是invalidate整整产生作用的地方,一个invalidate请求会将绘制缓存设为失效 * 不过这个方式可以设置参数为false来禁止绘制缓存失效 */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 ((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)) { // 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); //调用父控件的invalidateChild方法 p.invalidateChild(this, damage); } ... }}
从这里可以看出invalidate方法的作用是令当前View的绘制缓存失效,并且会调用onDraw方法,不过我们并没有在上述代码中看到onDraw(注意invalidate方法只能在UI线程中调用,工作线程调用该方法会报错),我们知道该方法最终调用了父控件的invalidateChild方法,那么我们来查看下ViewGroup中的invalidateChild:
/** * 不要主动调用该方法 */public final void invalidateChild(View child, final Rect dirty) { ViewParent parent = this; ... do { ... 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.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag; } } parent = parent.invalidateChildInParent(location, dirty); } while (parent != null); }}
该方法执行了一段循环代码,这里层层调用父控件的invalidateChildInParent方法,知道返回值为空,我们知道绘制的起点是在ViewRootImpl中,那么打开ViewRootImpl,查看其invalidateChildInParent代码:
@Overridepublic ViewParent invalidateChildInParent(int[] location, Rect dirty) { invalidateRectOnScreen(dirty); return null;}private void invalidateRectOnScreen(Rect dirty) { if (!mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); }}
这里在返回null之后上述循环终止,我们注意到在循环终止前ViewRootImpl调用了scheduleTraversals方法,这个方法跟我们之前讲过的performTraversals方法很像,我们来看看里面有什么:
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //这行代码在Choreographer类内部使用Handler机制最终调用了doTraversal方法 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } //调用此方法判断是否需要重新测量放置以及绘制 performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } }}
可以看到在绕了一圈之后,ViewRootImpl最终会调用我们上篇讲到的performTraversals方法,重复判断流程。至此,View的invalidate方法执行流程完毕。
postInvalidate
我们在前面的注释里可以看出,invalidate方法是不可以在非UI线程执行的,而postInvalidate则可以,结合我们之前学习的Handler知识,我们猜想可能是使用了handler机制,最终仍然是调用了invalidate,那我们来源码中验证一下我们的猜想:
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); }}public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds);}@Overridepublic void handleMessage(Message msg) { switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; }}
可以看到,跟我们猜想的一样,经过一系列列调用,最后使用Handler机制,调用了invalidate方法,也就是说这两个方法区别基本只在线程之间。
requestLayout
requestLayout也是我么View中一个很重要的方法,我们在之前讲绘制流程的时候也有看到这个方法,那么我们来探究一下它究竟做了什么:
public void requestLayout() { ... if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } ...}
这方法与之前invalidateInternal方法类似,也是一层层调用父控件的requestLayout方法,直到ViewRootImpl(ViewGroup没有复写该方法,这里不再关注):
@Overridepublic void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }}
又看到了熟悉的scheduleTraversals方法,这里跟之前invalidate的区别仅仅只是标志位可能有所改变,后续流程一致。
小结
一般引起invalidate调用的情景如下:
- 调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
- 调用setSelection()方法 :请求重新draw(),但只会绘制调用者本身。
- 调用setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE\VISIBLE 转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要“重新绘制”的视图。
- 调用setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。
- 调用requestFocus方法。请求View树的draw过程,只绘制“需要重绘”的View。
- invalidate方法只能在UI线程调用,而postInvalidate可以在工作线程调用,不过invalidate效率更高(显然)
- requestLayout()方法会调用measure过程和layout过程,不会调用draw过程,也不会重新绘制任何View包括该调用者本身
好了,这就是本篇的全部内容了,这里还是偏理论的东西较多,掌握脉络即可。下一篇文章我们会来聊一聊Android中的事件分发机制,敬请期待~
如果您觉得文章不错,可以来我的个人博客查看更多内容~
更多相关文章
- Android中Handler小例子
- Android(安卓)C调用Java
- Android(安卓)使用RxJava+Retrofit +Realm 组合加载数据 (二)
- android学习笔记1-android介绍以及学习方法
- android 常用的调试方法
- Android中的Context几种获取方法和区别
- Android日期对话框NumberPicker的用法教程
- Android(三):远程Service基本操作
- Android(安卓)自定义线程池