Android View系统源码分析(十二)—— View.invalidate()

本文详细解析了Android中View的重绘机制,包括invalidate()函数的工作原理及其在View树中的传播过程。阐述了如何确定哪些区域需要重绘,并介绍了动画效果对重绘的影响。
该函数的作用是请求View树进行重绘,当应用程序需要重绘某个视图时,可以调用该函数。视图极其父视图在界面上是分层先后显示的。
绘制的流程中,首先绘制最底层的根视图,然后再绘制其他的子视图。子视图或者是一个ViewGroup,或者是一个View。
如果是ViewGroup的话,则继续再绘制ViewGroup内部的子视图,绘制过程一般不会对所有视图进行重绘,而仅绘制那些“需要重绘”的视图,也就是mPrivateFlags中包含一个标志位为DRAWN的视图,当该视图需要绘制时,就给mPrivateFlags中添加该标志位。
函数invalidate()的作用就是要根据所有视图中的该标志位计算具体哪个区域需要重绘,这个区域将用一个矩形Rect表示,并最终将这个Rect存放到ViewRoot中的mDirty变量中,之后的重绘过程将重绘所有包含在该mDirty区中的视图。
对于一个视图没有透明度,即Alpha通道,那么理论上讲,如果只是该视图需要重绘,则不会导致该视图所在的父视图也进行重绘。只有该视图隐藏时,再通知其父视图绘制之前该视图所占用的区域即可。

相反,如果该视图存在Alpha通道,那么也就是说可以看到其父视图,那么其父视图也是需要重绘。

VIEW-INVALIDATE

下载VSDX

View.invalidate()

   /**
     * Invalidate the whole view. If the view is visible, {@link #onDraw} will
     * be called at some point in the future. This must be called from a
     * UI thread. To call from a non-UI thread, call {@link #postInvalidate()}.
     * 
     * 使全部的视图无效。如果该视图是可视的,onDraw方法将会被将来被调用。
     * 并且onDraw方法必须要在UI线程上被调用。
     * 如果想在非UI线程上调用onDraw方法的话就需要使用 postInvalidate()来实现。
     */
    public void invalidate() {
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
        }
 
        //如果逻辑变量中有DRAWN属性标志,并且该视图为有效视图。
        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
 
            //清除掉逻辑变量重哦那个的 DRAWN 、DRAWING_CACHE_VALID属性位。
            mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
 
            final ViewParent p = mParent;
            final AttachInfo ai = mAttachInfo;
 
            //如果该视图的父视图不为空并且 AttachInfo对象不为空
            if (p != null && ai != null) {
                final Rect r = ai.mTmpInvalRect;
 
                //保存当前视图的矩阵(视图坐标)
                r.set(0, 0, mRight - mLeft, mBottom - mTop);
 
                // Don't call invalidate -- we don't want to internally scroll
                // our own bounds
                // 不要调用自己的invalidate,我们不想内部滚动我们拥有的范围
 
                //调用父视图的invalidateChild,通知父视图该区域需要重绘。
                p.invalidateChild(this, r);
            }
        }
    }
 
 
 
 
 
