经过上一章的摸索,我们已经了解了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中的事件分发机制,敬请期待~
如果您觉得文章不错,可以来我的个人博客查看更多内容~

更多相关文章

  1. Android中Handler小例子
  2. Android(安卓)C调用Java
  3. Android(安卓)使用RxJava+Retrofit +Realm 组合加载数据 (二)
  4. android学习笔记1-android介绍以及学习方法
  5. android 常用的调试方法
  6. Android中的Context几种获取方法和区别
  7. Android日期对话框NumberPicker的用法教程
  8. Android(三):远程Service基本操作
  9. Android(安卓)自定义线程池

随机推荐

  1. android中google“定位查询”编辑
  2. 【Android(安卓)Developers Training】 1
  3. 一起学android之如何获取网络类型并判断
  4. MTK Android(安卓)Driver知识大全
  5. 我的第一个 Mono for Android(安卓)应用
  6. Android(安卓)Camera(五)
  7. 安卓开发环境AS2.0搭建
  8. android adb shell 笔记
  9. android点击事件
  10. android的学习(一)环境搭建