Loading...
avatar

Android图形系统—View.layout解析

图文概括

  1. 单一View通过layout方法确定位置,ViewGroup子类通过重写抽象onLayout方法来实现子视图以及自己的位置分配逻辑
  2. getWidth|getHeight与getMeasuredWidth|getMeasureHeight区别
方法 概念 时机 场景
getMeasuredWidth和Height 测量的宽和高 measure过程 onMeasure使用
getWidth和Height 计算的宽和高 layout过程 onLayout使用

源码分析

View.measure过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 计算View的四个顶点为孩子(left、top、right、bottom)
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;
}
// 保存上一次View的四个位置
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 设置当前视图View的l、t、r、b位置,及判断布局是否有改变
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// View大小 或者 位置发生变化 则重新布局View
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 1. 对于单一View,onLayout是一个空实现
// 2. 对于ViewGroup,因为位置的确定与布局有关,所以onLayout是一个抽象方法,需要重写实现
onLayout(changed, l, t, r, b);
...
}

...
}

// 通过传入的4个顶点位置设置View的四个顶点位置 ,返回位置是否变化
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
// 是否改变
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;

// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;

int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

// 位置变化,执行invalidate
invalidate(sizeChanged);
// 设置新的顶点位置
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
...

}
return changed;
}

// 单一View在layout已经确定了位置,所以onLayout空实现
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

}

ViewGroup.measure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// measure过程调用的View.measure,不同在于onLayout
@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;
}

}

// 抽象方法,子类需要实现以实现child的layout计算
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);

// 大体过程
protected void onLayout(boolean changed,
int l, int t, int r, int b){
for (int i=0; i<getChildCount(); i++) {
View child = getChildAt(i);
// 计算子View的测量宽高
// 执行child的layout方法,child.layout
}
}

推荐阅读:图形系统总结

Author: Afree
Link: https://afree8909.github.io/blog/2019/11/19/Android%E5%9B%BE%E5%BD%A2%E7%B3%BB%E7%BB%9F%E2%80%94View.layout%E8%A7%A3%E6%9E%90/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment