当你想叠放一些组件的时候, stack 组件非常有用。相当于 web 中 的 relative
和absolute
定位产生的效果。
为了方便说明,约定 stack 有两种子组件,定位子组件和非定位子组件,区别就是是否被有定位属性的Positioned
包起来。
// 非定位组件
Stack( children:[ const Text('hello')])
// 非定位组件,Positioned 为没有定位属性,会被忽略。
Stack( children:[ const Positioned( child: Text('hello'))])
// 定位组件
Stack( children:[ const Positioned( left: 0, child: Text('hello'))])
Stack 组件的大小
如果父级传过来的是 tight 约束,那么 Stack 就不再考虑子组件的尺寸,Stack 的大小就是 tight 约束的大小。
如果 是 loose 约束,有两种情况:
- 如果 Stack 只有定位组件,那么 Stack 会倾向于最大。
- 如果 Stack 有非定位组件,那么它会尝试缩小自己,让自己能 wrap 所有非定位组件。
比如下面的 stack 会尽量缩小自己,Positioned 没有定位属性,也属于非定位组件。
Center(
child: Stack(alignment: AlignmentDirectional.bottomCenter, children: [
Positioned(
child: Container(
color: Colors.green,
height: 100,
width: 100,
))
]),
)
在没有约束且只有定位组件的情况下,stack 会报错。比如下面的例子就会报错。
UnconstrainedBox(
child: Stack(
children: const [
Positioned(left: 0, child: Text('hello')),
],
))
子组件绘制顺序
Stack(
children: [
Container(width: 160,height: 160,color: Colors.blue,),
Container(width: 140,height: 140,color: Colors.red,),
Container(width: 120,height: 120,color: Colors.green,),
],)
前面的子组件最先绘制,所以看起来在最底下。
如果把一个定位子组件排在第 4 位,那么它就会排在第 4 个绘制。在绘制顺序上定位组件和非定位组件没有差别。
子组件对齐方式
子组件默认的定位方式是 topLeft,可以通过 alignment 参数修改。
Stack(
alignment: Alignment.center,
...
如果你指定了alignment却没有效果,可能是因为 Stack 和子组件一样大
如果把 其中的 Containter
换成 Positioned(child:Container)
效果一样,同样受 alignment
属性控制。因为 Positioned
没有定位属性。
子组件的大小
Stack 默认给非定位子组件的约束是 loose,fit: StackFit.loose
,在允许范围内子组件可以自己决定大小。
Stack 也可以传给非定位子组件 tight 约束,fit: StackFit.expand
这时非定位子组件大小不能自己决定,而是直接充满整个 Stack。
fit 的可选值还有一个 StackFit.passthrough
。这个时候, Stack 把父约束透传给子组件。
定位子组件的大小
定位子组件比较超然,不受fit
值的影响。
定位子组件是以 Stack 的左上角为原点计算 x,y值。
定位子组件的大小与 top,bottom,left,right,width,height 这几个值有关。
满足下面两条 定位子组件 获得 tight 约束
- 同时指定 top,bottom,或单独指定 height
- 同时指定 left,right,或单独指定 width
根据 top,bottom 可以得出 height = child.size.height-top-bottom
根据 left,right 可以得出 width = child.size.width-left-right
如果不满足上面两条,子组件就彻底放飞了,没有约束。
比如下面的蓝块因为只有bottom一个值,所以没有约束,除了下面,其它三面可以无限延伸。
Center(child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.bottomCenter,
children: [
Container(
color: Colors.green,
width: 300,
height: 100,
),
Positioned(
bottom: 0,
child: Container(
color: Colors.blue,
height: 200,
width: 150,
)
)
],
),
)
IndexedStack
IndexedStack 是 Stack 的子类,和 Stack 唯一不同的是 IndexedStack 只绘制 index 属性指定的 child,而不是绘制所有 child. 其它都一样。
RenderIndexedStack 类 override paintStack 方法,从默认的绘制所有子组件,修改成只绘制当前组件。
@override
void paintStack(PaintingContext context, Offset offset) {
if (firstChild == null || index == null) {
return;
}
//找到 index 属性指定的 child 只绘制这一个
final RenderBox child = _childAtIndex();
final StackParentData childParentData = child.parentData! as StackParentData;
context.paintChild(child, childParentData.offset + offset);
}
参考
- Stack Class
- Render Stack Class
- Positioned Class
- Indexdstack Class