Flutter 常见布局模型
Flutter的常见的布局模型有容器(Container)、弹性盒子布局(Flex、Row、Column、Expanded)、流式布局(Wrap、Flow)、层叠布局(Stack、Position)、滚动布局(ListView、GridView)等。
布局模型也都是 widget,很多布局widget都继承自Container,其布局UI树状图如图:
布局容器 Container
创建一个项目:
flutter create buju
lib/main.dart 代码
import 'dart:math' as math;
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: Container(
height: 50,
width: 100,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.all(Radius.circular(10.0)),
border: Border.all(color: Colors.black, width: 2.0),
),
margin: EdgeInsets.fromLTRB(200, 50, 0, 0),
transform: Matrix4.rotationZ(30 * math.pi / 180),
child: Text("Container \n布局"),
),
),
);
}
}
从上面代码可以看到 Container 布局有很多属性:
color 设置容器的背景色。
width 设置容器的宽。
height 设置容器的高。
alignment 设置子widget的对齐方式,如居中。
decoration 设置背景装饰,如阴影、背景图等。
foregroundDecoration 设置前景装饰,比如前景色。
margin 设置容器等外边距。
padding 设置容器等内边距。
transform 设置形变,如需旋转、拉长等。
child 设置容器包裹的子Widget。
更多请见文档。
效果图如下:
弹性盒子布局
Flex
direction 设置主轴排列方向
Flex 有个重要的属性 direction,用来指定主轴的方向,子元素会按照主轴的方向排列。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: Flex(
direction: Axis.horizontal,
children: <Widget>[
Container(
width: 30,
height: 50,
color: Colors.red,
),
Container(
width: 30,
height: 50,
color: Colors.green,
),
Container(
width: 30,
height: 50,
color: Colors.grey,
),
],
),
),
);
}
}
这里主轴方向是horizontal水平方向,效果图如下:
改成垂直direction: Axis.vertical,
后的效果:
mainAxisAlignment 设置子元素排列方式
另一个重要属性是mainAxisAlignment
,用来决定主轴的排列方式。
默认是start:mainAxisAlignment: MainAxisAlignment.start
。
end:指最后一个元素排在主轴的最后。这里以主轴水平方向排列为例,如下图:
center:所有子元素按照主轴方向依次排列,并居中显示在父容器中。如下图:
spaceAround:每个子元素之间的间隔相等,第一个元素和最后一个元素距离父容器的边距为孩子之间间距的一半。如下图:
spaceBetween:子元素两端对齐,第一个子元素和最后一个子元素分别位于容器中主轴的起始处和终止处,同时各个子元素之间的间隔相等,如下图:
spaceEvenly: 各个子元素之间的间隔相等,同时第一个子元素和最后一个子元素距离父容器的边距也为各子元素之间的间隔。
mainAxisSize 设置主轴大小
mainAxisSize默认是max,Flex容器占据主轴全部空间。
还可以设置为min,Flex容器尽可能占据少的主轴空间。
direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
这里把Flex容器设置为最小的大小,导致三个子元素挤在了一起。
crossAxisAlignment 设置交叉轴对齐方式
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: Flex(
direction: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 30,
height: 100,
color: Colors.red,
),
Container(
width: 30,
height: 50,
color: Colors.green,
),
Container(
width: 30,
height: 50,
color: Colors.grey,
),
],
),
),
);
}
}
crossAxisAlignment 用来设置元素在交叉轴方向上的对齐方式。
默认值是center,各个子元素在交叉轴上居中对齐。如下图(这里以主轴沿水平方向):
end:子元素布局在Flex容器交叉轴方向的尾部。
start:子元素布局在Flex容器交叉轴方向的头部。
stretch:子元素填充满交叉轴方向的空间。
verticalDirection 设置交叉轴布局的对齐方向
这个属性和上面的crossAxisAlignment属性配合使用,用于指定交叉轴布局的对齐方向。
默认值是down,表示默认对齐的方向是从上到下,crossAxisAlignment默认start时,三个元素在顶部。
如果将verticalDirection改成up,表示从下到上,crossAxisAlignment还是默认start时,效果图如下:
Row 和 Column 都继承自Flex,所以Flex都属性同样适用于Row和Column,只是预先指定好了主轴的方向。
Row表示在水平方向上布局子元素,主轴方向是水平方向。
Column表示在垂直方向上布局子元素,主轴方向是垂直方向。
Flex 更多参数见:https://api.flutter-io.cn/flutter/widgets/Flex-class.html
Expanded 按比例缩放
Expanded 布局能让子元素能够按照一定的比例缩放,来填充满父容器在主轴方向上剩余的剩余空间。通常,Expanded 组件通常是作为Flex 组件的子组件使用。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(
flex: 1,
child: Container(
height: 150,
color: Colors.red,
),
),
Expanded(
flex: 1,
child: Container(
height: 150,
color: Colors.green,
),
),
Expanded(
flex: 1,
child: Container(
height: 150,
color: Colors.blue,
),
),
],
),
),
);
}
}
效果图:
如果把 Expanded 的 Flex 属性分别设置为 1,2,3,得到的效果图如下:
流式布局
在弹性布局中,如果宽度或高度超过屏幕尺寸,会抛出异常:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: Flex(
direction: Axis.horizontal,
children: <Widget>[
Container(
width: 150,
height: 100,
color: Colors.red,
),
Container(
width: 280,
height: 50,
color: Colors.green,
),
Container(
width: 50,
height: 50,
color: Colors.grey,
),
],
),
),
);
}
}
════════ Exception caught by rendering library ═════════════════════════════════
The following assertion was thrown during layout:
A RenderFlex overflowed by 105 pixels on the right.
这是因为弹性和子布局模型是线性的,不会自动换行。
Wrap 自动换行的容器
Wrap支持子元素自动换行,默认主轴水平方向。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: Wrap(
direction: Axis.horizontal,
spacing: 10,
runAlignment: WrapAlignment.start,
verticalDirection: VerticalDirection.down,
runSpacing: 10,
children: <Widget>[
Container(
width: 150,
height: 150,
color: Colors.red,
),
Container(
width: 200,
height: 50,
color: Colors.green,
),
Container(
width: 50,
height: 50,
color: Colors.grey,
),
Container(
width: 200,
height: 150,
color: Colors.black,
),
],
),
),
);
}
}
alignment、crossAxisAlignment、verticalDirection 与 Flex 的同名属性作用相同。
runAlignment 用来设置新一行或新一列的对齐方式。默认值是start,如果主轴方向是水平方向,start就是在交叉轴方向上从顶部开始布局子元素,如下图。end 是在交叉轴方向上从底部开始布局子元素。center 设置居中对齐,子元素会从交叉轴方向上父容器的中心开始布局。
Flow 布局允许用户自定义布局规则,具体参数见:https://api.flutter-io.cn/flutter/widgets/Flow-class.html
层叠布局:绝对定位
Stack 布局类似Web开发中的绝对定位。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: Center(
child: Stack(
children: <Widget>[
Container(
width: 150,
height: 150,
color: Colors.red,
),
Positioned(
left: 8.0,
right: 8.0,
top: 8.0,
child: Text('测试'),
)
],
),
)),
);
}
}
这个例子是对Text组件和Container组件的重叠,其中Positioned是定位组件,只能放在Stack中。
滚动布局
ListView 处理大量数据
当数据量特别大时,上面的布局方式实现起来会非常麻烦,而用滚动布局,直接将数据放入滚动布局模型的 children 数据中即可。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: ListView.builder(
itemCount: 50,
itemExtent: 50,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [Text('索引: ${index + 1}')],
),
);
})),
);
}
}
详细参数见文档:https://api.flutter-io.cn/flutter/widgets/ListView-class.html。
ListView.separated 增加分割线
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: ListView.separated(
itemCount: 50,
separatorBuilder: (BuildContext context, int ndex) {
return Divider();
},
itemBuilder: (BuildContext context, int index) {
return SizedBox(
height: 50,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [Text('索引: ${index + 1}')],
),
);
})),
);
}
}
GridView 构建多行多列表格
GridView 的创建需要一个 delegate 代表,即属性 gridDelegate , Flutter SDK 默认提供了两个 SliverGridDelegate 的子类,分别是: SliverGridDelegateWithFixedCrossAxisCount 和 SliverGridDelegateWithMaxCrossAxisExtent 。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisSpacing: 8, mainAxisSpacing: 8),
children: [
Container(
width: 50,
height: 50,
color: Colors.red,
),
Container(
width: 50,
height: 50,
color: Colors.red,
),
Container(
width: 50,
height: 50,
color: Colors.red,
),
Container(
width: 50,
height: 50,
color: Colors.red,
),
Container(
width: 50,
height: 50,
color: Colors.red,
),
Container(
width: 50,
height: 50,
color: Colors.red,
),
],
)),
);
}
}
效果图:
动态创建 GridView
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: GridView.builder(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 8,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 2.0),
itemBuilder: (BuildContext context, int index) {
return Container(
color: Colors.red,
width: 50,
height: 50,
);
}),
),
);
}
}
maxCrossAxisExtent 设置条目的宽度。
mainAxisSpacing 设置主轴的间隔。
crossAxisSpacing 设置交叉轴间隔。
childAspectRatio 设置子元素的宽高比。
相关链接
https://docs.flutter.cn/ui/layout/