ViewGroup.invalidateChild(View child , final Rect dirty)


 public final void invalidateChild(View child, final Rect dirty){
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD);
        }
 
        ViewParent parent = this;
 
        final AttachInfo attachInfo = mAttachInfo;
 
        if (attachInfo != null) {
 
            //从 attachInfo 对象中获取子视图的布局坐标
            final int[] location = attachInfo.mInvalidateChildLocation;
            location[CHILD_LEFT_INDEX] = child.mLeft;
            location[CHILD_TOP_INDEX] = child.mTop;
 
            // If the child is drawing an animation, we want to copy this flag onto
            // ourselves and the parent to make sure the invalidate request goes
            // through
            //如果子视图正在做动画的话,我们想复制这个标记到我们自己以及父视图来确保这个无效请求的过程
 
            //判断是否要正在或者下次绘制要做动画。子视图的mPrivateFlags会包含DRAW_ANIMATION标志位。
            final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION;
 
            // Check whether the child that requests the invalidate is fully opaque
            // 检查请求 invalidate的子视图是否是完全不透明的
            // 首先子视图的 isOpaque() 必须返回true表示该视图为不透明的,其次是当前没有进行动画,最后是getAnimation不为空。
            final boolean isOpaque = child.isOpaque() && !drawAnimation &&
                    child.getAnimation() != null;
 
            // Mark the child as dirty, using the appropriate flag
            // Make sure we do not set both flags at the same time
            // 确保DIRTY_OPAQUE、DIRTY标记不要同时出现。
 
            final int opaqueFlag = isOpaque ? DIRTY_OPAQUE : DIRTY;
 
            do {
                View view = null;
 
                //如果该ViewGroup的父视图是View的子类(非ViewRoot)
                if (parent instanceof View) {
 
                    view = (View) parent;
                }
 
                //如果当前视图的子视图是在做动画
                if (drawAnimation) {
 
                    //如果父视图不为空
                    if (view != null) {
                        //添加父视图的逻辑的 DRAW_ANIMATION属性。
                        view.mPrivateFlags |= DRAW_ANIMATION;
                    } else if (parent instanceof ViewRoot) {
 
                        //如果父视图为ViewRoot,就设置ViewRoot的mIsAnimating变量为true。
                        ((ViewRoot) parent).mIsAnimating = true;
                    }
                }
 
                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                // flag coming from the child that initiated the invalidate
                // 如果父视图是包含 DIRTY_OPAQUE 或者 NOT_DIRTY 属性的,标记父视图为DIRTY_OPAQUE 属性。
                // 该属性是来自于发起 invalidate()的那个子视图。
 
                // 如果父视图不为空,并且父视图的逻辑变量的 DIRTY 属性位不是 DIRTY(只要父视图不完全绘制,子视图为不透明的)。
                if (view != null && (view.mPrivateFlags & DIRTY_MASK) != DIRTY) {
 
                    //设置当前视图的父视图的逻辑变量为 opaqueFlag 变量保存的属性,该属性有两种可能。
                    //如果当前视图的子视图是不透明的,则当前视图的父视图的dirty标志位的属性为DIRTY_OPAQUE,当前视图的父视图可以不重绘,因为子视图是不透明的。
                    //如果当前视图的子视图是透明的,则当前视图的父视图的dirty标志位的属性为 DIRTY。
                    view.mPrivateFlags = (view.mPrivateFlags & ~DIRTY_MASK) | opaqueFlag;
                }
 
                //更新parent变量,invalidateChildInParent()的返回值进行递归调用。location为当前ViewGroup的视图坐标,dirty为当前视图的子视图需要重绘的区域Rect。
                parent = parent.invalidateChildInParent(location, dirty);
 
                //循环到View树的根视图即退出。到此,从最初的child到所有的父视图都设置好了绘制所需的标识,包括DRAW_ANIMATION、DIRTY_OPAQUE、DIRTY。
            } while (parent != null);
        }
    }
 
 
 
ViewGroup.invalidateChildInParent(final int[] location , final Rect dirty)


