View postInvalidateOnAnimation() invalidate()

本文深入探讨了Android中View的postInvalidateOnAnimation()和invalidate()方法的工作原理。它们如何在下一个帧开始时触发重绘操作,以及如何在ViewRootImpl中调度这些操作。重点讲述了InvalidateOnAnimationRunnable的角色,它如何在draw阶段指示需要重绘的区域。同时,解释了即使没有立即调度Traversal_callback,但通过设置mDirty并在后续的CALLBACK_ANIMATION和CALLBACK_TRAVERSAL回调中完成重绘的过程。
部署运行你感兴趣的模型镜像
	/**
	 * <p>Cause an invalidate to happen on the next animation time step, typically the
	 * next display frame.</p>
	 *
	 * <p>This method can be invoked from outside of the UI thread
	 * only when this View is attached to a window.</p>
	 *
	 * @see #invalidate()
	 */
	public void postInvalidateOnAnimation() {
		// 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.dispatchInvalidateOnAnimation(this);
		}
	}

如同注释所讲,会在下一个Frame开始的时候,发起一些invalidate操作,

ViewRootImpl的dispatchInvalidateOnAnimation():

public void dispatchInvalidateOnAnimation(View view) {
        mInvalidateOnAnimationRunnable.addView(view);
    }

mInvalidateOnAnimationRunnable 是一个 InvalidateOnAnimationRunnable:

        public void addView(View view) {
            synchronized (this) {
                mViews.add(view);
                postIfNeededLocked();
            }
        }
而postIfNeededLocked()干的事情就是把mInvalidateOnAnimationRunnable 作为Choreographer.CALLBACK_ANIMATION(这个类型的task会在mesaure/layout/draw之前被运行)的Task 交给 UI线程的Choreographer.

而该runnable真正干的事情是:

@Override
        public void run() {
            final int viewCount;
            final int viewRectCount;
            synchronized (this) {
                mPosted = false;

                viewCount = mViews.size();
                if (viewCount != 0) {
                    mTempViews = mViews.toArray(mTempViews != null
                            ? mTempViews : new View[viewCount]);
                    mViews.clear();
                }

                viewRectCount = mViewRects.size();
                if (viewRectCount != 0) {
                    mTempViewRects = mViewRects.toArray(mTempViewRects != null
                            ? mTempViewRects : new AttachInfo.InvalidateInfo[viewRectCount]);
                    mViewRects.clear();
                }
            }

            for (int i = 0; i < viewCount; i++) {
                mTempViews[i].invalidate();
                mTempViews[i] = null;
            }

            for (int i = 0; i < viewRectCount; i++) {
                final View.AttachInfo.InvalidateInfo info = mTempViewRects[i];
                info.target.invalidate(info.left, info.top, info.right, info.bottom);
                info.recycle();
            }
        }
可以看到就是将添加到此runnbable中的view或者View的某块区域(rect)全部invalidate。

这样在后面的draw的时候就知道应该重绘哪些了.

View的invalidate会进一步触发ViewRootImpl的invalidateChildInParent()->invalidate()<一种情况(dirty == null 表示全部重绘),不过另外一种差不多>:

        @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        首先检查是不是UI线程
        checkThread();
        if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);

        dirty == null 代表着全部重绘
        if (dirty == null) {
            invalidate();
            return null;
        否则如果没有dirty的区域并且当前也没有动画,那么直接结束
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }

        下面的是将当前的dirty区域集合window做一些裁剪和交集
        if (mCurScrollY != 0 || mTranslator != null) {
            mTempRect.set(dirty);
            dirty = mTempRect;
            if (mCurScrollY != 0) {
                dirty.offset(0, -mCurScrollY);
            }
            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(dirty);
            }
            if (mAttachInfo.mScalingRequired) {
                dirty.inset(-1, -1);
            }
        }

        注意这里 localDirty 已经指向 mDirty了
        final Rect localDirty = mDirty;
        if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
            mAttachInfo.mSetIgnoreDirtyState = true;
            mAttachInfo.mIgnoreDirtyState = true;
        }

        // Add the new dirty rect to the current one
        localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
        // Intersect with the bounds of the window to skip
        // updates that lie outside of the visible region
        final float appScale = mAttachInfo.mApplicationScale;

        将dirty的区域与window区域相交(超过了window的不用画)
        final boolean intersected = localDirty.intersect(0, 0,
                (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        if (!intersected) {
            localDirty.setEmpty();
        }
        
        如果之后不会有draw的安排(performTraversals() 会在开始将mWillDrawSoon设为true)
        并且dirty和window有交集或者有动画,那么就schedule一个traversal来进行重绘.
        if (!mWillDrawSoon && (intersected || mIsAnimating)) {
            scheduleTraversals();
        }

        return null;
    }

    
    void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        scheduleTraversals();
    }
而scheduleTraversals:

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            scheduleConsumeBatchedInput();
        }
    }
因为本次Frame的现在只运行到了CALLBACK_ANIMATION阶段,CALLBACK_TRAVERSAL还没有运行到,那么doTranversal也就没有被运行到,

那么mTraversalScheduled就还是true,这样其实是没有真正发出Traversal_callback的,

不过虽然没有发出去,不代表这次invalidate就不会生效,因为前面的invalidate()里已经设置了mDirty了:

而mDirty会在ViewRootImpl的draw函数里被使用来得到哪些rect需要重绘,

而刚好,在本次Frame的CALLBACK_ANIMATION完了以后的CALLBACK_TRAVERSAL会被运行,performTraversals()->performDraw()->draw(), 这样,就在本次Frame

就完成了对invalidate的区域的重绘.


注意也就是 通过 以CALLBACK_ANIMATION形式送到Choreographer来保证了在下一个Frame的时候进行invalidate。


您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值