LinearLayout实现原理分析

LinearLayout 是 Android 中最常用的布局之一,它负责按照水平或垂直方向排列其子视图。LinearLayout 的实现原理主要集中在测量和布局两个阶段,这两个阶段分别对应于 onMeasure()onLayout() 方法。

LinearLayout 的测量过程 (onMeasure())

LinearLayoutonMeasure() 方法中,主要任务是测量所有子视图的大小,并根据父布局传递的测量规格来确定自身的大小。测量过程大致分为以下几个步骤:

  1. 测量模式和大小计算

    • 获取从父布局传递下来的测量规格和尺寸。
    • 计算可用的空间尺寸,考虑布局参数中的权重(weight)。
  2. 子视图测量

    • 遍历所有子视图,根据当前的测量模式和尺寸,调用每个子视图的 measure() 方法进行测量。
    • 如果设置了权重(weight),则需要根据可用空间和权重比重新分配尺寸。
  3. 确定自身大小

    • 根据子视图的测量结果和布局方向,确定 LinearLayout 自身的大小。
    • 考虑到 padding 和 margin 的影响。
  4. 设置测量尺寸

    • 最终调用 setMeasuredDimension() 方法来设置 LinearLayout 的测量尺寸。

LinearLayout 的布局过程 (onLayout())

LinearLayoutonLayout() 方法中,主要任务是根据测量结果,将子视图放置在正确的位置。布局过程如下:

  1. 初始化变量

    • 初始化一些变量,如当前的 x 和 y 坐标,用于跟踪子视图的放置位置。
  2. 遍历子视图

    • 遍历所有子视图,根据子视图的测量尺寸和布局方向(水平或垂直),计算并设置子视图的最终位置。
    • 考虑到子视图的 margin,以确保正确的间距。
  3. 放置子视图

    • 调用 layout() 方法,传入子视图的左、顶、右、底坐标,完成子视图的放置。

源码分析

以下是 LinearLayoutonMeasure()onLayout() 方法的基本流程,基于源码的简化描述:

onMeasure()
1@Override
2protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3    // 计算可用宽度和高度
4    final int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
5    final int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
6    
7    // 考虑到 padding
8    final int widthPadding = getPaddingLeftWithForeground() + getPaddingRightWithForeground();
9    final int heightPadding = getPaddingTopWithForeground() + getPaddingBottomWithForeground();
10    
11    // 测量子视图
12    measureChildren(widthMeasureSpec, heightMeasureSpec);
13    
14    // 根据子视图的测量结果确定 LinearLayout 的大小
15    setMeasuredDimension(
16        resolveSizeAndState(getDesiredWidth(), widthMeasureSpec, 0),
17        resolveSizeAndState(getDesiredHeight(), heightMeasureSpec, 0)
18    );
19}
onLayout()
1@Override
2protected void onLayout(boolean changed, int l, int t, int r, int b) {
3    final int count = getChildCount();
4    final int parentLeft = getPaddingLeftWithForeground();
5    final int parentRight = r - l - getPaddingRightWithForeground();
6    final int parentTop = getPaddingTopWithForeground();
7    final int parentBottom = b - t - getPaddingBottomWithForeground();
8    final int width = parentRight - parentLeft;
9    final int height = parentBottom - parentTop;
10    
11    int childLeft = parentLeft;
12    int childTop = parentTop;
13    
14    for (int i = 0; i < count; i++) {
15        final View child = getChildAt(i);
16        
17        if (child.getVisibility() != GONE) {
18            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
19            
20            final int childWidth = child.getMeasuredWidth();
21            final int childHeight = child.getMeasuredHeight();
22            
23            child.layout(childLeft + lp.leftMargin, childTop + lp.topMargin,
24                         childLeft + lp.leftMargin + childWidth,
25                         childTop + lp.topMargin + childHeight);
26            
27            if (isHorizontal()) {
28                childLeft += childWidth + lp.rightMargin;
29            } else {
30                childTop += childHeight + lp.bottomMargin;
31            }
32        }
33    }
34}

请注意,这里的代码是基于对源码的理解进行了简化和重写,以帮助理解 LinearLayout 的核心逻辑。实际的源码会更复杂,包括对权重的处理、性能优化以及对多种特殊情况的处理。

LinearLayout 的测量和布局

1. 测量子视图 (measureChildren)

LinearLayoutonMeasure() 方法中,measureChildren() 方法用于测量所有子视图。这个方法会调用每个子视图的 measure() 方法,根据父布局提供的测量规格来确定子视图的尺寸。

1void measureChildren(int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
2    final int count = getChildCount();
3    for (int i = 0; i < count; i++) {
4        final View child = getChildAt(i);
5        if (child.getVisibility() != GONE) {
6            LayoutParams lp = (LayoutParams) child.getLayoutParams();
7            child.measure(getChildMeasureSpec(parentWidthMeasureSpec,
8                    mTotalLength + getPaddingLeft() + getPaddingRight(), lp.width),
9                    getChildMeasureSpec(parentHeightMeasureSpec,
10                            getPaddingTop() + getPaddingBottom(), lp.height));
11        }
12    }
13}

这里的关键在于 getChildMeasureSpec() 方法,它会根据子视图的宽度和高度规格、父布局的测量规格、以及子视图的 layout params 来生成子视图的测量规格。

2. 布局子视图 (onLayout)

LinearLayoutonLayout() 方法中,子视图的位置是根据布局方向、子视图的大小、以及子视图与父布局之间的 margin 来确定的。

