前言
在这个方法的源码中,核心的思想用到了 二进制标志位 , 如果你还不知道什么是二进制标志位,墙裂建议先看一下二进制标志位在Java中的应用。
先抛出一个问题,如下图所示,当我调用 View1 的 requestLayout 方法时, 图中的 ViewGroup1、View1、ViewGroup2、View2 都会重新测量和布局吗?
1、requestLayout源码解析
public void requestLayout() {
//.......省略非关键代码
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
//.......省略非关键代码
}
requestLayout方法的核心工作分为两步:
- 通过 “或操作” 给当前的 View的 mPrivateFlags变量 添加上 PFLAG_FORCE_LAYOUT 和 PFLAG_INVALIDATED 两个二进制标志位。
- 获取到当前 View 的 mParent对象 (ViewGroup) ,调用 mParent 的 requestLayout 方法。
- 重复步骤一。在 parent 类中也会给自己添加上步骤一中的两个二进制标志位,并且会调用 parent 的 parent (也就是爷爷)的 requestLayout ,最终会这样不断的向上递归到最顶层的 DectorView , DectorView类是我们在屏幕中看见的UI视图中最根部的View,他继承自FrameLayout。 但是 DectorView 并不能自己触发自己的测量布局绘制流程,所以 DectorView 以成员变量的形式寄托在ViewRootImpl类中。 ViewRootImpl 类并不是 View 的子类,但是他是我们看到的屏幕上的UI界面的终极Boss, 正是这个类会依次触发DectorView的 measure、layout、draw 三大流程。
通过上边的步骤,当我们调用 View1 的 requestLayout 方法后,会一步步向上委托,一直到 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的时候,也就断了。所以当我调用 View1 的 requestLayout 方法时,只有 ViewGroup1 和 View1 进行了重新的测量和布局。
ok!大功告成!