View之requestLayout源码解析

前言

在这个方法的源码中,核心的思想用到了 二进制标志位 , 如果你还不知道什么是二进制标志位,墙裂建议先看一下二进制标志位在Java中的应用
先抛出一个问题,如下图所示,当我调用 View1requestLayout 方法时, 图中的 ViewGroup1、View1、ViewGroup2、View2 都会重新测量和布局吗?
icon

1、requestLayout源码解析

public void requestLayout() {
		  //.......省略非关键代码
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        //.......省略非关键代码
    }

requestLayout方法的核心工作分为两步:

  1. 通过 “或操作” 给当前的 View的 mPrivateFlags变量 添加上 PFLAG_FORCE_LAYOUT 和 PFLAG_INVALIDATED 两个二进制标志位。
  2. 获取到当前 ViewmParent对象 (ViewGroup) ,调用 mParentrequestLayout 方法。
  3. 重复步骤一。在 parent 类中也会给自己添加上步骤一中的两个二进制标志位,并且会调用 parent 的 parent (也就是爷爷)的 requestLayout ,最终会这样不断的向上递归到最顶层的 DectorViewDectorView类是我们在屏幕中看见的UI视图中最根部的View,他继承自FrameLayout。 但是 DectorView 并不能自己触发自己的测量布局绘制流程,所以 DectorView 以成员变量的形式寄托在ViewRootImpl类中ViewRootImpl 类并不是 View 的子类,但是他是我们看到的屏幕上的UI界面的终极Boss, 正是这个类会依次触发DectorView的 measure、layout、draw 三大流程。

通过上边的步骤,当我们调用 View1requestLayout 方法后,会一步步向上委托,一直到 ViewRootImpl ,然后由 ViewRootImpl 触发整个视图树的重新绘制。回到开头说的那个问题,既然视图树都重新绘制了,那么是不是当前屏幕上的所有视图都要重新测量布局一遍吗? 答案是否定的,只有那些 “需要被重新测量布局” 的View才会。那么View在测量的时候怎么判断是否需要重新绘制呢? 先看看 View#measure 方法是怎么实现的:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
        
        if (forceLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            onMeasure(widthMeasureSpec, heightMeasureSpec);

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
    }

我将无关的代码省略了,只保留了和requestLayout方法相关的代码。这句代码是不是很熟悉:

final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

我们在 requestLayout 方法中,给 mPrivateFlags 变量添加了PFLAG_FORCE_LAYOUT 和 PFLAG_INVALIDATED 两个二进制标志位,所以当一个View在执行measure方法的时候,会判断自己是否有 PFLAG_FORCE_LAYOUT 标志位,也就是这个view是否执行过requestLayout,如果有才会执行 onMeasure 方法继续测量,如果没有这个标志位就直接跳过了。并且在 onMesure 结束后又重新给 mPrivateFlags 添加了一个新的标志位:

mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;

这个标志位会在接下来的 View#layout 方法中用到,省略掉无关的代码后:

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

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        }
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
}

由于在measure 方法中的最后给 mPrivateFlags 变量添加了 PFLAG_LAYOUT_REQUIRED 标志位, 所以在接下来的 layout 方法中,if判断的条件才会成立。如果在measure方法中没有添加这个标志位(或者说view没有执行过requestLayout方法),那么 layout方法也就直接跳过了。并且layout方法会将上边添加的两个标志位全部清除掉。当measure 和 layout 两个方法执行完后,下一步就会执行draw方法。

总结

看到这上边抛出的问题其实就已经很清楚了,当我调用 View1 的 requestLayout 方法,首先会给 View1 添加标志位,然后找到自己的 parent 也就是 ViewGroup1,然后调用ViewGroup1 的 requestLayout 方法。ViewGroup1 又给自己添加上标志位…重复往上递归…。最后整个视图树一步步向下递归重绘的时候,发现ViewGroup1 有标志位,而ViewGroup2没有标志位,所以 ViewGroup1 会继续向下重绘View1,而ViewGroup2由于没有标志位所以测量到ViewGroup2的时候,也就断了。所以当我调用 View1requestLayout 方法时,只有 ViewGroup1 和 View1 进行了重新的测量和布局。

ok!大功告成!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值