View#requestLayout()的作用:
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 (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.
当发生一些导致视图的布局无效的改变时时,调用此函数。这将调度视图树的布局传递。
当视图层次结构正处于当前布局传递(isInLayout())的过程中时,不应该调用这个函数。
如果正在布局,请求可能会在当前布局传递结束时(然后布局将再次运行),或者在当前帧绘制完成并出现下一个布局之后被执行。
重写此方法的子类应该调用超类方法来正确处理可能的请求布局期间错误。
调用View#requestLayout()后的最终效果:
一般情况下,会有额外的view也执行绘制流程。
最小化效果:
①发起requestLayout()
调用的View,及其各级parent(直到ViewRootImpl)
,它们的requestLayout方法
都会被调用,都会添加PFLAG_FORCE_LAYOUT
和PFLAG_INVALIDATED
标记。
②下一个刷新时机中,添加了标记的View
会从上到下
依次执行onMeasure
、onLayout
方法。
实际中情况更复杂,不同的ViewGroup
它的onMeasure和onLayout实现有差别
,且由于同一层级的布局间也会有依赖关系
,所以onMeasure
和onLayout
的执行范围和次数都不确定
,有些情况下child的onDraw
也会执行。
PFLAG_FORCE_LAYOUT标记被清除的时机:
在View#layout
方法中,调用完onLayout
方法后,会清除掉PFLAG_FORCE_LAYOUT
标记。
PFLAG_INVALIDATED标记被清除的时机:
在View#layout
方法中,调用完onLayout
方法后,会清除掉PFLAG_FORCE_LAYOUT
标记。
在ViewRootImpl#performDraw
流程中,会调用到ThreadedRenderer#updateViewTreeDisplayList(View view)
方法,这里会读取PFLAG_INVALIDATED
标记并给View#mRecreateDisplayList
赋值,然后清除PFLAG_INVALIDATED
标记。
接下来的View#updateDisplayListIfDirty()
方法,用到了mRecreateDisplayList
变量,此时mRecreateDisplayList
为true。
1、View#requestLayout()
/**
* 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.
*
* <p>Subclasses which override this method should call the superclass method to
* handle possible request-during-layout errors correctly.</p>
*/
@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
// AttachInfo#mViewRequestingLayout用来追踪最开始发起requestLayout的View。
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;
}
}
①清除mMeasureCache
②给mAttachInfo.mViewRequestingLayout
赋值,
③给mPrivateFlags
添加PFLAG_FORCE_LAYOUT
、PFLAG_INVALIDATED
标记。
④调用mParent
的requestLayout
,对mParent
做同样的操作。
最终的效果是,调用requestLayout的view
及其上级的parent(直至DecorView、ViewRootImpl)
,它们的mPrivateFlags
中都添加了PFLAG_FORCE_LAYOUT、PFLAG_INVALIDATED
标记。
2、最终会调用到ViewRootImpl#requestLayout方法中:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
这里将mLayoutRequested
置为true。然后调用scheduleTraversals()
;
ViewRootImpl#performTraversals()
ViewRootImpl#measureHierarchy()
3、ViewRootImpl:
private boolean mInLayout = false;
ArrayList<View> mLayoutRequesters = new ArrayList<View>();
boolean mHandlingLayoutInLayoutRequest = false;
实例验证
接下来我们通过实例来验证下:
简单布局:FrameLayout
布局1:
<?xml version="1.0" encoding="utf-8"?>
<com.tinytongtong.androidstudy.measure.view.CustomLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".measure.RequestLayoutTestActivity">
<com.tinytongtong.androidstudy.measure.view.CustomFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.tinytongtong.androidstudy.measure.view.CustomSingleView
android:id="@+id/view1"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/background_wtf" />
<com.tinytongtong.androidstudy.measure.view.CustomTextView
android:id="@+id/view2"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="130dp"
android:background="@color/background_wtf"
android:gravity="center"
android:text="text" />
<com.tinytongtong.androidstudy.measure.view.CustomButton
android:id="@+id/view3"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="right" />
</com.tinytongtong.androidstudy.measure.view.CustomFrameLayout>
<com.tinytongtong.androidstudy.measure.view.CustomRelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp">
<com.tinytongtong.androidstudy.measure.view.CustomHelloView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/background_warn" />
<com.tinytongtong.androidstudy.measure.view.CustomWorldView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="right"
android:background="@color/background_info" />
</com.tinytongtong.androidstudy.measure.view.CustomRelativeLayout>
</com.tinytongtong.androidstudy.measure.view.CustomLinearLayout>
log1:
E/CustomLayout-View: onMeasure widthSpecSize:275, widthSpecMode:1073741824, heightSpecSize:275, heightSpecMode:1073741824
E/CustomLayout-Frame: onMeasure widthSpecSize:1080, widthSpecMode:1073741824, heightSpecSize:1823, heightSpecMode:-2147483648
E/CustomLayout-Linear: onMeasure widthSpecSize:1080, widthSpecMode:1073741824, heightSpecSize:1823, heightSpecMode:1073741824
E/CustomLayout-View: onLayout changed:false, l:0, t:0, r:275, b:275
E/CustomLayout-Frame: onLayout changed:false, l:0, t:0, r:1080, b:633
E/CustomLayout-Linear: onLayout changed:false, l:0, t:0, r:1080, b:1823
E/CustomLayout-View: onDraw
现象:
某个child调用requestLayout
后:
①先会调用其onMeasure
,然后是从下往上
调用其各级parent
的onMeasure
;
②接着会调用child的onLayout
方法;最后从下往上
调用其各级parent
的onLayout
方法。
④child的onDraw
方法被调用
原理分析:
1、先会调用其onMeasure,然后是其各级parent依次调用onMeasure;
--> ViewRootImpl#doTraversal
--> ViewRootImpl#performTraversals
--> ViewRootImpl#measureHierarchy: // Ask host how big it wants to be。layoutRequested为true。
--> ViewRootImpl#performMeasure:!goodMeasure 条件满足
--> DecorView#measure(int widthMeasureSpec, int heightMeasureSpec)
--> View#measure(int widthMeasureSpec, int heightMeasureSpec):当view有PFLAG_FORCE_LAYOUT标记或needsLayout为true时,就会调用onMeasure方法。要么立即调用,要么添加PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT,在onLayout中优先调用onMeasure。
----①从mPrivateFlags中读取PFLAG_FORCE_LAYOUT标记,如果结果为true,且缓存中没有宽高,则调用自身的onMeasure方法。
----②获取needsLayout的值,如果为true,也会走①的流程。如果此时缓存中有宽高信息,则给mPrivateFlags3添加PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT标记。layout方法中会用到。
--> DecorView#onMeasure(int widthMeasureSpec, int heightMeasureSpec)
--> FrameLayout#onMeasure(int widthMeasureSpec, int heightMeasureSpec)
--> ViewGroup#measureChildWithMargins
--> View#measure
...
最后调用到发起requestLayout
的View。
这里跟child平级的View
,由于view有PFLAG_FORCE_LAYOUT
标记且needsLayout
为false,所以没有给mPrivateFlags
添加PFLAG_LAYOUT_REQUIRED
,也没有给mPrivateFlags3
添加PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT
标记,所以不会调用onLayout
方法,也不会在onLayout
调用时再次额外调用onMeasure
。
2、接着会调用child的onLayout方法;最后调用各级parent的onLayout方法。
ViewGroup#onLayout
方法中,一般是先调用child的onLayout
,然后打印我们自己的log的。
3、child的onDraw方法调用:
--> ViewRootImpl#doTraversal
--> ViewRootImpl#performTraversals
--> ViewRootImpl#performDraw:cancelDraw为false.
--> ViewRootImpl#draw(boolean fullRedrawNeeded)
--> ThreadedRenderer#draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks):绘制DecorView
--> ThreadedRenderer#updateRootDisplayList(View view, DrawCallbacks callbacks)
--> ThreadedRenderer#updateViewTreeDisplayList(View view):读取PFLAG_INVALIDATED标记并给#View#mRecreateDisplayList赋值,然后清除PFLAG_INVALIDATED标记。
--> View#updateDisplayListIfDirty():用到了mRecreateDisplayList,此时mRecreateDisplayList为true。
--> DecorView#draw(Canvas canvas):
--> View#draw(Canvas canvas)
--> View#dispatchDraw(Canvas canvas)
--> ViewGroup#dispatchDraw(Canvas canvas)
--> ViewGroup#drawChild(Canvas canvas, View child, long drawingTime)
--> View#draw(Canvas canvas, ViewGroup parent, long drawingTime)
--> View#updateDisplayListIfDirty()
--> ViewGroup#dispatchDraw(Canvas canvas)
--> ViewGroup#drawChild(Canvas canvas, View child, long drawingTime)
......
最终调用到child的onDraw
。
布局2:RealtiveLayout
<?xml version="1.0" encoding="utf-8"?>
<com.tinytongtong.androidstudy.measure.view.CustomLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".measure.RequestLayoutTestActivity">
<com.tinytongtong.androidstudy.measure.view.CustomRelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.tinytongtong.androidstudy.measure.view.CustomSingleView
android:id="@+id/view1"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/background_wtf" />
<com.tinytongtong.androidstudy.measure.view.CustomTextView
android:id="@+id/view2"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="130dp"
android:background="@color/background_wtf"
android:gravity="center"
android:text="text" />
<com.tinytongtong.androidstudy.measure.view.CustomButton
android:id="@+id/view3"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentRight="true" />
</com.tinytongtong.androidstudy.measure.view.CustomRelativeLayout>
<com.tinytongtong.androidstudy.measure.view.CustomFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp">
<com.tinytongtong.androidstudy.measure.view.CustomHelloView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/background_warn" />
<com.tinytongtong.androidstudy.measure.view.CustomWorldView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="right"
android:background="@color/background_info" />
</com.tinytongtong.androidstudy.measure.view.CustomFrameLayout>
</com.tinytongtong.androidstudy.measure.view.CustomLinearLayout>
log2:CustomLayout-View
就是发起requestLayout
的child
。
E/CustomLayout-View: onMeasure widthSpecSize:275, widthSpecMode:1073741824, heightSpecSize:1823, heightSpecMode:-2147483648
E/CustomLayout-View: onMeasure widthSpecSize:275, widthSpecMode:1073741824, heightSpecSize:275, heightSpecMode:1073741824
E/CustomLayout-Relative: onMeasure widthSpecSize:1080, widthSpecMode:1073741824, heightSpecSize:1823, heightSpecMode:-2147483648
E/CustomLayout-Linear: onMeasure widthSpecSize:1080, widthSpecMode:1073741824, heightSpecSize:1823, heightSpecMode:1073741824
E/CustomLayout-View: onLayout changed:false, l:0, t:0, r:275, b:275
E/CustomLayout-TextView: onMeasure widthSpecSize:275, widthSpecMode:1073741824, heightSpecSize:275, heightSpecMode:1073741824
E/CustomLayout-TextView: onLayout changed:false, l:28, t:358, r:303, b:633
E/CustomLayout-Button: onMeasure widthSpecSize:275, widthSpecMode:1073741824, heightSpecSize:275, heightSpecMode:1073741824
E/CustomLayout-Button: onLayout changed:false, l:805, t:0, r:1080, b:275
E/CustomLayout-Relative: onLayout changed:false, l:0, t:0, r:1080, b:633
E/CustomLayout-Linear: onLayout changed:false, l:0, t:0, r:1080, b:1823
现象:
某个child
调用requestLayout
后:
①其先会被调用两次onMeasure
(因为直接parent
是RelativeLayout
,会调用两次onMeasure
),然后是从下往上
其各级parent
依次调用一次onMeasure
;
②接着会调用child的onLayout方法,然后是child平级的各个child,会依次调用onMeasure、onLayout;
③最后从下往上
调用其各级parent的onLayout方法。
原理分析:
1、child先被调用两次onMeasure:这是因为其parent是RelativeLayout会对child测量两次。
主要是由PFLAG_FORCE_LAYOUT
标记触发的measure
流程。
ViewRootImpl#doTraversal
ViewRootImpl#performTraversals
ViewRootImpl#measureHierarchy: // Ask host how big it wants to be。layoutRequested为true。
ViewRootImpl#performMeasure:!goodMeasure 条件满足
DecorView#measure(int widthMeasureSpec, int heightMeasureSpec)
View#measure(int widthMeasureSpec, int heightMeasureSpec):当view有PFLAG_FORCE_LAYOUT标记或needsLayout为true时,就会调用onMeasure方法。要么立即调用,要么添加PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT,在onLayout中优先调用onMeasure。
----①从mPrivateFlags中读取PFLAG_FORCE_LAYOUT标记,如果结果为true,且缓存中没有宽高,则调用自身的onMeasure方法。
----②获取needsLayout的值,如果为true,也会走①的流程。如果此时缓存中有宽高信息,则给mPrivateFlags3添加PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT标记。layout方法中会用到。
----③如果①或②的条件为true,则会给mPrivateFlags添加PFLAG_LAYOUT_REQUIRED,该标记会执行后续的onLayout。
DecorView#onMeasure(int widthMeasureSpec, int heightMeasureSpec)
FrameLayout#onMeasure(int widthMeasureSpec, int heightMeasureSpec)
ViewGroup#measureChildWithMargins
View#measure
...
最后调用到发起requestLayout的View。
child平级的几个View,他们的needsLayout
的值为true,所以都会给mPrivateFlags
添加PFLAG_LAYOUT_REQUIRED
,会给mPrivateFlags3
添加PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT
标记,当他们的onLayout
执行时,也都会先调用onMeasure
。
如果parent
的布局没有变,则尽量不会去调用不相干的View的onMeasure
方法。
2、child的各级parent依次调用依次onMeasure:
ViewGroup#onMeasure
方法中,一般是先调用child
的onMeasure
,然后确定自身宽高。然后打印我们自己的log的。
3、接着执行layout流程。
ViewGroup#onLayout
中,会遍历调用child
的layout/onLayout
方法。
平级的child
的layout
方法中,由于mPrivateFlags3
中有PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT
标记,所以会先调用onMeasure
,再调用onLayout
;
--> ViewRootImpl#doTraversal
--> ViewRootImpl#performTraversals:didLayout为true
--> ViewRootImpl#performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)
--> DecorView#layout(int l, int t, int r, int b)
--> ViewGroup#layout(int l, int t, int r, int b)
--> View#layout(int l, int t, int r, int b)
--> DecorView#onLayout(boolean changed, int left, int top, int right, int bottom)
--> FrameLayout#onLayout(boolean changed, int left, int top, int right, int bottom)
--> FrameLayout#layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity):遍历调用child的layout方法。
--> View#layout(int l, int t, int r, int b)
--> ViewGroup#layout(int l, int t, int r, int b)
...
最后调用到发起requestLayout
的View
。
child
的parent
是RealtiveLayout
,跟child
平级的View#layout
方法中,会先调用onMeasure
方法,接着调用onLayout
方法。
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { // true
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
...
// 接着还会调用onLayout方法。
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...
}
// 清除掉PFLAG_FORCE_LAYOUT标记
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
}
4、最后调用各级parent的onLayout方法。
ViewGroup#onLayout
方法中,一般是先调用child
的onLayout
,然后打印我们自己的log的。