• android invalidate 执行流程详解


    invalidate()函数的主要作用是请求View树进行重绘,该函数可以由应用程序调用,或者由系统函数间接 调用,例如setEnable(), setSelected(), setVisiblity()都会间接调用到invalidate()来请求View树重绘,更新View树的显示。      注:requestLayout()和requestFocus()函数也会引起视图重绘
          下面我们通过源码来了解invalidate()函数的工作原理,首先我们来看View类中invalidate()的实现过程:


    1. /**
    2.      * Invalidate the whole view. If the view is visible,
    3.      * [url=mailto:{@link]{@link[/url] #onDraw(android.graphics.Canvas)} will be called at some point in
    4.      * the future. This must be called from a UI thread. To call from a non-UI thread,
    5.      * call [url=mailto:{@link]{@link[/url] #postInvalidate()}.
    6.      */
    7.     public void invalidate() {
    8.         invalidate(true);
    9.     }
    复制代码

    invalidate()函数会转而调用invalidate(true),继续往下看:


    1. /**
    2.      * This is where the invalidate() work actually happens. A full invalidate()
    3.      * causes the drawing cache to be invalidated, but this function can be called with
    4.      * invalidateCache set to false to skip that invalidation step for cases that do not
    5.      * need it (for example, a component that remains at the same dimensions with the same
    6.      * content).
    7.      *
    8.      * @param invalidateCache Whether the drawing cache for this view should be invalidated as
    9.      * well. This is usually true for a full invalidate, but may be set to false if the
    10.      * View's contents or dimensions have not changed.
    11.      */
    12.     void invalidate(boolean invalidateCache) {
    13.         if (ViewDebug.TRACE_HIERARCHY) {
    14.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
    15.         }
    16.         if (skipInvalidate()) {
    17.             return;
    18.         }
    19.         if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
    20.                 (invalidateCache && (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) ||
    21.                 (mPrivateFlags & INVALIDATED) != INVALIDATED || isOpaque() != mLastIsOpaque) {
    22.             mLastIsOpaque = isOpaque();
    23.             mPrivateFlags &= ~DRAWN;
    24.             mPrivateFlags |= DIRTY;
    25.             if (invalidateCache) {
    26.                 mPrivateFlags |= INVALIDATED;
    27.                 mPrivateFlags &= ~DRAWING_CACHE_VALID;
    28.             }
    29.             final AttachInfo ai = mAttachInfo;
    30.             final ViewParent p = mParent;
    31.             //noinspection PointlessBooleanExpression,ConstantConditions
    32.             if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
    33.                 if (p != null && ai != null && ai.mHardwareAccelerated) {
    34.                     // fast-track for GL-enabled applications; just invalidate the whole hierarchy
    35.                     // with a null dirty rect, which tells the ViewAncestor to redraw everything
    36.                     p.invalidateChild(this, null);
    37.                     return;
    38.                 }
    39.             }
    40.             if (p != null && ai != null) {
    41.                 final Rect r = ai.mTmpInvalRect;
    42.                 r.set(0, 0, mRight - mLeft, mBottom - mTop);
    43.                 // Don't call invalidate -- we don't want to internally scroll
    44.                 // our own bounds
    45.                 p.invalidateChild(this, r);
    46.             }
    47.         }
    48.     }
    复制代码

    下面我们来具体进行分析invalidate(true)函数的执行流程:


                1、首先调用skipInvalidate(),该函数主要判断该View是否不需要重绘,如果不许要重绘则直接返回,不需要重绘的条件是该View不可见并且未进行动画

                2、接下来的if语句是来进一步判断View是否需要绘制,其中表达式 (mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)的意思指的是如果View需要重绘并且其大小不为0,其余几个本人也未完全理解,还望高手指点~~如果需要重绘,则处理相关标志位

                3、对于开启硬件加速的应用程序,则调用父视图的invalidateChild函数绘制整个区域,否则只绘制dirty区域(r变量所指的区域),这是一个向上回溯的过程,每一层的父View都将自己的显示区域与传入的刷新Rect做交集。

          接下来看invalidateChild()的 实现过程:


    1.     public final void invalidateChild(View child, final Rect dirty) {
    2.         if (ViewDebug.TRACE_HIERARCHY) {
    3.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD);
    4.         }
    5.         ViewParent parent = this;
    6.         final AttachInfo attachInfo = mAttachInfo;
    7.         if (attachInfo != null) {
    8.             // If the child is drawing an animation, we want to copy this flag onto
    9.             // ourselves and the parent to make sure the invalidate request goes
    10.             // through
    11.             final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION;
    12.             if (dirty == null) {
    13.                 if (child.mLayerType != LAYER_TYPE_NONE) {
    14.                     mPrivateFlags |= INVALIDATED;
    15.                     mPrivateFlags &= ~DRAWING_CACHE_VALID;
    16.                     child.mLocalDirtyRect.setEmpty();
    17.                 }
    18.                 do {
    19.                     View view = null;
    20.                     if (parent instanceof View) {
    21.                         view = (View) parent;
    22.                         if (view.mLayerType != LAYER_TYPE_NONE) {
    23.                             view.mLocalDirtyRect.setEmpty();
    24.                             if (view.getParent() instanceof View) {
    25.                                 final View grandParent = (View) view.getParent();
    26.                                 grandParent.mPrivateFlags |= INVALIDATED;
    27.                                 grandParent.mPrivateFlags &= ~DRAWING_CACHE_VALID;
    28.                             }
    29.                         }
    30.                         if ((view.mPrivateFlags & DIRTY_MASK) != 0) {
    31.                             // already marked dirty - we're done
    32.                             break;
    33.                         }
    34.                     }
    35.                     if (drawAnimation) {
    36.                         if (view != null) {
    37.                             view.mPrivateFlags |= DRAW_ANIMATION;
    38.                         } else if (parent instanceof ViewRootImpl) {
    39.                             ((ViewRootImpl) parent).mIsAnimating = true;
    40.                         }
    41.                     }
    42.                     if (parent instanceof ViewRootImpl) {
    43.                         ((ViewRootImpl) parent).invalidate();
    44.                         parent = null;
    45.                     } else if (view != null) {
    46.                         if ((view.mPrivateFlags & DRAWN) == DRAWN ||
    47.                                 (view.mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
    48.                             view.mPrivateFlags &= ~DRAWING_CACHE_VALID;
    49.                             view.mPrivateFlags |= DIRTY;
    50.                             parent = view.mParent;
    51.                         } else {
    52.                             parent = null;
    53.                         }
    54.                     }
    55.                 } while (parent != null);
    56.             } else {
    57.                 // Check whether the child that requests the invalidate is fully opaque
    58.                 final boolean isOpaque = child.isOpaque() && !drawAnimation &&
    59.                         child.getAnimation() == null;
    60.                 // Mark the child as dirty, using the appropriate flag
    61.                 // Make sure we do not set both flags at the same time
    62.                 int opaqueFlag = isOpaque ? DIRTY_OPAQUE : DIRTY;
    63.                 if (child.mLayerType != LAYER_TYPE_NONE) {
    64.                     mPrivateFlags |= INVALIDATED;
    65.                     mPrivateFlags &= ~DRAWING_CACHE_VALID;
    66.                     child.mLocalDirtyRect.union(dirty);
    67.                 }
    68.                 final int[] location = attachInfo.mInvalidateChildLocation;
    69.                 location[CHILD_LEFT_INDEX] = child.mLeft;
    70.                 location[CHILD_TOP_INDEX] = child.mTop;
    71.                 Matrix childMatrix = child.getMatrix();
    72.                 if (!childMatrix.isIdentity()) {
    73.                     RectF boundingRect = attachInfo.mTmpTransformRect;
    74.                     boundingRect.set(dirty);
    75.                     //boundingRect.inset(-0.5f, -0.5f);
    76.                     childMatrix.mapRect(boundingRect);
    77.                     dirty.set((int) (boundingRect.left - 0.5f),
    78.                             (int) (boundingRect.top - 0.5f),
    79.                             (int) (boundingRect.right + 0.5f),
    80.                             (int) (boundingRect.bottom + 0.5f));
    81.                 }
    82.                 do {
    83.                     View view = null;
    84.                     if (parent instanceof View) {
    85.                         view = (View) parent;
    86.                         if (view.mLayerType != LAYER_TYPE_NONE &&
    87.                                 view.getParent() instanceof View) {
    88.                             final View grandParent = (View) view.getParent();
    89.                             grandParent.mPrivateFlags |= INVALIDATED;
    90.                             grandParent.mPrivateFlags &= ~DRAWING_CACHE_VALID;
    91.                         }
    92.                     }
    93.                     if (drawAnimation) {
    94.                         if (view != null) {
    95.                             view.mPrivateFlags |= DRAW_ANIMATION;
    96.                         } else if (parent instanceof ViewRootImpl) {
    97.                             ((ViewRootImpl) parent).mIsAnimating = true;
    98.                         }
    99.                     }
    100.                     // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
    101.                     // flag coming from the child that initiated the invalidate
    102.                     if (view != null) {
    103.                         if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
    104.                                 view.getSolidColor() == 0) {
    105.                             opaqueFlag = DIRTY;
    106.                         }
    107.                         if ((view.mPrivateFlags & DIRTY_MASK) != DIRTY) {
    108.                             view.mPrivateFlags = (view.mPrivateFlags & ~DIRTY_MASK) | opaqueFlag;
    109.                         }
    110.                     }
    111.                     parent = parent.invalidateChildInParent(location, dirty);
    112.                     if (view != null) {
    113.                         // Account for transform on current parent
    114.                         Matrix m = view.getMatrix();
    115.                         if (!m.isIdentity()) {
    116.                             RectF boundingRect = attachInfo.mTmpTransformRect;
    117.                             boundingRect.set(dirty);
    118.                             m.mapRect(boundingRect);
    119.                             dirty.set((int) boundingRect.left, (int) boundingRect.top,
    120.                                     (int) (boundingRect.right + 0.5f),
    121.                                     (int) (boundingRect.bottom + 0.5f));
    122.                         }
    123.                     }
    124.                 } while (parent != null);
    125.             }
    126.         }
    127.     }
    复制代码

    大概流程如下,我们主要关注dirty区域不是null(非硬件加速)的情况:
                1、判断子视图是否是不透明的(不透明的条件是isOpaque()返回true,视图未进行动画以及child.getAnimation() == null),并将判断结果保存到变量isOpaque中,如果不透明则将变量opaqueFlag设置为DIRTY_OPAQUE,否则设置为 DIRTY。

                2、定义location保存子视图的左上角坐标

                3、如果子视图正在动画,那么父视图也要添加动画标志,如果父视图是ViewGroup,那么给mPrivateFlags添加 DRAW_ANIMATION标识,如果父视图是ViewRoot,则给其内部变量mIsAnimating赋值为true

                4、设置dirty标识,如果子视图是不透明的,则父视图设置为DIRTY_OPAQUE,否则设置为DIRTY

                5、调用parent.invalidateChildInparent(),这里的parent有可能是ViewGroup,也有可能是 ViewRoot(最后一次while循环),首先来看ViewGroup, ViewGroup中该函数的主要作用是对dirty区域进行计算

          以上过程的主体是一个do{}while{}循环,不断的将子视图的dirty区域与父视图做运算来确定最终要重绘的dirty区域,最终循环到 ViewRoot(ViewRoot的parent为null)为止,并将dirty区域保存到ViewRoot的mDirty变量中


    1. /**
    2.      * Don't call or override this method. It is used for the implementation of
    3.      * the view hierarchy.
    4.      *
    5.      * This implementation returns null if this ViewGroup does not have a parent,
    6.      * if this ViewGroup is already fully invalidated or if the dirty rectangle
    7.      * does not intersect with this ViewGroup's bounds.
    8.      */
    9.     public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    10.         if (ViewDebug.TRACE_HIERARCHY) {
    11.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD_IN_PARENT);
    12.         }
    13.         if ((mPrivateFlags & DRAWN) == DRAWN ||
    14.                 (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
    15.             if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
    16.                         FLAG_OPTIMIZE_INVALIDATE) {
    17.                 dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
    18.                         location[CHILD_TOP_INDEX] - mScrollY);
    19.                 final int left = mLeft;
    20.                 final int top = mTop;
    21.                 if ((mGroupFlags & FLAG_CLIP_CHILDREN) != FLAG_CLIP_CHILDREN ||
    22.                         dirty.intersect(0, 0, mRight - left, mBottom - top) ||
    23.                         (mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) {
    24.                     mPrivateFlags &= ~DRAWING_CACHE_VALID;
    25.                     location[CHILD_LEFT_INDEX] = left;
    26.                     location[CHILD_TOP_INDEX] = top;
    27.                     if (mLayerType != LAYER_TYPE_NONE) {
    28.                         mLocalDirtyRect.union(dirty);
    29.                     }
    30.                     return mParent;
    31.                 }
    32.             } else {
    33.                 mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
    34.                 location[CHILD_LEFT_INDEX] = mLeft;
    35.                 location[CHILD_TOP_INDEX] = mTop;
    36.                 if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
    37.                     dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
    38.                 } else {
    39.                     // in case the dirty rect extends outside the bounds of this container
    40.                     dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
    41.                 }
    42.                 if (mLayerType != LAYER_TYPE_NONE) {
    43.                     mLocalDirtyRect.union(dirty);
    44.                 }
    45.                 return mParent;
    46.             }
    47.         }
    48.         return null;
    49.     }
    复制代码

    该 函数首先调用offset将子视图的坐标位置转换为在父视图(当前视图)的显示位置,这里主要考虑scroll后导致子视图在父视图中的显示区域会发生变 化,接着调用union函数求得当前视图与子视图的交集,求得的交集必定是小于dirty的范围,因为子视图的dirty区域有可能超出其父视图(当前视 图)的范围,最后返回当前视图的父视图。

          再来看ViewRoot中invalidateChildInparent的执行过程:


    1. public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    2.         invalidateChild(null, dirty);
    3.         return null;
    4.     }
    复制代码

    该函数仅仅调用了ViewRoot的invalidateChild,下面继续看invalidateChild的源码:


    1. public void invalidateChild(View child, Rect dirty) {
    2.         checkThread();
    3.         if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
    4.         if (dirty == null) {
    5.             // Fast invalidation for GL-enabled applications; GL must redraw everything
    6.             invalidate();
    7.             return;
    8.         }
    9.         if (mCurScrollY != 0 || mTranslator != null) {
    10.             mTempRect.set(dirty);
    11.             dirty = mTempRect;
    12.             if (mCurScrollY != 0) {
    13.                dirty.offset(0, -mCurScrollY);
    14.             }
    15.             if (mTranslator != null) {
    16.                 mTranslator.translateRectInAppWindowToScreen(dirty);
    17.             }
    18.             if (mAttachInfo.mScalingRequired) {
    19.                 dirty.inset(-1, -1);
    20.             }
    21.         }
    22.         if (!mDirty.isEmpty() && !mDirty.contains(dirty)) {
    23.             mAttachInfo.mSetIgnoreDirtyState = true;
    24.             mAttachInfo.mIgnoreDirtyState = true;
    25.         }
    26.         mDirty.union(dirty);
    27.         if (!mWillDrawSoon) {
    28.             scheduleTraversals();
    29.         }
    30.     }
    复制代码

    具体分析如下:            1、判断此次调用是否在UI线程中进行

                2、将dirty的坐标位置转换为ViewRoot的屏幕显示区域

                3、更新mDirty变量,并调用scheduleTraversals发起重绘请求

          至此一次invalidate()就结束了

          总结:invalidate主要给需要重绘的视图添加DIRTY标记,并通过和父视图的矩形运算求得真正需要绘制的区域,并保存在ViewRoot中的 mDirty变量中,最后调用scheduleTraversals发起重绘请求,scheduleTraversals会发送一个异步消息,最终调用 performTraversals()执行重绘,performTraversals()的具体过程以后再分析。

  • 相关阅读:
    09、AppBarControl
    15、ScrollViewerSample
    11、DataBinding
    18、Compression
    关于创建oracle dblink 过程的几点心得吧
    教你如何玩转DK血
    DK需要知道的事
    Displaying Dynamic KML with ASP.NET
    WLK:裁缝/附魔350450速冲攻略
    WLK狂暴,防御战士的一点心得.
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/3684377.html
Copyright © 2020-2023  润新知