一个 forceLayout() 和 requestLayout() 的测试

本文探讨了Android中View的forceLayout()和requestLayout()的区别与应用场景。通过测试case展示了它们如何影响子View的onMeasure(), onLayout(), layout(), draw()和onDraw()调用。forceLayout()在下次layout bypass中强制重新测量和布局,而requestLayout()不仅强制布局,还会触发布局传递。理解这两个方法对于优化Android应用的性能至关重要。" 106681196,9091652,配置zipkin服务端连接rabbitmq进行链路追踪,"['链路追踪', 'zipkin', 'rabbitmq']

两个view:

一个是系统默认的FrameLayout,  A

一个是自己自定义的MyView extends View,重载了onMeasure函数(): B

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		// Log.e(TAG, "onMeasure " + MeasureSpec.getSize(widthMeasureSpec)
		// 		+ " " + MeasureSpec.getSize(heightMeasureSpec));
		setMeasuredDimension(MeasureSpec.makeMeasureSpec(mW-=10, MeasureSpec.EXACTLY),
				MeasureSpec.makeMeasureSpec(mH-=10, MeasureSpec.EXACTLY));
	}
逻辑很简单,每次只要B的onMeasure被触发,那么B的尺寸就原来小10.

A包含B,

几个测试case: (measure() 是final的,无法覆盖,不过B的measure()应该是每次被调到的)

1. A.requestLayout();                                    不会调用到B.onMeasure() 和 B.onLayout(), 会调用B.layout() (注意连draw()都没有被调到,这意味着B被A认为完全没变,不需要重绘)

2. B.forceLayout(); A.requestLayout()           会调用到B的.onMeasure() 和 onLayout() 以及 layout(), draw(), onDraw()

3. B.requestLayout()                                      会调用到B的.onMeasure() 和 onLayout() 以及 layout(),draw(), onDraw()

4. B.invalidate()                                               B的layout(), onLayout() 和 onMeasure() 没有被调到,只有 draw() 和 onDraw() 被调到.

5  A.invalidate()                                               B的所有函数都不会被调到.


这个结果也符合code的逻辑,forceLayout()如果注释说的一样,是在下一次layout bypass 的过程(自己不会发起一次layout bypass)中,会强制的重新onMeasure和onLayout()

而requestLayout() 除了干forceLayout()的事情将自己的FORCE_LAYOUT标志位设上外,还会发起一次layout pass,

在layout bypass 从 A 传到 B时,虽然 A 的 onMeasure() 和 onLayout() 会调用 B 的 measure() 和 layout(), 但是,因为 B的layout状态没有什么改变,因此

onMeasure()和 onLayout()不会被调用.

在当前4.4的code:

