View postInvalidateOnAnimation() invalidate()
  1Klse8Cpv8td 2023年11月02日 35 0


/**
	 * <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。


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  qSqNY1UH2lvR   2023年11月02日   43   0   0 链路权重ide
  01BFOGI7NzGp   2023年11月02日   52   0   0 nginxluanginx location ifide
  Fv5flEkOgYS5   2023年11月02日   37   0   0 i++javaide
1Klse8Cpv8td