在View的工作原理(一)我们知道,Activity.setContentView()就是用来初始化View树,确定界面主题,样式,确定View的层次结构,以及添加我们自定义的布局到View树的。在这个过程中,系统添加内置布局到DecorView,添加我们的自定义布局到contentParent,都是调用的addView(),包括我们平时用一个ViewGroup添加子view的时候也是用这个。那么addView()这个操作到底做了什么呢,为什么添加完了,就能在界面看到添加进去的子View?我们就来看看addView()的源码,看看到底做了什么操作.
public void addView(View child) {
addView(child, -1);
}
public void addView(View child, int index) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
//最终都是调用三个参数的
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
// addViewInner()将调用child.requestLayout() ,当我们设置了新的布局参数的时候。因此
//我们在这之前调用了requestLayout(),以便child的request在我们这个层级被阻止
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
可以看到addView()最终做了两个重要的操作,也就是本篇要学习的重点:View刷新UI的两个非常重要的方法 requestLayout()和 invalidate()
View.requestLayout()
requestLayout()是View中的方法:
/**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
* tree. This should not be called while the view hierarchy is currently in a layout
* pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
* end of the current layout pass (and then layout will run again) or after the current
* frame is drawn and the next layout occurs.
*
* Subclasses which override this method should call the superclass method to
* handle possible request-during-layout errors correctly.
*/
//注释翻译:view的布局要改变的时候调用.调用这个方法将会使整个View树重新布局.
//如果View树正在布局过程中,这个布局请求会在布局过程结束或者当前帧绘制完成并且下一个布局发生时再执行
@CallSuper
public void requestLayout() {
//mMeasureCache 是LongSparseLongArray类型(比HashMap更高效的容器)
if (mMeasureCache != null) mMeasureCache.clear();
//mViewRequestingLayout 是View类型的变量,用于跟踪记录哪个View发起requestLayout()
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
//viweRoot不为null并且view树正在进行布局
if (viewRoot != null && viewRoot.isInLayout()) {
//如果不处理view的布局请求
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
//记录发起布局请求的view
mAttachInfo.mViewRequestingLayout = this;
}
//给当前view添加布局标记和重绘标记
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
//调用父控件的requestLayout
mParent.requestLayout();
}
//如果发起布局请求View的是当前view,则置空
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
可以看到requestLayout()方法做的操作就是:
1,判断view树是否在进行布局,如果是,那么当前view发起的布局请求不处理,直接return
2,给当前view添加布局标记和重绘标记
3,如果当前view的父View不为null,就调用父View的requestLayout()
4,如果发起布局请求的是当前view,置空发起变量
这里有个关键点:子View发起布局请求会调用父view的requestLayout(),而父View也就是ViewGroup,ViewGroup并没有复写View的requestLayout(),调用的requestLayout()还是View的requestLayout()。这样父View就也会调用它的父View的requestLayout(),层层往上传递,直到View树的顶层View,也就是DecorView。说到这里,有个问题:View怎么确定他的父View是谁呢,或者说View的mParent,也就是父View在哪里赋值的呢?我们就看一下View的mParent赋值的地方:
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
这个函数就是为mParent方法赋值的,它是在View里面定义的,而它又被ViewGroup的addViewInner()调用,通过前面addView()的源码我们知道,addViewInner又是在addView()被调用,所以,我们只要调用了ViewGroup的addView(),就会去调用assignParent()绑定一个View和它的父View。
但是我们看到它的参数类型不是ViewGroup,而是ViewParent。前面我们说View的父View不是ViewGroup吗,难道说错了?其实没有错,ViewParent是一个接口,ViewGroup就实现了这个接口,而且除了ViewGroup实现了它以外,还有一个类也实现了这个接口:ViewRootImpl。通过这种继承关系,我们可以有个猜想:只要是实现了ViewParent的类就是父View,ViewRootImpl肯定也是父View.
其实ViewRootImpl就是DecorView的mParent,但由于ViewRootImpl不是一个View,所以我们在View里面找不到给DecorView的mParent赋值的地方。指定ViewRootImpl为DecorView的mParent的操作是在Activity启动之后,ActivityThread 会调用 WindowManager#addView(),而这个 addView() 最终其实是调用了 WindowManagerGlobal 的 addView() 方法。由于Activity的启动过程巨复杂,涉及的知识点太多,就不展开看具体源码了,这里直接跳到WindowManagerGlobal 的 addView() :
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
..........省略部分代码
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
........省略
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
//第一个参数view就是DecorView
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
看一下ViewRootImpl的setView():
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
.........
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
...........
//指定DecorView为ViewRootImpl的子View
view.assignParent(this);
...........
}
}
这里就把DecorView和ViewRootImpl绑定了。看到这里,再通过requestLayout()源码我们可以知道,requestLayout()最终会调用到ViewRootImpl的requestLayout()。我们看一下ViewRootImpl的requestLayout():
@Override
public void requestLayout() {
//没有处理布局请求
if (!mHandlingLayoutInLayoutRequest) {
//检测是否在主线程
checkThread();
//修改布局请求处理标记为true
mLayoutRequested = true;
//view的绘制过程触发
scheduleTraversals();
}
}
在ViewRootImpl.requestLayout()里面会去调用scheduleTraversals(),这个方法就是View的绘制过程的起点,具体怎么触发view的绘制过程,下篇再分析。
View.invalidate()
/**
* Invalidate the whole view. If the view is visible,
* {@link #onDraw(android.graphics.Canvas)} 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()}.
*/
//如果view是visible,就重绘这个view. onDraw()将会在将来某个时间点被调用,
//这个方法必须要在UI线程中使用,如果要在非UI线程中重绘view,使用postInvalidate()
public void invalidate() {
invalidate(true);
}
//invalidateCache 绘制缓存是否也要被重绘
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
从注释中可以看到要在UI线程中重绘调用invalidate(),如果要在工作线程中重绘要用postInvalidate().我们看一下实际的调用者invalidateInternal():
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
//如果view不可见或者正在做动画,跳过重绘
if (skipInvalidate()) {
return;
}
//如果正在绘制并且有范围(View大小确定了) 或者绘制缓存有效 或者没有重绘标记 或者 不透明
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
//全部重绘
if (fullInvalidate) {
//记录是否透明
mLastIsOpaque = isOpaque();
//取消正在绘制的标记
mPrivateFlags &= ~PFLAG_DRAWN;
}
//添加变脏标记
mPrivateFlags |= PFLAG_DIRTY;
//如果绘制缓存也要重绘,一般为true
if (invalidateCache) {
//添加重绘标记
mPrivateFlags |= PFLAG_INVALIDATED;
//移除绘制缓存有效标记
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// Propagate the damage rectangle to the parent view.
//传递重绘区域给父View
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);
}
// Damage the entire projection receiver, if necessary.
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
}
}
接着看ViewGroup的invalidateChild(View child, final Rect dirty):
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
//使用硬件加速绘制
onDescendantInvalidated(child, child);
return;
}
//局部变量parent初始值为自己
ViewParent parent = this;
if (attachInfo != null) {
// 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
final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;
// Check whether the child that requests the invalidate is fully opaque
// Views being animated or transformed are not considered opaque because we may
// be invalidating their old position and need the parent to paint behind them.
Matrix childMatrix = child.getMatrix();
final boolean isOpaque = child.isOpaque() && !drawAnimation &&
child.getAnimation() == null && childMatrix.isIdentity();
// Mark the child as dirty, using the appropriate flag
// Make sure we do not set both flags at the same time
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;
//记录要重绘的子view的相对于父View的坐标
location[CHILD_LEFT_INDEX] = child.mLeft;
location[CHILD_TOP_INDEX] = child.mTop;
if (!childMatrix.isIdentity() ||
(mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
Matrix transformMatrix;
if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
Transformation t = attachInfo.mTmpTransformation;
boolean transformed = getChildStaticTransformation(child, t);
if (transformed) {
transformMatrix = attachInfo.mTmpMatrix;
transformMatrix.set(t.getMatrix());
if (!childMatrix.isIdentity()) {
transformMatrix.preConcat(childMatrix);
}
} else {
transformMatrix = childMatrix;
}
} else {
transformMatrix = childMatrix;
}
transformMatrix.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
do {
View view = null;
//如果局部变量parent属于View,就给view赋值
if (parent instanceof View) {
view = (View) parent;
}
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) 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
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
opaqueFlag = PFLAG_DIRTY;
}
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
}
}
//调用自己的(parent初始值为自己)invalidateChildInParent(location, dirty),返回的却是自己的mParent,
//也就是自己的父View.while循环判断父View是否为null,不为null,一直向上传递,直到ViewRootImp
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
}
}
再看一下ViewGroup的invalidateChildInParent(location, dirty):
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
//当前ViewGroup有正在绘制标志位或者绘制缓存有效标志位
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
// either DRAWN, or DRAWING_CACHE_VALID
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
!= FLAG_OPTIMIZE_INVALIDATE) {
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
final int left = mLeft;
final int top = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
dirty.setEmpty();
}
}
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;
} else {
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
} else {
// in case the dirty rect extends outside the bounds of this container
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
location[CHILD_LEFT_INDEX] = mLeft;
location[CHILD_TOP_INDEX] = mTop;
mPrivateFlags &= ~PFLAG_DRAWN;
}
//移除绘制缓存有效标记
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
}
返回当前ViewGroup的父View
return mParent;
}
return null;
}
可以看到View.invalidate()主要内容有:
1,调用真正的重绘方法invalidateInternal();
2,将要绘制区域传递给父View,调用父View的invalidateChild();
3,调用invalidateChildInParent()处理child的dirty矩阵与ViewGroup可显示矩阵的关系,同时返回该父View的Parent以便下次循环接着调用(mParent就当做是ViewGroup了)
这里层层往上传递的过程就和requestLayout类似了,唯一的不同是,invalidate()最终调用的ViewRootImpl的invalidateChildInParent():
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
//重绘区域为null,再次请求重绘
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
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;
}
接着看ViewRootImpl的 invalidateRectOnScreen:
private void invalidateRectOnScreen(Rect dirty) {
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;
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)) {
//触发view的绘制过程
scheduleTraversals();
}
}
至此,View的两个非常重要的方法requestLayout(),invalidate分析完毕
(待完善:1,requestLayout与invalidate的区别,2,invalidate和postInvalidate的区别,3,invalidate如何控制只绘制发起重绘请求的view,而不是所有的View)