相信很多同学,都是在wendux的flutter中文社区中学习入门的,但是我在看完之后,使用 flutter 开发项目时,布局依然遇到了一些困难,不知道该用哪种widget,所以详细记录了一下项目必用的布局内容。
写作风格参考了 阮一峰的flex布局
MainAxisAlignment
,控制容器中的元素如何在当前容器中布局,例如居中,从头开始,从尾开始.
MainAxisAlignment.start
Row /*or Column*/(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
MainAxisAlignment.center
Row /*or Column*/(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
MainAxisAlignment.end
Row /*or Column*/(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
MainAxisAlignment.spaceBetween
Row /*or Column*/(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
MainAxisAlignment.spaceEvenly
(两边间距相同)
Row /*or Column*/(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
MainAxisAlignment.spaceAround
(均匀分布)
Row /*or Column*/(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
baseline 用于对齐不同文本基线
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: <Widget>[
Text(
'Baseline',
style: Theme.of(context).textTheme.display3,
),
Text(
'Baseline',
style: Theme.of(context).textTheme.body1,
),
],
),
Row
和 Column
非常常用,需要多个元素进行排布时,必然会用到,他们都有两条轴,相互垂直。(主轴方向,自动占满可用区域,后面示例可发现)
对于 Row,主轴是横向的(x轴),副轴是纵向的(y轴)
对于 Column,主轴是纵向的(y轴),副轴是横向的(x轴)
MainAxisAlignment 用于控制主轴方向上元素排列
CrossAxisAlignment 用于控制 副轴 上元素排列
这里使用 Row
以及 两小,一大
图标做示例,注意 副轴
方向上的小星星位置.
CrossAxisAlignment.start
Row /*or Column*/(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 200),
Icon(Icons.star, size: 50),
],
),
CrossAxisAlignment.center
Row /*or Column*/(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 200),
Icon(Icons.star, size: 50),
],
),
CrossAxisAlignment.end
Row /*or Column*/(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 200),
Icon(Icons.star, size: 50),
],
),
CrossAxisAlignment.stretch
(容器副轴延展并占满可用区域,主轴会自动占满)
Row /*or Column*/(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 200),
Icon(Icons.star, size: 50),
],
),
MainAxisSize
MainAxisSize
用于控制容器在主轴方向上是否占满可用区域,默认为MainAxisSize.max,表示主轴方向会自动占满
Row /*or Column*/(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 200),
Icon(Icons.star, size: 50),
],
),
对于一个排列中的多个不等宽(高)元素,如何让他们等宽(高)呢?
示例:
/// 多个RaisedButton元素,具有不同的文本内容,占据的宽度不同
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('IntrinsicWidth')),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
},
child: Text('Short'),
),
RaisedButton(
onPressed: () {
},
child: Text('A bit Longer'),
),
RaisedButton(
onPressed: () {
},
child: Text('The Longest text button'),
),
],
),
),
);
使用
IntrinsicWidth
进行包裹后,即可让他们等宽。(等高同理,使用IntrinsicHeight
)。效果如下:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('IntrinsicWidth')),
body: Center(
child: IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
RaisedButton(
onPressed: () {
},
child: Text('Short'),
),
RaisedButton(
onPressed: () {
},
child: Text('A bit Longer'),
),
RaisedButton(
onPressed: () {
},
child: Text('The Longest text button'),
),
],
),
),
),
);
}
对于需要不规则,特殊定位的组件,使用
stack + position
进行布局
@override
Widget build(BuildContext context) {
Widget main = Scaffold(
appBar: AppBar(title: Text('Stack')),
);
return Stack(
fit: StackFit.expand,
children: <Widget>[
main,
Banner(
message: "Top Start",
location: BannerLocation.topStart,
),
Banner(
message: "Top End",
location: BannerLocation.topEnd,
),
Banner(
message: "Bottom Start",
location: BannerLocation.bottomStart,
),
Banner(
message: "Bottom End",
location: BannerLocation.bottomEnd,
),
],
);
}
下面这个示例,使用Posotion
进行绝对定位,需要知道定位元素的 top / left
值。(一个定位在左上角,一个定位在右下角)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Stack')),
body: Stack(
fit: StackFit.expand,
children: <Widget>[
Material(color: Colors.yellowAccent),
Positioned(
top: 0,
left: 0,
child: Icon(Icons.star, size: 50),
),
Positioned(
top: 340,
left: 250,
child: Icon(Icons.call, size: 50),
),
],
),
);
}
如果不想去猜 top/left
的值,可以使用 layoutbuilder
,它的builder中自带一个参数,可以获取当前容器的宽高。
Widget build(BuildContext context) {
const iconSize = 50;
return Scaffold(
appBar: AppBar(title: Text('Stack with LayoutBuilder')),
body: LayoutBuilder(
builder: (context, constraints) =>
Stack(
fit: StackFit.expand,
children: <Widget>[
Material(color: Colors.yellowAccent),
Positioned(
top: 0,
child: Icon(Icons.star, size: iconSize),
),
Positioned(
top: constraints.maxHeight - iconSize,
left: constraints.maxWidth - iconSize,
child: Icon(Icons.call, size: iconSize),
),
],
),
),
);
}
Expand 常用于需要按照一定比例占满某些区域。比如两栏,三栏布局,其中px值是可以这样用的.(flex可以作为px的值进行设置)
Row(
children: <Widget>[
Expanded(
child: Container(
decoration: const BoxDecoration(color: Colors.red),
),
flex: 300,
),
Expanded(
child: Container(
decoration: const BoxDecoration(color: Colors.green),
),
flex: 200,
),
Expanded(
child: Container(
decoration: const BoxDecoration(color: Colors.blue),
),
flex: 100,
),
],
),
Container
是最为常用的容器,它拥有多种属性。
Container
单纯作为一个容器
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Container as a layout')),
body: Container(
color: Colors.yellowAccent,
child: Text("Hi"),
),
);
}
Container
撑满整个可用区域
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Container as a layout')),
body: Container(
height: double.infinity,
width: double.infinity,
color: Colors.yellowAccent,
child: Text("Hi"),
),
);
}
Container
作为一个 decoration 装饰容器。颜色通过 BoxDecoration 属性去定义。
一个好的写法是:decoration 属性放在 child 之前, foregroundDecoration 属性放在 child之前
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Container.foregroundDecoration')),
body: Container(
height: double.infinity,
width: double.infinity,
decoration: BoxDecoration(color: Colors.yellowAccent),
foregroundDecoration: BoxDecoration(color: Colors.red.withOpacity(0.5)),
child: Text("Hi"),
),
);
}
Container 有一个属性:transform,可以直接控制容器转换,而不必使用
Tansform
组件
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Container.transform')),
body: Container(
height: 300,
width: 300,
transform: Matrix4.rotationZ(pi / 4),
decoration: BoxDecoration(color: Colors.yellowAccent),
child: Text(
"Hi",
textAlign: TextAlign.center,
),
),
);
}
Container将一张图片设置为背景
Scaffold(
appBar: AppBar(title: Text('image: DecorationImage')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.yellow,
image: DecorationImage(
fit: BoxFit.fitWidth,
image: NetworkImage(
'https://flutter.io/images/catalog-widget-placeholder.png',
),
),
),
),
),
);
Container 添加边框
Scaffold(
appBar: AppBar(title: Text('border: Border')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.yellow,
border: Border.all(color: Colors.black, width: 3),
),
),
),
);
Container 添加圆角边框
Scaffold(
appBar: AppBar(title: Text('borderRadius: BorderRadius')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.yellow,
border: Border.all(color: Colors.black, width: 3),
borderRadius: BorderRadius.all(Radius.circular(18)),
),
),
),
);
Container 设置圆形:
shape: BoxShape
Scaffold(
appBar: AppBar(title: Text('shape: BoxShape')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.yellow,
shape: BoxShape.circle,
),
),
),
);
Container 添加阴影 :
boxShadow: List
Scaffold(
appBar: AppBar(title: Text('boxShadow: List' )),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.yellow,
boxShadow: const [
BoxShadow(blurRadius: 10),
],
),
),
),
);
Container 添加渐变 :
boxShadow: List
渐变有3种LinearGradient(线性渐变), RadialGradient(曲线渐变), SweepGradient (?)
.
Scaffold(
appBar: AppBar(title: Text('gradient: LinearGradient')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: const [
Colors.red,
Colors.blue,
],
),
),
),
),
);
Scaffold(
appBar: AppBar(title: Text('gradient: RadialGradient')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
gradient: RadialGradient(
colors: const [Colors.yellow, Colors.blue],
stops: const [0.4, 1.0],
),
),
),
),
);
Scaffold(
appBar: AppBar(title: Text('gradient: SweepGradient')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
gradient: SweepGradient(
colors: const [
Colors.blue,
Colors.green,
Colors.yellow,
Colors.red,
Colors.blue,
],
stops: const [0.0, 0.25, 0.5, 0.75, 1.0],
),
),
),
),
);
当几个小组件之间需要 Padding 边距,Margin间隔 时,使用Sizebox可以高效复用布局.
Column(
children: <Widget>[
Icon(Icons.star, size: 50),
const SizedBox(height: 100),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
SizeBox 控制一个元素 显示/隐藏
Widget build(BuildContext context) {
bool isVisible = true;
return Scaffold(
appBar: AppBar(
title: Text('isVisible = $isVisible'),
),
body: isVisible
? Icon(Icons.star, size: 150)
: const SizedBox(),
);
}
SafeArea 用于控制设备顶部的工具栏。包裹整个容器即可。
Widget build(BuildContext context) {
return Material(
color: Colors.blue,
child: SafeArea(
child: SizedBox.expand(
child: Card(color: Colors.yellowAccent),
),
),
);
}
bool _value = false;
Offstage(
offstage: _value,
child: Image.asset("order/ic_check", width: 16.0, height: 16.0)),
api.flutter.dev 英文文档
flutter 中文文档