invalidate()
当一个View调用了invalidate方法的话,然后就很容易走到了这个方法
View.invalidateInternal()
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
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)) {
...
final ViewParent p = mParent; //首先获取当前View 的父容器
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
//然后调用了ViewGroup的invalidateChild方法
p.invalidateChild(this, damage);
}
...
}
}
接下来我们就走到了ViewGroup的的invalidateChild()方法中了
ViewGroup.invalidateChild()
@Deprecated
@Override
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
...
//这个while循环的主要作用是一步步返回到最顶层的ViewGroup
do {
...
//这个方法我们就可以发现了刚才我说的
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);
}
}
最后一直往上寻找他们的根,最后一直会找到一个ViewRootImpl类,这个类很重要,我会在后面的文章中详细的讲解,今天主要的任务是看这个invalidate方法,下面我们继续看:
ViewRootImpl.invalidateChildInParent
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
//我们不能更新ui在非主线程中,就是因为有这个 checkThread()的方法存在,
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
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;
}
之所以将这个方法提取出来,如果我们稍微了解过View的绘制流程的同学对这个 scheduleTraversals()比较敏感.
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
继续跟进去看:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
/**
下面的这个postCallback中传入了几个参数,今天我们看的就是这个mTraversalRunnable
这个参数 final TraversalRunnable mTraversalRunnable = new TraversalRunnable();是一个runnable的接口实现
我们也知道这个接口只有一个方法 run
然后我们进入这个方法中,去看看
*/
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
下面这个方法来自Runnable的run方法,唯一的一个方法实现.
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();//这是唯一的入口函数,也是View绘制流程的入口函数
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
viewRootImpl.performTraversals() ==非常重要==
private void performTraversals() {
//源码比较长.主要的三个方法是
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
//源码中做了好多判断,当我们调用invalidate方法的时候,通过各种判断是不会进入前面两个方法的,只会调用perfromDraw方法.
performDraw();
}
performTraversals()方法也就是View绘制流程真正的入口,不管是view的重新绘制还是我下面要说的requestLayout()方法都会进入这个方法来处理逻辑.
private void performDraw() {
...
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...
}
private void draw(boolean fullRedrawNeeded) {
...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
...
} else {
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
...
}
/**
* @return true if drawing was successful, false if an error occurred
*/
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
//锁定我们要绘制那个区域
canvas = mSurface.lockCanvas(dirty);
...
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not lock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
}
...
mView.draw(canvas);
...
return true;
}
我们可以看到一个mView.draw方法,这个mView不是我们之前想要绘制的那个View而是最顶层的那个View,下面就是View 的绘制流程了,通过mView 这个最顶层的容器,依次往下来onDraw.
总结
当一个view 调用invalidate方法时,先依次调用父容器的invalidateChildInParent方法
最终调用到ViewRootImpl方法中的invalidateChildInParent()方法,然后通过这个方法来实现一个Runnable接口
ViewRootImpl的performTraversals() 方法,然后又开始向下分发,给各个子View去绘制自己.
requestLayout()
其实看完invalidate方法,回过头看requestLayout方法就很简单了.
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
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();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
依次往上跑,一直到ViewRootImpl的requestLayout()方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//View调用requestLayout最终层层上传到ViewRootImpl后最终触发了该方法
scheduleTraversals();
}
}
代码就不重复贴了,主要的思想是:
requestLayout()方法会调用measure过程和layou过程,不会调用draw过程,所以不会重新绘制任何View(包括该调用者本身)
总结
当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求父view重新调用他的onMeasure、onLayout来重新设置自己位置。
有时我们在改变一个view 的内容之后,可能会造成显示出现错误,比如写ListView的时候 重用convertview中的某个TextView 可能因为前后填入的text长度不同而造成显示出错,此时我们可以在改变内容之后调用requestLayout方法加以解决。