public void forceLayout() {
     ................................................
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;
    }

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
       ...............................................................
        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
               .......................................................
            int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                    mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                ..................................................
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimension((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
......................................................................................
            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
.............................................................................
}


public void layout(int l, int t, int r, int b) {
        .....................................................
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

           ...................................................................
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

可见forceLayout()可以导致onMeasure() 和 onLayout(). 而requestLayout() 干的事情比forceLayout()只多不少.

View的forceLayout后面一般会紧跟着View的measure(), 这样可以把view的measuredSize 通过 setMeasuredDimension 设上.

而PFLAG_INVALIDATED 这个flag 应该是标记 重绘的。

而requestLayout()/forceLayout() 设置的另一个flag PFLAG_FORCE_LAYOUT,也一定可以在measure()中 将PFLAG_LAYOUT_REQUIRED flag给打上,

而PFLAG_LAYOUT_REQUIRED 则是可以保证了在调用 layout()函数时,onLayout() 函数会被调用到.


还有一点,之前没有仔细看, View的measure(A, B) 函数:

	       如果有PFLAG_FORCE_LAYOUT 或者 本次的measure的尺寸 A, B 和 之前的尺寸不一样
               if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
				widthMeasureSpec != mOldWidthMeasureSpec ||
				heightMeasureSpec != mOldHeightMeasureSpec) {

			........................................................

			这一步其实是从之前的MeasureCache里找是否存在 和 本次的measure尺寸一致的 cache
                        int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
					mMeasureCache.indexOfKey(key);
			如果没有找到,那么就调用onMeasure, 一般来说,调用了onMeasure才能真正的setMeasuredDimension
                        if (cacheIndex < 0 || sIgnoreMeasureCache) {
				// measure ourselves, this should set the measured dimension flag back
				onMeasure(widthMeasureSpec, heightMeasureSpec);
                                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
                        } else {
                               如果找到有cache,那么就将cache的值 设为 setMeasuredDimension
                               long value = mMeasureCache.valueAt(cacheIndex);
                               // Casting a long to int drops the high 32 bits, no mask needed
                               setMeasuredDimension((int) (value >> 32), (int) value);
                               mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
                        }
                 }
                
                ..............................................
                mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;

                ..............................................
                mOldWidthMeasureSpec = widthMeasureSpec;
                mOldHeightMeasureSpec = heightMeasureSpec;

                mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                      (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension 

这说明调用measure(A, B), 只要尺寸变化,都会影响 MeasuredHeight/Width

<FrameLayout android:id="@+id/switch_video_map" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:id="@+id/map" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="@color/white"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:text="捡球车遥控面板" android:textColor="#263238" android:textSize="20sp" android:textStyle="bold"></TextView> </RelativeLayout> <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" app:cardCornerRadius="8dp" android:layout_marginBottom="16dp" android:layout_marginLeft="15dp" android:layout_marginRight="15dp"> </androidx.cardview.widget.CardView> </LinearLayout> <com.skydroid.fpvplayer.FPVWidget android:id="@+id/fpvWidget" android:layout_width="190dp" android:layout_height="100dp" android:layout_gravity="bottom|start" android:background="@color/black"/> </FrameLayout> fpvWidget = findViewById(R.id.fpvWidget); mapWidget = findViewById(R.id.map); switchVideoMapLayout = findViewById(R.id.switch_video_map); mapWidget.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isFullVideo) { switchVideoMap(); } } }); fpvWidget.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (!isFullVideo) { switchVideoMap(); } } }); private boolean isFullVideo = false; private void switchVideoMap() { if (isFullVideo) { // 设置交换图层后的宽高 FrameLayout.LayoutParams fpvLayoutParams = (FrameLayout.LayoutParams) fpvWidget.getLayoutParams(); fpvLayoutParams.width = PxUtils.dp2px(this, 190); fpvLayoutParams.height = PxUtils.dp2px(this, 100); fpvLayoutParams.gravity = Gravity.BOTTOM | Gravity.START; fpvWidget.setLayoutParams(fpvLayoutParams); FrameLayout.LayoutParams mapLayoutParams = (FrameLayout.LayoutParams) mapWidget.getLayoutParams(); mapLayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; mapLayoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; mapWidget.setLayoutParams(mapLayoutParams); // 交换图层 switchVideoMapLayout.removeView(fpvWidget); switchVideoMapLayout.addView(fpvWidget); isFullVideo = false; } else { // 设置交换图层后的宽高 FrameLayout.LayoutParams fpvLayoutParams = (FrameLayout.LayoutParams) fpvWidget.getLayoutParams(); fpvLayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; fpvLayoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; fpvWidget.setLayoutParams(fpvLayoutParams); FrameLayout.LayoutParams mapLayoutParams = (FrameLayout.LayoutParams) mapWidget.getLayoutParams(); mapLayoutParams.width = PxUtils.dp2px(this, 190); mapLayoutParams.height = PxUtils.dp2px(this, 100); mapLayoutParams.gravity = Gravity.BOTTOM | Gravity.START; mapWidget.setLayoutParams(mapLayoutParams); // 交换图层 switchVideoMapLayout.removeView(mapWidget); switchVideoMapLayout.addView(mapWidget); isFullVideo = true; } } 这是我的一个安卓代码,点击mapfpvWidget,可以实现俩个界面自动切换显示,但是我现在发现从fpvWidget切换回map的时候,map里面CardView里面的数据右边的一半没有显示?请帮我分析是怎么回事?应该怎么修改?
最新发布
11-25
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值