# 层叠布局

本文作者:阳九五 (opens new window)

本站地址:https://blog.56321654.xyz (opens new window)

# Stack

Stack是一个可将子组件叠在一起显示的容器组件。

# 布局算法

Stack里的子组件在布局时会被分为“有位置”和“无位置”这2大类。

  • “有位置”组件 - Positioned组件
  • “无位置”组件 - 非Positioned组件

布局时,Stack首先会找到所有的“无位置”的子组件,并向它们传入fit属性所设置的布局约束,允许它们一定程度内自由选择自身的尺寸,并让它们依次汇报最终确定的尺寸结果。

在得到全部“无位置”子组件所确定的最终尺寸后,Stack会把自身尺寸匹配到其中最大的子组件的尺寸,再把其他的子组件按照aligenment属性设置的对齐方式拜访。

# fit属性

尺寸适配属性用于控制Stack如何将自己的父级组件的尺寸约束传达给“无位置”的子组件,类型是StackFit,默认值是StackFit.loose(宽松)。

  • StackFit.loose: 未定位的子组件可以自由地选择它们的大小,不受Stack大小的限制。子组件会根据它们自己的属性和内容来确定大小,而不会尝试去填充或扩展到Stack的边界。

  • StackFit.expand: 未定位的子组件会尽可能地扩展以填充Stack的可用空间。这意味着如果Stack有一个确定的大小,子组件会尝试扩展到这个大小,通常是通过设置子组件的double.infinity作为约束条件。这通常用于让子组件填满整个Stack的空间。

  • StackFit.passthrough: 这个值表示父组件(即Stack)的约束条件会直接传递给未定位的子组件,而不会进行任何调整。这意味着子组件将根据父组件的约束条件来确定自己的大小。如果父组件的约束条件是宽松的,子组件可能会比Stack的实际大小小;如果约束条件是严格的,子组件可能会被压缩以适应Stack的大小。

# alignment属性

如果子组件的尺寸小于Stack本身,alignment属性可用指定对齐方式。

Alignment由两个值组成:x和y,它们分别表示水平和垂直方向上的相对位置。

Alignment的x和y值的范围是从-1.0到1.0,其中:

x = -1.0 表示最左边。
x = 0.0 表示中间。
x = 1.0 表示最右边。

同样地,

y = -1.0 表示最顶部。
y = 0.0 表示中间。
y = 1.0 表示最底部。

以下是一些常用的Alignment值的例子:

Alignment.topLeft:表示左上角,值为(-1.0, -1.0)。
Alignment.topCenter:表示顶部中间,值为(0.0, -1.0)。
Alignment.topRight:表示右上角,值为(1.0, -1.0)。
Alignment.centerLeft:表示中间左侧,值为(-1.0, 0.0)。
Alignment.center:表示正中心,值为(0.0, 0.0)。
Alignment.centerRight:表示中间右侧,值为(1.0, 0.0)。
Alignment.bottomLeft:表示左下角,值为(-1.0, 1.0)。
Alignment.bottomCenter:表示底部中间,值为(0.0, 1.0)。
Alignment.bottomRight:表示右下角,值为(1.0, 1.0)。

# 叠放次序

Stack在渲染时会将子组件按照children属性里的列表顺序依次绘制并覆盖叠放,因此列表里第一个组件首先被绘制,于是出现在最底层,容易被其他组件覆盖。

# 溢出

默认情况下,溢出Stack尺寸边界的子组件会被裁剪,不予显示。这一行为是由clipBehavior属性控制的,默认为Clip.hardEdge

名称 说明
Clip.none 不进行裁剪。如果子组件没有超出父组件的边界,这个选项不会有任何性能消耗。
Clip.hardEdge 进行裁剪但不应用抗锯齿。这种方式的裁剪速度比Clip.none慢一点,但比其他抗锯齿的方式快。
Clip.antiAlias 进行裁剪并且应用抗锯齿,使得边缘更平滑。这种方式的裁剪速度比Clip.antiAliasWithSaveLayer快,但比Clip.hardEdge慢,通常用于处理圆形和弧形裁剪。
Clip.antiAliasWithSaveLayer 进行裁剪、抗锯齿,并且在裁剪之后立即保存一个saveLayer。这种方式的性能开销最大,因为它涉及到额外的缓冲区,所以使用的情况较少。

# 示例

class CustomWidget extends StatelessWidget {
  const CustomWidget({super.key});

  
  Widget build(BuildContext context) {
    return SizedBox(
        child: Stack(
          // 居中对齐
          alignment: Alignment.center,
          // 子元素溢出, none 不裁剪
          clipBehavior: Clip.none,
          // 子元素层叠放
          children: [
            // 三个色块
            Container(
              width: 300,
              height: 300,
              color: Colors.amber,
            ),
            Container(
              width: 200,
              height: 200,
              color: Colors.blue,
            ),
            Container(
              width: 100,
              height: 100,
              color: Colors.green,
            ),
            // 绝对定位
            const Positioned(
              right: -50,
              bottom: -50,
              child: FlutterLogo(size: 100),
            ),
          ],
        ),
      );
  }
}
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

Image

# Positioned

若需要精准控制若干个子组件在Stack中的位置,则开发者可选用Positioned组件。

定位宽高都为空时,Stack不会再把Positioned这个组件当作“有位置”的组件,而是直接当成Positioned不存在,对其子组件按照“无位置”方式处理。

# 定位

名称 说明
letf 距离左边距
top 距离上边距
right 距离右边距
bottom 距离下边距
  • top和bottom同时传入时,会迫使Positioned的child的高度约束至相应的高度。
  • left和right同时传入时,额外增加对其子组件的约束,间接设置了它的宽度

Positioned当横轴和纵轴方向其中某个维度没有传入相关参数时,Stack会依照自身的alignment属性值处理默认的维度。

# 宽高

名称 说明
width 宽度
height 高度

同一纬度的3个属性(横轴left、right、width 和 纵轴top、bottom、height属性)最多只可以传入2个,否则会产生运行时错误。

# 示例

class CustomWidget extends StatelessWidget {
  const CustomWidget({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      width: 200,
      height: 200,
      color: Colors.black26,
      child: const Stack(
        children: [
          // 左上
          Positioned(
            left: 20,
            top: 20,
            width: 50,
            height: 50,
            child: FlutterLogo(),
          ),
          // 右下
          Positioned(
            right: 20,
            bottom: 20,
            width: 50,
            height: 50,
            child: FlutterLogo(),
          ),
          // 同时设置上、下、左、右
          Positioned(
            top: 75,
            right: 75,
            bottom: 75,
            left: 75,
            child: FlutterLogo(),
          ), 
        ],
      ),
    );
  }
}
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

Image

最近更新: 8/6/2025, 2:39:35 PM