0
点赞
收藏
分享

微信扫一扫

调用View#requestLayout后,哪些View会被影响?

闲鱼不咸_99f1 2022-03-11 阅读 104
android

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_LAYOUTPFLAG_INVALIDATED标记。
②下一个刷新时机中,添加了标记的View从上到下依次执行onMeasureonLayout方法。

实际中情况更复杂,不同的ViewGroup它的onMeasure和onLayout实现有差别,且由于同一层级的布局间也会有依赖关系,所以onMeasureonLayout执行范围和次数都不确定,有些情况下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_LAYOUTPFLAG_INVALIDATED标记。
④调用mParentrequestLayout,对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,然后是从下往上调用其各级parentonMeasure
②接着会调用child的onLayout方法;最后从下往上调用其各级parentonLayout方法。
④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就是发起requestLayoutchild

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(因为直接parentRelativeLayout,会调用两次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方法中,一般是先调用childonMeasure,然后确定自身宽高。然后打印我们自己的log的。

3、接着执行layout流程。

ViewGroup#onLayout中,会遍历调用childlayout/onLayout方法。
平级的childlayout方法中,由于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)
...

最后调用到发起requestLayoutView

childparentRealtiveLayout,跟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方法中,一般是先调用childonLayout,然后打印我们自己的log的。

demo地址

https://gitee.com/tinytongtong/AndroidStudy/blob/dev/app/src/main/java/com/tinytongtong/androidstudy/measure/RelativeLayoutTestActivity.java

举报

相关推荐

0 条评论