zoukankan      html  css  js  c++  java
  • Android视图重绘,使用invalidate还是requestLayout

    概述

    在我们在进行自定义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()了

  • 相关阅读:
    SQLyog使用中的点滴总结
    Maven的GroupID和ArtifactID的含义
    Android View 深度分析requestLayout、invalidate与postInvalidate
    15 个 Android 通用流行框架大全
    Android Studio中获取查看签名SHA1证书指纹数据或MD5的方法
    android:descendantFocusability用法简析
    Android应用层View绘制流程与源码分析
    android.graphic.Path
    Android Scroller简单用法
    Android 2D Graphics学习 Region和Canvas裁剪
  • 原文地址:https://www.cnblogs.com/ganchuanpu/p/8807919.html
Copyright © 2011-2022 走看看