在Android视图绘制开发中,invalidate方法常用,在主线程中调用它,用于触发视图的绘制刷新。下面我们分析一下该方法的主要流程,看一下一个视图执行该方法后是如何进行刷新的,参考源码。场景,添加一个简单的正方形视图,点击触发它的invalidate方法,场景比较简单,代码就补贴了,从View#invalidate方法开始分析。
public void invalidate() {
invalidate(true);
}
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop,
invalidateCache, true);
}
调用View#invalidateInternal方法,绘制区域是视图相对自己坐标系的坐标值(0,0,width,height)。
mTop:视图相对父容器坐标系的上侧边界坐标。
mLeft:视图相对父容器坐标系的左侧边界坐标。
mRight-mLeft:视图宽度。
mBottom-mTop:视图高度。
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
...
//跳过绘制,如非VISIBLE时
if (skipInvalidate()) {
return;
}
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) ==....) {
...
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
//告诉父View绘制的区域
p.invalidateChild(this, damage);
}
...
}
}
绘制视图增加PFLAG_INVALIDATED标志,删除PFLAG_DRAWING_CACHE_VALID标志。父视图ViewParent ,调用它的#invalidateChild方法,传入绘制区域Rect,该区域是相对视图坐标系的值,即(0,0,width,height)。
ViewGroup实现ViewParent接口,ViewGroup#invalidateChild方法。参数告诉父视图,绘制的子视图以及子视图的坐标区域(相对子视图自己的坐标系)。
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
//AttachInfo不能是空
if (attachInfo != null) {
//如果子View正在动画
final boolean drawAnimation = (child.mPrivateFlags &
PFLAG_DRAW_ANIMATION) == PFLAG_DRAW_ANIMATION;
//View是否存在矩阵变换
Matrix childMatrix = child.getMatrix();
//完全不透明,没有动画
final boolean isOpaque = child.isOpaque() && !drawAnimation &&
child.getAnimation() == null && childMatrix.isIdentity();
//子View设置标志dirty
int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;
//LayerType不是LAYER_TYPE_NONE
if (child.mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
final int[] location = attachInfo.mInvalidateChildLocation;
location[CHILD_LEFT_INDEX] = child.mLeft;
location[CHILD_TOP_INDEX] = child.mTop;
.....
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
...
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
opaqueFlag = PFLAG_DIRTY;
}
...
}
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// 矩阵变换相关
...
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
...
dirty.set(...);
}
}
} while (parent != null);
}
}
首先,该方法有一个向上层查找父视图的遍历,只要父视图存在,一直调用父视图的invalidateChildInParent方法,到顶层视图DecorView,DecorView内部的ViewParent是ViewRootImpl,因此,ViewRootImpl#invalidateChildInParent方法。
invalidateChildInParent方法有两个参数,dirty区域是子视图区域(0,0,width,height),即invalidate方法的视图区域。location数组存储子视图相对父视图坐标系的左上坐标值,即mLeft和mTop。
public ViewParent invalidateChildInParent(final int[] location,
final Rect dirty) {
if ((mPrivateFlags & PFLAG_DRAWN) == ....) {
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | ...) {
//变换成相对父视图的坐标系的值。
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
//与父View坐标系联合,dirty可能变为父亲视图的区域。
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
//获取父View相对它的父节点的左侧和上侧距离
final int left = mLeft;
final int top = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
//有此标志,dirty区域已经是相对父视图的坐标系的值了,
//与父视图相交,区域有交集。在没有Scroll的情况下。
//其实就是视图自己的区域(相对父视图坐标系)
if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
dirty.setEmpty();
}
}
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
//location设置相对父视图的位置。
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;
...
//返回它的父视图。
return mParent;
} else {
.....
location[CHILD_LEFT_INDEX] = mLeft;
location[CHILD_TOP_INDEX] = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
} else {
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
....
return mParent;
}
}
return null;
}
首先,入参dirty区域的offset方法,加上location数组偏移,新区域是子视图相对于父视图坐标系的值,并且减去此时父视图mScrollX/mScrollY偏移,比如,父视图有一个向上的ScrollY偏移值。则整个子视图相对父节点的mBottom与mTop都会减小ScrollY。
然后,重新设置location数组元素值,设置成父视图相对它的父视图的左上侧距离。
若不考虑dirty的其他变化,union或interset,一层层将新location与dirty传给父视图,最后的dirty是执行invalidate方法的绘制视相对于顶层视图坐标系的坐标位置区域。
当设置FLAG_CLIP_CHILDREN标志时,仅绘制调用invalidate方法的视图区域,不绘制其父视图的其他区域,这是默认设置。若不设置此标志,dirty区域将union联合父视图区域,变成父视图区域。
每一个父视图删除PFLAG_DRAWING_CACHE_VALID标志。
最后,触发ViewRootImp#invalidateChildInParent方法。
绘制区域图。
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
//保证是创建视图树结构的线程
checkThread();
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
//如果dirty内容为空,什么都不做,返回。
return null;
}
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);
}
}
invalidateRectOnScreen(dirty);
return null;
}
最后的入参,dirty区域,调用invalidate的视图,相对顶层视图坐标系的坐标值。
根据mCurScrollY设置dirty区域偏移,真正绘制在ViewRootImpl#invalidateRectOnScreen方法。绘制屏幕上的一块待更新脏区域。
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
.....
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
mDirty是保存在ViewRootImpl的一块脏区域。这里得到的mDirty区域是空的,与dirty区域union联合后,mDirty值变成dirty区域。再与窗体区域intersect相交,查看该区域是否在窗体中。
触发一次scheduleTraversals方法,此时,mDirty区域已经变为视图待更新区域了。
最终的绘制区域dirty是执行invalidate方法当视图相对整个窗体坐标系的Rect区域。
调试结果图。
手机模拟器绘制图。 黄色区域为绘制区域视图。
和invalidate一样,requestLayout方法也会触发 scheduleTraversals方法,但是requestLayout会设置一个mLayoutRequested标志,该标志会触发测量和布局,如果我们单纯的仅仅是视图刷新,不涉及大小的改变,只用invalidate刷新就可以了。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
遍历后,调用ViewRootImpl#performDraw方法,draw(fullRedrawNeeded)方法,fullRedrawNeeded是false,这里仅仅绘制一个区域。
硬件渲染时,不需要提供dirty区域,将dirty置空,软件渲染才需要dirty区域。
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
...
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
...
dirty.setEmpty();//设置空。
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
}
在硬件渲染时,只重绘触发invalidate方法的视图,具体流程图。
先看一下ThreadedRenderer的updateViewTreeDisplayList方法。
private void updateViewTreeDisplayList(View view) {
view.mPrivateFlags |= View.PFLAG_DRAWN;
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
view.updateDisplayListIfDirty();
view.mRecreateDisplayList = false;
}
从顶层视图开始,由invalidate方法引起的一次渲染视图,整个视图树结构已经建立好了。此时,顶层视图DecorView没有设置PFLAG_INVALIDATED标志,不存在mRecreateDisplayList重建标志,不需要重建DisplayList。
进入DecorView#updateDisplayListIfDirty方法。
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
if (!canHaveDisplayList()) {
return renderNode;
}
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.isValid()
|| (mRecreateDisplayList)) {
if (renderNode.isValid() && !mRecreateDisplayList) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();//派发子视图
return renderNode; // no work needed
}
...
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
RenderNode的isValid标志有效,并且没有mRecreateDisplayList标志,因此,不会重建顶层视图的Canvas数据。无PFLAG_DRAWING_CACHE_VALID标志(在前面已经将所有父视图该标志删除)。
进入dispatchGetDisplayList方法,将绘制事件派发给其他的视图节点,该方法在View是空方法,ViewGroup中重写。
protected void dispatchGetDisplayList() {
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
child.getAnimation() != null)) {
//判断子视图是否需要重建DisplayList
recreateChildDisplayList(child);
}
}
...
}
遍历每个子视图,recreateChildDisplayList方法,根据具体情况重建视图的绘制数据。
private void recreateChildDisplayList(View child) {
child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
child.mPrivateFlags &= ~PFLAG_INVALIDATED;
child.updateDisplayListIfDirty();
child.mRecreateDisplayList = false;
}
子视图无PFLAG_INVALIDATED标志时,不设置mRecreateDisplayList标志,因此,在子视图updateDisplayListIfDirty方法不会重建DisplayList绘制数据,这时,和顶层视图一样,直接返回当前视图RenderNode,并继续将绘制事件分发给下层视图。
当到达invalidate的视图时,PFLAG_INVALIDATED绘制标志存在,将设置mRecreateDisplayList标志,在该视图的updateDisplayListIfDirty方法中重建它的DisplayList绘制数据,调用View#onDraw方法。
总结
硬件渲染,最终仅将invalidate视图的绘制操作数据写入gpu重绘。invalidate基本流程图。任重而道远