美文网首页
Layout过程

Layout过程

作者: Utte | 来源:发表于2018-08-07 22:00 被阅读56次

一、调用流程

Layout流程也是从perfromTravrsals开始,在调用完测量流程后,又调用了performLayout(),这就是Layout流程的起点。

1. ViewRootImpl # performTraversals()

performTraversals()调用了performLayout()。

// ...
if (didLayout) {
    // 参数分别是窗口的params和窗口的测量宽高
    performLayout(lp, mWidth, mHeight);
    // ...
}
// ...
2. ViewRootImpl # performLayout()

performLayout()调用了DecorView的layout()。

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    // ...
    // host就是DecorView
    final View host = mView;
    if (host == null) {
        return;
    }
    
    // ...
    
    try {
        // 调用DecorView的layout(),传入窗口四个顶点的坐标。
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        
        // ...
        
    } finally {
        // ...
    }
    mInLayout = false;
}
3. ViewGroup # layout()

DecorView自身并没有重写layout(),它的父类FrameLayout也没有,所以其实调用的是ViewGroup的layout()。和measure()类似的是,这也是一个final的方法 ,将窗口的顶点坐标传递到View,调用了View的layout()。

@Override
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        // 处理动画
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        // 调用View的layout()
        super.layout(l, t, r, b);
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}
4. View # layout()

layout()中会先调用setFrame()将传入的窗口坐标设置到全局变量,再调用onLayout()去分发layout。

public void layout(int l, int t, int r, int b) {
    // ...
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        // ...
    }
    // ...
}
5. DecorView # onLayout()

可以写一个demo做下实验,经过上面的步骤,onLayout()调用的确实是DecorView的onLayout()。调用了父类(FrameLayout)的onLayout()。

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    // ...
}
6. FrameLayout # onLayout()

FrameLayout的onLayout()简单的调用勒兹的layoutChildren(),从方法名也能看出开始分发处理子View的layout了。

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
7. FrameLayout # layoutChildren()

在这个方法里完成了layout的分发。遍历子View,调用子View的layout()。

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    
    // ...
    
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();
            
            // 从水平和竖直方向对变量做处理...
            
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

二、View和ViewGroup中的相关方法

View # layout()
  • 重新设置了最新的四个相对坐标。
  • 调用onDraw()。
public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
    // mXXX是此View相对于父View的相对坐标
    // 先保存下来
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    // setFrame()将mXXX变为传入的参数
    // 四个顶点确定了,View的位置也就确定了
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        // 调用onLayout()
        onLayout(changed, l, t, r, b);
        // ...
    }
    // ...
}
View # setFrame()

实际上就是重新赋值mLeft、mTop、mRight、mBottom,

protected boolean setFrame(int left, int top, int right, int bottom) {
    // ...
    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;
        // ...
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        // ...
        }
        // ...
    }
    return changed;
}
View # onLayout()

View中的onLayout()是空实现,具体实现和具体布局有关,所以没有做统一实现。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
ViewGroup # layout()

ViewGroup中并没有做很多工作,而且是一个final的方法,调用了View的layout()。

@Override
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        super.layout(l, t, r, b);
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}
ViewGroup # onLayout()

ViewGroup中的onLayout()是一个抽象方法,ViewGroup是一个抽象类,所以所以继承它的类都需要实现onLayout()。

@Override
protected abstract void onLayout(boolean changed,
        int l, int t, int r, int b);

三、测量宽高和实际宽高。

1. 获取方式

测量宽高:

width = view.getMeasuredWidth();
height = view.getMeasuredHeight();
public final int getMeasuredWidth() {
    return mMeasuredWidth & MEASURED_SIZE_MASK;
}

实际宽高

width = view.getWidth();
height = view.getHeight();
public final int getWidth() {
    return mRight - mLeft;
}
2. 异同点

同:正常情况下测量宽高与实际宽高相等。

mLeft、mRight、mTop、mBottom这四个值是从layout()的参数而来的,而大部分调用layout时,传的参数都是根据getMeasureWidth()和getMeasureHeight()来决定的,所以正常情况下都有mRight - mLeft == mMeasureWidth & MEASURED_SIZE_MASK,除非修改layout()参数计算的规则。

异:确定时机不同。

测量宽高在measure过程中形成,而实际宽高是在layout赋值mXXX四个顶点坐标后形成的。

相关文章

网友评论

      本文标题:Layout过程

      本文链接:https://www.haomeiwen.com/subject/jqjzvftx.html