当你想叠放一些组件的时候, 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); } 复制代码