对于拥有多个子 Widget 的布局类容器而言,其布局行为无非就是两种规则的抽象:水平方向上应该如何布局、垂直方向上应该如何布局。
如同 Android 的 LinearLayout、前端的 Flex 布局一样,Flutter 中也有类似的概念,即将子 Widget 按行水平排列的 Row,按列垂直排列的 Column,以及负责分配这些子 Widget 在布局方向(行 / 列)中剩余空间的 Expanded。
Row 与 Column 的使用方法很简单,我们只需要将各个子 Widget 按序加入到 children 数组即可。在下面的代码中,我们把 4 个分别设置了不同的颜色和宽高的 Container 加到 Row 与 Column 中:
//Row的用法示范
Row(
children: <Widget>[
Container(color: Colors.yellow, width: 60, height: 80,),
Container(color: Colors.red, width: 100, height: 180,),
Container(color: Colors.black, width: 60, height: 80,),
Container(color: Colors.green, width: 60, height: 80,),
],
);
//Column的用法示范
Column(
children: <Widget>[
Container(color: Colors.yellow, width: 60, height: 80,),
Container(color: Colors.red, width: 100, height: 180,),
Container(color: Colors.black, width: 60, height: 80,),
Container(color: Colors.green, width: 60, height: 80,),
],
);
Row示例:
可以看到,单纯使用 Row 和 Column 控件,在子 Widget 的尺寸较小时,无法将容器填满,视觉样式比较难看。对于这样的场景,我们可以通过 Expanded 控件,来制定分配规则填满容器的剩余空间。
比如,我们希望 Row 组件(或 Column 组件)中的绿色容器与黄色容器均分剩下的空间,于是就可以设置它们的弹性系数参数 flex 都为 1,这两个 Expanded 会按照其 flex 的比例(即 1:1)来分割剩余的 Row 横向(Column 纵向)空间:
Row(
children: <Widget>[
Expanded(flex: 1, child: Container(color: Colors.yellow, height: 60)), //设置了flex=1,因此宽度由Expanded来分配
Container(color: Colors.red, width: 100, height: 180,),
Container(color: Colors.black, width: 60, height: 80,),
Expanded(flex: 1, child: Container(color: Colors.green,height: 60),)/设置了flex=1,因此宽度由Expanded来分配
],
);
于 Row 与 Column 而言,Flutter 提供了依据坐标轴的布局对齐行为,即根据布局方向划分出主轴和纵轴:主轴,表示容器依次摆放子 Widget 的方向;纵轴,则是与主轴垂直的另一个方向。
我们可以根据主轴与纵轴,设置子 Widget 在这两个方向上的对齐规则 mainAxisAlignment 与 crossAxisAlignment。比如,主轴方向 start 表示靠左对齐、center 表示横向居中对齐、end 表示靠右对齐、spaceEvenly 表示按固定间距对齐;而纵轴方向 start 则表示靠上对齐、center 表示纵向居中对齐、end 表示靠下对齐。
主轴:mainAxisAlignment ; 纵轴:crossAxisAlignment
对于Row控件:
主轴对齐方式:
纵轴对齐方式:
这里需要注意的是,对于主轴而言,Flutter 默认是让父容器决定其长度,即尽可能大,类似 Android 中的 match_parent。
在上面的例子中,Row 的宽度为屏幕宽度,Column 的高度为屏幕高度。主轴长度大于所有子 Widget 的总长度,意味着容器在主轴方向的空间比子 Widget 要大,这也是我们能通过主轴对齐方式设置子 Widget 布局效果的原因。
示例:
Column(
//测试Row对齐方式,排除Column默认居中对齐的干扰
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(" hello world "),
Text(" I am Jack "),
],
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(" hello world "),
Text(" I am Jack "),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
textDirection: TextDirection.rtl,
children: <Widget>[
Text(" hello world "),
Text(" I am Jack "),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
verticalDirection: VerticalDirection.up,
children: <Widget>[
Text(" hello world ", style: TextStyle(fontSize: 30.0),),
Text(" I am Jack "),
],
),
],
);
实际效果:
解释:第一个Row很简单,默认为居中对齐;第二个Row,由于mainAxisSize值为MainAxisSize.min,Row的宽度等于两个Text的宽度和,所以对齐是无意义的,所以会从左往右显示;第三个Row设置textDirection值为TextDirection.rtl,所以子组件会从右向左的顺序排列,而此时MainAxisAlignment.end表示左对齐,所以最终显示结果就是图中第三行的样子;第四个 Row 测试的是纵轴的对齐方式,由于两个子 Text 字体不一样,所以其高度也不同,我们指定了verticalDirection值为VerticalDirection.up,即从低向顶排列,而此时crossAxisAlignment值为CrossAxisAlignment.start表示底对齐。
===============================================================
Column
Column可以在垂直方向排列其子组件。参数和Row一样,不同的是布局方向为垂直,主轴纵轴正好相反,读者可类比Row来理解,下面看一个例子: Column的主轴是垂直方向的,纵轴是水平方向的。
import 'package:flutter/material.dart';
class CenterColumnRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("hi"),
Text("world"),
],
);
}
}
效果图:
由于我们没有指定Column的mainAxisSize,所以使用默认值MainAxisSize.max,则Column会在垂直方向占用尽可能多的空间,此例中会占满整个屏幕高度。
由于我们指定了 crossAxisAlignment 属性为CrossAxisAlignment.center,那么子项在Column纵轴方向(此时为水平方向)会居中对齐。注意,在水平方向对齐是有边界的,总宽度为Column占用空间的实际宽度,而实际的宽度取决于子项中宽度最大的Widget。在本例中,Column有两个子Widget,而显示“world”的Text宽度最大,所以Column的实际宽度则为Text("world") 的宽度,所以居中对齐后Text("hi")会显示在Text("world")的中间部分。
实际上,Row和Column都只会在主轴方向占用尽可能大的空间,而纵轴的长度则取决于他们最大子元素的长度。如果我们想让本例中的两个文本控件在整个手机屏幕中间对齐,我们有两种方法:
将Column的宽度指定为屏幕宽度;这很简单,我们可以通过ConstrainedBox或SizedBox来强制更改宽度限制,例如:
ConstrainedBox(
constraints: BoxConstraints(minWidth: double.infinity),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("hi"),
Text("world"),
],
),
);
将minWidth设为double.infinity,可以使宽度占用尽可能多的空间。
使用Center组件:
import 'package:flutter/cupertino.dart';
class ColumnPageV2 extends StatelessWidget{
const ColumnPageV2({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child:
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("hi"),
Text("world0009")
],
),
);
}
}
=========================================================
如果Row里面嵌套Row,或者Column里面再嵌套Column,那么只有最外面的Row或Column会占用尽可能大的空间,里面Row或Column所占用的空间为实际大小,下面以Column为例说明:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ColumnPageV3 extends StatelessWidget {
const ColumnPageV3({super.key});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,//靠左对齐,纵轴(水平方向)宽度为子widget中的最大宽度
mainAxisSize: MainAxisSize.max,//有效,外层Colum高度为整个屏幕,默认设置
children: [
Container(
color: Colors.red,
child:const Column(
mainAxisSize: MainAxisSize.max, //无效,内层Colum高度为实际高度
children: <Widget>[
Text("hello world "),
Text("I am Jack "),
],
),
)
],
),
),
);
}
}
如果要让里面的Column占满外部Column,可以使用Expanded 组件:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ColumnPageV3 extends StatelessWidget {
const ColumnPageV3({super.key});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,//靠左对齐,纵轴(水平方向)宽度为子widget中的最大宽度
mainAxisSize: MainAxisSize.max,//有效,外层Colum高度为整个屏幕,默认设置
children: [
// Container(
// color: Colors.red,
// child:const Column(
// mainAxisSize: MainAxisSize.max, //无效,内层Colum高度为实际高度
// children: <Widget>[
// Text("hello world "),
// Text("I am Jack "),
// ],
// ),
// )
Expanded(
child: Container(
color: Colors.red,
child: const Column(
mainAxisAlignment: MainAxisAlignment.center, //垂直方向居中对齐
children: <Widget>[
Text("hello world "),
Text("I am Jack "),
],
),
),
)
],
),
),
);
}
}