1@Override
2protected void onLayout(boolean changed, int l, int t, int r, int b) {
3    final int count = getChildCount();
4    final int parentLeft = getPaddingLeft();
5    final int parentRight = r - l - getPaddingRight();
6    final int parentTop = getPaddingTop();
7    final int parentBottom = b - t - getPaddingBottom();
8    final int parentWidth = parentRight - parentLeft;
9    final int parentHeight = parentBottom - parentTop;
10
11    int widthUsed = 0;
12    int heightUsed = 0;
13    final boolean measureHeight = isModeExactly(mMeasureSpecModeWidth);
14    final boolean measureWidth = isModeExactly(mMeasureSpecModeHeight);
15
16    // 遍历所有子视图,布局它们
17    for (int i = 0; i < count; i++) {
18        final View child = getChildAt(i);
19        if (child.getVisibility() != GONE) {
20            LayoutParams lp = (LayoutParams) child.getLayoutParams();
21            final int childLeft;
22            final int childTop;
23            final int childWidth;
24            final int childHeight;
25
26            if (mOrientation == VERTICAL) {
27                childLeft = parentLeft + lp.leftMargin;
28                childTop = parentTop + heightUsed + lp.topMargin;
29                childWidth = parentWidth - lp.leftMargin - lp.rightMargin;
30                childHeight = child.getMeasuredHeight();
31                heightUsed += childHeight + lp.topMargin + lp.bottomMargin;
32            } else {
33                childLeft = parentLeft + widthUsed + lp.leftMargin;
34                childTop = parentTop + lp.topMargin;
35                childWidth = child.getMeasuredWidth();
36                childHeight = parentHeight - lp.topMargin - lp.bottomMargin;
37                widthUsed += childWidth + lp.leftMargin + lp.rightMargin;
38            }
39
40            child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
41        }
42    }
43}

这里的关键在于确定子视图的左上角坐标 (childLeft, childTop) 和子视图的宽高 (childWidth, childHeight)。这些值取决于布局的方向(水平或垂直)和子视图的布局参数。

3. 响应权重 (weight)

当子视图具有权重属性时,LinearLayout 会根据可用的空间和权重的比例来分配额外的空间给子视图。权重响应发生在测量子视图的阶段。

1final int widthSpec;
2final int heightSpec;
3
4if (mWeightSum > 0 && measureWidth) {
5    final int widthWithoutWeights = mTotalLength + getPaddingLeft() + getPaddingRight();
6    final int widthAvailable = parentWidth - widthWithoutWeights;
7    widthSpec = MeasureSpec.makeMeasureSpec((int) ((float) widthAvailable * weight), MeasureSpec.EXACTLY);
8} else {
9    widthSpec = getChildMeasureSpec(parentWidthMeasureSpec,
10            mTotalLength + getPaddingLeft() + getPaddingRight(), lp.width);
11}
12
13if (mWeightSum > 0 && measureHeight) {
14    final int heightWithoutWeights = mTotalLength + getPaddingTop() + getPaddingBottom();
15    final int heightAvailable = parentHeight - heightWithoutWeights;
16    heightSpec = MeasureSpec.makeMeasureSpec((int) ((float) heightAvailable * weight), MeasureSpec.EXACTLY);
17} else {
18    heightSpec = getChildMeasureSpec(parentHeightMeasureSpec,
19            getPaddingTop() + getPaddingBottom(), lp.height);
20}
21
22child.measure(widthSpec, heightSpec);

在上面的代码片段中,mWeightSum 表示所有子视图权重的总和,widthAvailableheightAvailable 分别表示可用于分配给有权重的子视图的剩余宽度和高度。如果权重总和大于零,并且测量模式是确切的,那么会根据权重比例分配空间给子视图。

总结

LinearLayout 的实现原理主要集中在如何测量和布局其子视图,以满足指定的布局方向和权重响应。通过理解 onMeasure()onLayout() 方法,以及子视图测量规格的生成,可以更好地掌握 LinearLayout 的内部工作原理。

相关推荐

  1. LinearLayout实现原理分析

    2024-07-18 12:42:05       21 阅读
  2. ImageView实现原理分析

    2024-07-18 12:42:05       21 阅读
  3. LinearLayout和RelativeLayout对比

    2024-07-18 12:42:05       43 阅读
  4. Qt源码分析:QMetaObject实现原理

    2024-07-18 12:42:05       33 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-18 12:42:05       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-18 12:42:05       71 阅读
  3. 在Django里面运行非项目文件

    2024-07-18 12:42:05       58 阅读
  4. Python语言-面向对象

    2024-07-18 12:42:05       69 阅读

热门阅读

  1. 存储ODS数据的时候为什么在Hive中建立Iceberg表

    2024-07-18 12:42:05       19 阅读
  2. 基于 Gunicorn、Flask 和 Docker 的高并发部署模型

    2024-07-18 12:42:05       21 阅读
  3. 残月之肃-C++

    2024-07-18 12:42:05       18 阅读
  4. 升本1.0.5-规划-英语-207天

    2024-07-18 12:42:05       22 阅读
  5. CmakeLists

    2024-07-18 12:42:05       25 阅读
  6. C语言:进程间通信

    2024-07-18 12:42:05       19 阅读
  7. OPenCV批量实现直方图均衡化----20240718

    2024-07-18 12:42:05       17 阅读
  8. vue中:class、watch、v-show使用

    2024-07-18 12:42:05       20 阅读