/**
     * Don't call or override this method. It is used for the implementation of
     * the view hierarchy.
     *
     * This implementation returns null if this ViewGroup does not have a parent,
     * 这个实现如果该ViewGroup没有父视图的话(也就是根视图的时候),就返回空。
     * if this ViewGroup is already fully invalidated or if the dirty rectangle
     * does not intersect with this ViewGroup's bounds.
     * 如果这个ViewGroup已经完全无效或者如果dirty的Rect与此ViewGroup界限不相交的话也会返回空。
     */
    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD_IN_PARENT);
        }
 
        //如果当前ViewGroup的逻辑标识中包含DRAWN属性标识。含义是该视图是否已经绘制到屏幕上了,这与视图的VISIBLE状态有所不同,就算视图是VISIBLE的,但是如果视图没有显示到屏幕上,其DRAWN标识依然为false。
        if ((mPrivateFlags & DRAWN) == DRAWN) {
 
            //如果ViewGroup标识的 FLAG_OPTIMIZE_INVALIDATE(优化失效) 以及 FLAG_ANIMATION_DONE(动画完成)属性位的值不为 FLAG_ANIMATION_DONE。判断是否已经完成动画,或者没有动画。
            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
                        FLAG_OPTIMIZE_INVALIDATE) {
 
                //设置dirty的矩形偏移量,这样就从子视图的视图坐标转换成了父视图的布局坐标。
                dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                        location[CHILD_TOP_INDEX] - mScrollY);
 
                final int left = mLeft;
                final int top = mTop;
 
 
                //经过对父视图显示的区域的交叉 insersect 的计算,对子视图的Rect进行截取。所以现在的DIRTY是有可能变小的,并对交叉不显示的区域是不经过绘制的。
                if (dirty.intersect(0, 0, mRight - left, mBottom - top) ||
                        (mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) {
                    mPrivateFlags &= ~DRAWING_CACHE_VALID;
 
                    //更新定点为当前父视图的视图坐标,也就是子视图的布局坐标。
                    location[CHILD_LEFT_INDEX] = left;
                    location[CHILD_TOP_INDEX] = top;
 
                    //返回当前视图的父视图
                    return mParent;
                }
            } else {
 
                //绘制动画
                //清除 DRAWN 、 DRAWING_CACHE_VALID
                mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
 
                //直接设置其视图坐标为整个绘制范围
                location[CHILD_LEFT_INDEX] = mLeft;
                location[CHILD_TOP_INDEX] = mTop;
 
                //设置dirty的Rect为当前父视图的整个显示区域。因为动画影响的是整个区域。
                dirty.set(0, 0, mRight - location[CHILD_LEFT_INDEX],
                        mBottom - location[CHILD_TOP_INDEX]);
 
                return mParent;
            }
        }
 
        return null;
    }
 
 
 
ViewRoot.invalidteChildInParent(final int[] location , final Rect dirty)
  public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
 
        invalidateChild(null, dirty);
        return null;
    }
 
 
 
ViewRoot.invalidateChild(View child , Rect dirty)
 public void invalidateChild(View child, Rect dirty) {
 
        //确保主线程调用,否则直接抛出异常。
        checkThread();
        if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
 
        //如果当前显示区域有Y轴偏移,或者有 mTranslator 转换器(如果物理分辨率和显示分辨率不同时会有该对象)。
        if (mCurScrollY != 0 || mTranslator != null) {
 
            //将dirty区域设置到mTempRect中
            mTempRect.set(dirty);
 
            //将dirty的引用指向mTempRect,该区域为实际绘制的区域。
            dirty = mTempRect;
 
            //如果Y轴有偏移量,就设置dirty的偏移量进行调整。
            if (mCurScrollY != 0) {
               dirty.offset(0, -mCurScrollY);
            }
 
            //处理逻辑像素转换
            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(dirty);
            }
 
            //如果有缩放的需要的话,就将dirty区域进行1像素的内嵌。
            if (mAttachInfo.mScalingRequired) {
                dirty.inset(-1, -1);
            }
        }
 
        //使用成员变量 mDirty 结合当前要绘制的dirty区域。mDirty保存了是一次invalidate所产生的dirty区,应用程序可以在一次MessageQueue消息处理过程中多次调用invalidate()函数,从而不断更新ViewRoot中的mDirty变量。本步骤中正是把新的dirty添加到mDirty变量中。
        mDirty.union(dirty);
 
        // 查看是否刚刚发送过请求,也就是之前发送的请求未处理。
        if (!mWillDrawSoon) {
 
            //检测 mTraversalScheduled 变量。
            scheduleTraversals();
        }
    }
 
 
 
ViewRoot.checkThread()


 void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
 
 
 
View.scheduleTraversals()
 public void scheduleTraversals() {
 
        //如果之前的遍历已经执行完了
        if (!mTraversalScheduled) {
 
            //更新标记
            mTraversalScheduled = true;
 
            //发送遍历消息。
            sendEmptyMessage(DO_TRAVERSAL);
        }
    }
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值