层叠布局(Stack、Positioned)
层叠布局允许子组件按照代码中声明的顺序堆叠起来。Flutter中使用Stack
和Positioned
这两个组件来配合实现绝对定位。Stack
允许子组件堆叠,而Positioned
用于根据Stack
的四个角来确定子组件的位置。
Stack
Stack({
super.key,
this.alignment = AlignmentDirectional.topStart,//决定如何去对齐没有定位(没有使用Positioned)或部分定位的子组件
this.textDirection,//都用于确定alignment对齐的参考系
this.fit = StackFit.loose,//用于确定没有定位的子组件如何去适应Stack的大小。StackFit.loose表示使用子组件的大小,StackFit.expand表示扩伸到Stack的大小。
this.clipBehavior = Clip.hardEdge,//决定对超出Stack显示空间的部分如何剪裁,Clip枚举类中定义了剪裁的方式,Clip.hardEdge 表示直接剪裁,不应用抗锯齿
super.children,
})
实例:
import 'package:flutter/material.dart';
class StackPage extends StatelessWidget {
const StackPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Stack'),),
body: Padding(
padding: const EdgeInsets.all(20),
child: Center(
child: Stack(
alignment: AlignmentDirectional.center,
children: [
Container(
width: 250,
height: 250,
color: Colors.amber,
),
Container(
width: 100,
height: 400,
color: Colors.purple,
),
Container(
width: 400,
height: 100,
color: Colors.red,
),
],
),
),
),
);
}
}
IndexedStack
IndexedStack
是 Stack
的子类。与 Stack
不同,IndexedStack
一次最多只显示一个小部件,并隐藏其他小部件。可以通过 index 属性指定要显示的子小部件。如果索引为null,则不会显示任何子小部件。其构造函数:
IndexedStack({
super.key,
super.alignment,
super.textDirection,
StackFit sizing = StackFit.loose,
this.index = 0,//显示子widget的索引,默认为0。如果index为null,则不显示子widget
super.children,
})
实例:
class StackPage extends StatefulWidget {
const StackPage({Key? key}) : super(key: key);
@override
State<StackPage> createState() => _StackPageState();
}
class _StackPageState extends State<StackPage> {
int index = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Stack'),),
body: Padding(
padding: const EdgeInsets.all(20),
child: Center(
child: IndexedStack(
index: index,
children: [
// index = 0
Container(
width: 400,
height: 400,
color: Colors.amber,
alignment: Alignment.center,
child: const Text('0',style: TextStyle(fontSize: 50),),
),
// index = 1
Container(
width: 400,
height: 400,
color: Colors.purple,
alignment: Alignment.center,
child: const Text('1',style: TextStyle(fontSize: 50),),
),
// index = 2
Container(
width: 400,
height: 400,
color: Colors.red,
alignment: Alignment.center,
child: const Text('2',style: TextStyle(fontSize: 50),),
)
],
),
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh_rounded),
onPressed: (){
setState(() {
if(index == 2){
index = 0;
}else{
index = index + 1;
}
});
},
),
);
}
}
Positioned(绝对定位)
const Positioned({
super.key,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
required super.child,
})
left、top 、right、 bottom分别代表离Stack左、上、右、底四边的距离。width和height用于指定需要定位元素的宽度和高度。注意,Positioned的width、height 和其他地方的意义稍微有点区别,此处用于配合left、top 、right、 bottom来定位组件。
实例:
import 'package:flutter/material.dart';
class PositionedPage extends StatelessWidget {
const PositionedPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Positioned'),),
body: Center(
child: Container(
width: 400,
height: 400,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.amber
),
child: Stack(
children: [
Positioned(
top: 0,
left: 0,
child: Container(
width: 100,
height: 100,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.purple
),
)
),
Positioned(
top: 150,
left: 150,
child: Container(
width: 200,
height: 200,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.red
),
)
),
Positioned(
top: -50,
left: 100,
child: Container(
width: 200,
height: 200,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.green
),
)
)
],
),
),
),
);
}
}
对齐与相对定位(Align)
调整一个子元素在父元素中的位置的话,使用Align组件会更简单一些。
Align组件
const Align({
super.key,
this.alignment = Alignment.center,
this.widthFactor,
this.heightFactor,
super.child,
})
widthFactor
和heightFactor
是用于确定Align
组件本身宽高的属性;它们是两个缩放因子,会分别乘以子元素的宽、高,最终的结果就是Align 组件的宽高。如果值为null,则组件的宽高将会占用尽可能多的空间。
body: Container(
color: Colors.green.shade50,
child: const Align(
alignment: Alignment.topCenter,
child: FlutterLogo(
size: 50.0,
),
),
),
body: Container(
color: Colors.green.shade50,
child: const Align(
alignment: Alignment.topCenter,
widthFactor: 2,
heightFactor: 2,
child: FlutterLogo(
size: 50.0,
),
),
),
因为FlutterLogo的宽高为 50,则Align的最终宽高都为2*50=100。
Alignment
Alignment继承自AlignmentGeometry,表示矩形内的一个点,他有两个属性x、y,分别表示在水平和垂直方向的偏移。
Alignment(this.x, this.y)
坐标转换公式:
(Alignment.x*childWidth/2+childWidth/2, Alignment.y*childHeight/2+childHeight/2)
FractionalOffset
FractionalOffset
继承自 Alignment
,它和 Alignment
唯一的区别就是坐标原点不同!FractionalOffset
的坐标原点为矩形的左侧顶点,这和布局系统的一致。
实际偏移 = (FractionalOffse.x * childWidth, FractionalOffse.y * childHeight)
body: Container(
color: Colors.green.shade50,
height: 120.0,
width: 120.0,
child: const Align(
alignment: FractionalOffset(0.2, 0.6),
child: FlutterLogo(
size: 50.0,
),
),
),
FractionalOffset(0.2, 0.6)带入坐标转换公式得FlutterLogo实际偏移为(12,36)。