
Stack 是一个经常被用到的组件,我看可以通过它来叠合若干个组件。源码中对它的介绍是:
一个将其子部件,相对于盒边缘进行定位的组件。
也就是说 Stack 组件擅长的是 定位 组件。下面是 Stack 组件类的定义和 构造方法,可以看出它继承自 MultiChildRenderObjectWidget。所以可接受一个组件列表。另外还有一些配置参数,在下面将一一介绍 。

如下,通过 Stack 将蓝色盒子、红色盒子和一个图标叠放在一起。可以看出,默认情况下组件会以 Stack 区域的左上角进行对齐叠放,且会根据列表元素的顺序依次叠放。

Stack(
children: <Widget>[
Container( width: 100, height: 100, color: Colors.red ),
Container( width: 60, height: 60, color: Colors.green ),
Icon(Icons.ac_unit,color: Colors.white )
],
);
textDirection属性
根据不同国家的阅读习惯,给出 textDirection 来确定排布方向。其类型为 TextDirection 枚举,有两个元素,rtl 代表从右向左,ltr 代表从左向右,我们的阅读习惯自然是 ltr 。
enum TextDirection {
/// The text flows from right to left (e.g. Arabic, Hebrew).
rtl,
/// The text flows from left to right (e.g., English, French).
ltr,
}当把 textDirection 设为 rtl 时,效果如下,会以 Stack 区域的右上角进行对齐叠放。

Stack(
textDirection: TextDirection.rtl,
children: <Widget>[
Container( width: 100, height: 100, color: Colors.red ),
Container( width: 60, height: 60, color: Colors.green ),
Icon(Icons.ac_unit,color: Colors.white )
],
);
alignment属性
alignment 属性我们应该非常熟悉,它的类型是 AlignmentGeometry ,用于控制对齐方式。比如将 alignment 设置为 Alignment.center 常量,就会在 Stack 中居中排布。

Stack(
alignment: Alignment.center,
children: <Widget>[
Container( width: 100, height: 100, color: Colors.red ),
Container( width: 60, height: 60, color: Colors.green ),
Icon(Icons.ac_unit,color: Colors.white )
],
);另外 Alignment 除了八个方位外,可以自己指定对齐方式。之前再 《出神入化的Align》 一文中详细介绍了 alignment 属性的用法,这里不再赘述。比如下面是 Alignment(0.2,0.2) 的效果。

fit 属性类型为 StackFit 枚举,有如下三个元素。默认情况下是 loose 。
enum StackFit {
loose,
expand,
passthrough,
}先看个例子:如下,在 Stack 外层通过 ConstrainedBox 施加一个宽高固定的约束,毫无疑问 Stack 在被强制约束后,其占位区域被固定。

ConstrainedBox(
constraints: BoxConstraints.tightFor(width:200,height:150),
child: Stack(
fit: StackFit.loose,
children: <Widget>[
Container(width: 100, height: 100, color: Colors.red),
Container(width: 60, height: 60, color: Colors.green),
Icon(Icons.ac_unit,color: Colors.white,)
],
));可以看出,在 StackFit.loose 的情况下,子组件并未受到该固定尺寸的约束,而是以该固定区域为上限的松散约束,如下红色所示。

此时,在 StackFit.expand 的情况下,子组件也会受到一个固定尺寸的强约束。

Container 在该约束下,会变成固定的宽高,即使蓝色自身要求是 60*60 。

其实稍微瞄一下 RenderStack 的源码我们就能知道 fit 具体的处理逻辑。在 loose 时,会通过 constraints.loosen() 将创建一个松散约束,该方法就是将最小宽高设为 0 ,最大宽高设为对应最大值。而 expand 会将自身约束的最大区域最为固定宽高约束。最后 passthrough 则什么都不处理,直接将自身约束施加给子节点。

BoxConstraints loosen() {
assert(debugAssertIsValid());
return BoxConstraints(
minWidth: 0.0,
maxWidth: maxWidth,
minHeight: 0.0,
maxHeight: maxHeight,
);我们可以清楚地看到 overflow 属性是一个过时的属性,而 clipBehavior 就是替代它的。clipBehavior 是什么我就不多说了,详见 ClipPath 一文。

那为什么需要 clipBehavior 进行裁剪呢?通过 Positioned 组件可以对子组件进行精确定位,甚至是负数。如下左边偏移 -20 ,右边偏移 -10 。默认裁剪行为是 hardEdge ,效果如下:

class StackDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return
Container(
color: Colors.grey.withAlpha(33),
constraints: BoxConstraints.tightFor(width:200,height:150),
child: Stack(
children: <Widget>[
Container(width: 100, height: 100, color: Colors.red),
Positioned(
left: -20,
top: -10,
child: Container(width: 60, height: 60, color: Colors.green)),
Icon(Icons.ac_unit,color: Colors.white,)
],
));
}
}当 clipBehavior: Clip.none, 时,不会进行裁剪,出界的就会被显示出来。注意一点,出界的部分是无法响应点击事件的。如果需要点击,有其他组件能够解决。

那 Stack 的使用方式到这里就介绍完毕,那本文到这里就结束了,谢谢观看,明天见~