先放上结论
- requestLayout会直接递归调用父窗口的requestLayout,直到ViewRootImpl,然后触发peformTraversals,由于mLayoutRequested为true,会导致onMeasure和onLayout被调用。不一定会触发OnDraw
- requestLayout触发onDraw可能是因为在在layout过程中发现l,t,r,b和以前不一样,那就会触发一次invalidate,所以触发了onDraw,也可能是因为别的原因导致mDirty非空(比如在跑动画)
- view的invalidate不会导致ViewRootImpl的invalidate被调用,而是递归调用父view的invalidateChildInParent,直到ViewRootImpl的invalidateChildInParent,然后触发peformTraversals,会导致当前view被重绘,由于mLayoutRequested为false,不会导致onMeasure和onLayout被调用,而OnDraw会被调用
以上结论来自 从源码看invalidate和requestLayout的区别,这篇博客讲得比较细致,推荐大家对着源码看一遍,会很有收获。
博客主要讲了
View调用invalidate方法时,怎么保证不绘制所有的view,而只绘制当前view呢
我这里重点讲一下 为什么 View#invalidate
方法不会导致onMeasure
和onLayout
被调用,以及mDirty与onDraw方法的关系。这两个个问题被作者一笔带过了。
1. invalidate不调用performMeasure和performLayout
先看下 requestLayout 方法
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
//这里设置为true
mLayoutRequested = true;
scheduleTraversals();
}
}
再来看 performTraversal方法的大致逻辑,对应的逻辑我都写到注释中了,挺清晰的。
private void performTraversals() {
......
// 调用invalidate的话,mLayoutRequested为false,所以layoutRequested为false
// 调用requestLayout,会把mLayoutRequested设置为true
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
......
if (layoutRequested) {
// Clear this now, so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
// layout pass.
//每次使用performTraversals都会把mLayoutRequested重置为false
mLayoutRequested = false;
}
// 如果layoutRequested为false,那windowShouldResize一定是false了,不可能可以调用到performMeasure了
boolean windowShouldResize = layoutRequested && windowSizeMayChange && ......;
if (mFirst || windowShouldResize || ......) {
......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
}
.......
// invalidate没有把mLayoutRequested设置为true,因此didLayout将为false,因此也无法调用performLayout
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
performLayout(lp, mWidth, mHeight);
......
}
......
performDraw()
}
2. performDraw方法与requestLayout,view.invalidate的关系
performDraw里面会调用draw方法
private boolean draw(boolean fullRedrawNeeded) {
······
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
······
// 执行真正的绘制流程
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
······
}
······
}
可以看到,如果!dirty.isEmpty()为true,才会去绘制。
而view#invalidate时,会直接把自身的 left,right,top,bottom传递过去,并一路调用到 ViewRootImpl#invalidateRectOnScreen(Rect dirty),该方法中会调用scheduleTraversals()
。从而保证可以调用performDraw()
。而requestLayout是没有这一步的,因此往往不会执行绘制方法。