Flutter 布局组件
布局组件
总结一下最常用的几种组件,都是widget类的实现。
| 组件类别 | 核心组件 | 主要特点/使用场景 |
|---|---|---|
| 基础容器 | Container、Center、Align、Padding | 提供装饰、对齐、边距等基础样式和布局控制,是使用频率极高的组件 |
| 线性布局 | Row、Column | 在水平或垂直方向线性排列子组件,是构建界面的基础 |
| 弹性布局 | Flex, Expanded, Flexible | 按照比例分配剩余空间,实现自适应布局,常与 Row和 Column配合使用 |
| 层叠布局 | Stack, Positioned | 让子组件重叠堆叠,用于实现如图片上叠加文字、悬浮按钮等效果 |
| 流式布局 | Wrap, Flow | 当主轴空间不足时自动换行或换列,常用于标签、滤镜等动态宽高内容的排列 |
| 滚动布局 | ListView, GridView | 提供可滚动的列表或网格视图,高效展示大量数据 |
Container举例
基础布局组件,可以方便地容纳一个子组件,并对其施加各种样式、布局约束和变换
| 属性类别 | 关键属性 | 作用说明 |
|---|---|---|
| 布局定位 | alignment | 控制其 child(子组件)在容器内部的对齐方式。 |
| 尺寸控制 | width/height/constraints | 设置容器的宽度和高度/为容器设置更复杂的尺寸约束(如最小/最大宽高) |
| 间距留白 | padding/margin | 容器内容与容器内边缘之间区域/设置容器外边缘与相邻组件之间区域 |
| 装饰效果 | color/decoration | 为容器设置一个简单的背景颜色/为容器设置复杂的背景装饰 |
| 变换效果 | transform | 对容器及其内容进行矩阵变换 |
| 子组件 | child | 容器内包含的唯一直接子组件 |
如代码所示:
import 'package:flutter/material.dart';
// 无状态组件,生命周期只有一个build。在父组件更新的时候的,影响了子组件的时候,会执行子组件的build
class MainPage extends StatelessWidget {
@override
Widget build(Object context) {
return MaterialApp(
title: "无状态组件标题",
home: Scaffold(
appBar: AppBar(title: Text("标题")),
body: Center(
child: Row(
children: [
// Container(
// margin: EdgeInsets.only(left: 50),
// height: 200,
// width: 200,
// // alignment: Alignment.topCenter, // 子组件对齐
// alignment: Alignment.center, // 子组件对齐
// transform: Matrix4.rotationZ(0.1),
// decoration: BoxDecoration(
// border: Border.all(color: Colors.blue),
// borderRadius: BorderRadius.all(Radius.circular(10.5)),
// boxShadow: [
// BoxShadow(color: Colors.amberAccent, offset: Offset(5, 5)),
// ],
// ),
// child: Text(
// "中心",
// style: TextStyle(color: Color.fromRGBO(255, 100, 0, 1)),
// ),
// // ),
// Align(
// widthFactor: 2,
// heightFactor: 2,
// child: Icon(Icons.abc, size: 100, color: Colors.green),
// ),
Container(
margin: EdgeInsets.only(left: 20),
height: 200,
width: 200,
decoration: BoxDecoration(color: Colors.black),
child: Padding(
padding: EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(color: Colors.amber),
),
),
),
],
),
),
bottomNavigationBar: Container(
height: 100,
child: Center(child: Text("首页")),
),
),
);
}
}
void main() {
runApp(MainPage());
}
Center举例
将其子组件在父容器的空间内进行水平和垂直方向上的居中排列
应用场景: 页面内容整体居中,如将一个登录表单或一个加载中的提示图标在页面正中显示
注意事项:Center不能设置宽高,Center的最终大小取决于其父组件传递给它的约束, Center会向它的父组件申请尽 可能大的空间
实现固定宽高且居中的组件:Center去包裹一个具有固定宽高的子组件。Container/SizeBox
Align举例
如前面Container举例中注释的代码Align部分。
精确控制其子组件在父容器空间内的对齐位置
alignment(对齐方式):子组件在父容器内的对齐方式。
widthFactor(宽度因子):Align的宽度将是子组件宽度乘以该因子
heightFactor(高度因子):Align的高度将是子组件高度乘以该因子
Center的区别:Center是 Align的一个特例,继承自 Align,相当于一个将 alignment属性为居中的Align.center
使用场景:当需要将一个组件放置在父容器的特定角落,Align是理想选择。
动态尺寸:通过 widthFactor和 heightFactor,可以创建出与子组件大小成比例的容器,动态布局中很有用
Padding举例
为其子组件添加内边距 一共有两个属性。 padding: 类型是EdgeInsetsGeometry,必传,内边距的大小和方向,通常使用 EdgeInsets类来设置 child:类型是Widget,需要被添加内边距的子组件。
child: Padding(
padding: EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(color: Colors.amber),
),
),
Row举例
就是一种横向布局的方式。
- 导航栏:如顶部或底部的标签栏、按钮组。
- 图文混排: 如列表项左侧的图标与右侧的文本描述。
- 表单行: 如标签和输入框的组合
Row本身不支持滚动,如果内容超出,需要使用ListView或者SingleChildScrollView包裹 父组件的大小直接影响Row的最终大小和子组件的布局行为
Row(
children: [多个widget]
)
主轴是横向的,用mainAxisAligment来控制横线多个子组件的对齐方式。 总结:
| 属性 | 类型 | 作用说明 |
|---|---|---|
| mainAxisAlignment | MainAxisAlignment | 控制子组件在主轴(水平方向)上的排列方式,如顶部对齐、居中或均匀分布。 |
| crossAxisAlignment | CrossAxisAlignment | 控制子组件在交叉轴(垂直方向)上的对齐方式,如左对齐、右对齐或拉伸填满。 |
| mainAxisSize | MainAxisSize | 决定 Row本身在水平方向上的尺寸策略:是占满所有可用空间(max),还是仅仅包裹子组件内容(min)。 |
| children | 多个Widget | 需要被水平排列的子组件列表。 |
主轴上mainAxisAligment主要有以下属性,可以参考css中的flex来对比。 space-between,space-around,space-evenly ,start,end
交叉轴上是crossAxisAlignment有以下属性,start,center,end,这里的center可以理解为就是这一行的中间部分了。这么理解的话就很清楚了。
Column举例
他和Row刚好是垂直的一种布局形式,因此只需理解为主轴是竖向布局即可。 他和Row需要的属性一模一样,参考前面的,只是主轴变成了竖向的了。
Column(
children: [多个widget]
)
不多记录了。
Flex举例
Flex就是Css中的那个弹性盒子,Flutter中又实现了一套一模一样的Flex布局。只是把CSS属性变成了dart的类的形式,其他概念一模一样。
Flex是Row、Column的结合体, Flex的子组件常使用Expanded或Flexible来控制空间分配,Flex 布局受其父组件传递的约束影响。确保父组件提供了适当的布局约束, Expanded 与 Flexible 的区别: Expanded强制子组件填满所有剩余空间, Flexible根据自身大小调整,不强制占满空间
允许沿一个主轴(水平或垂直)排列其子组件,灵活地控制这些子组件在主轴上的尺寸比例和空间分配
总结是:
| 属性 | 类型 | 作用说明 |
|---|---|---|
| direction | Axis.horizontal/Axis.vertical | 主轴方向,决定子组件的排列方向 |
| mainAxisAlignment | MainAxisAlignment | 子组件在主轴方向上的对齐方式。 |
| crossAxisAlignmnt | CrossAxisAlignment | 子组件在交叉轴方向上的对齐方式 |
| mainAxisSize | MainAxisSize | Flex容器自身在主轴上的尺寸策略 |
如代码所示:
import 'package:flutter/material.dart';
// 无状态组件,生命周期只有一个build。在父组件更新的时候的,影响了子组件的时候,会执行子组件的build
class MainPage extends StatelessWidget {
@override
Widget build(Object context) {
return MaterialApp(
title: "无状态组件标题",
home: Scaffold(
appBar: AppBar(title: Text("标题")),
body: Center(
child: Flex(
direction: Axis.vertical, // 水平排列
crossAxisAlignment: CrossAxisAlignment.stretch, // 满宽
children: [
Container(
height: 20,
decoration: BoxDecoration(color: Colors.amber),
child: Text("顶部"),
),
Expanded(
flex: 1,
child: Container(
decoration: BoxDecoration(color: Colors.blue),
child: Text("1"),
),
), // 各占一半父组件空间
Expanded(
flex: 1,
child: Container(
decoration: BoxDecoration(color: Colors.amber),
child: Text("2"),
),
),
],
),
),
bottomNavigationBar: Container(
height: 100,
child: Center(child: Text("首页")),
),
),
);
}
}
void main() {
runApp(MainPage());
}
Wrap举例
可以理解为css中的flex-wrap:wrap;自动换行的意思。
流式布局组件,当子组件在主轴方向上排列不下时,它会自动换行(或换列)
| 属性 | 常用值 | 作用说明 |
|---|---|---|
| direction | Axis.horizontal(水平)/Axis.vertical(垂直) | 设置主轴方向,即排列方向。 |
| spacing | 数值 | 主轴方向上,子组件之间的间距 |
| runSpacing | 数值 | 交叉轴方向上,行(或列)之间的间距 |
| alignment | WrapAlignment | 子组件在主轴方向上的对齐方式。 |
| runAlignment | WrapAlignment | 交叉轴方向上的对齐方式 |
代码如下:
import 'package:flutter/material.dart';
// 无状态组件,生命周期只有一个build。在父组件更新的时候的,影响了子组件的时候,会执行子组件的build
class MainPage extends StatelessWidget {
List<Widget> orderList() {
return List.generate(20, (index) {
String t = "订单编号是" + index.toString();
return Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.amber),
alignment: Alignment.center,
child: Text(t),
);
});
}
@override
Widget build(Object context) {
return MaterialApp(
title: "无状态组件标题",
home: Scaffold(
appBar: AppBar(title: Text("标题")),
body: Wrap(
spacing: 20,
runSpacing: 10,
direction: Axis.horizontal, // 横向自动换行
children: orderList(),
),
bottomNavigationBar: Container(
height: 100,
child: Center(child: Text("首页")),
),
),
);
}
}
void main() {
runApp(MainPage());
}
Stack定位布局
可以理解为就是CSS中的Position那一套,相对定位和绝相对定位之间的关系,在这里同样适用。 css中相对定位position: relative,然后子元素绝对定位了postion: absolute。,跟着父元素走,就这个意思。然后元素之间如果有position的话,最后是一层套一层。
但是,在Flutter中,不能这样子设置属性的形式了,应该是使用Positioned类组件,作为Stack类组件的子组件,用来进行定位元素。
总结: | 属性 | 类型 | 作用说明 | | -- | -- | -- | | alignment | AlignmentGeometry | 控制非定位子组件在 Stack内的对齐方式,默认左上角 | | fit | StackFit | 控制非定位子组件如何适应 Stack的尺寸 | | clipBehavior | Clip | 控制子组件超出 Stack边界时的裁剪方式 | | children | 多个widget | 需要被层叠排列的子组件列表 |
代码如下:
import 'package:flutter/material.dart';
// 无状态组件,生命周期只有一个build。在父组件更新的时候的,影响了子组件的时候,会执行子组件的build
class MainPage extends StatelessWidget {
Positioned PositionBox() {
return Positioned(
right: 100,
bottom: 50,
child: Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.blue),
),
);
}
@override
Widget build(Object context) {
return MaterialApp(
title: "无状态组件标题",
home: Scaffold(
appBar: AppBar(title: Text("标题")),
body: Stack(
children: [
Center(child: Container(color: Colors.amber)),
PositionBox(),
],
),
bottomNavigationBar: Container(
height: 100,
child: Center(child: Text("首页")),
),
),
);
}
}
void main() {
runApp(MainPage());
}
Text和Text.rich
Text是用来展示一行文字的。 如果需要在同一段文本中显示不同样式,可用Text.rich构造函数配合TextSpan来实现。
假如文本过长请务必设置 maxLines和 overflow
如果要做超过多少后,文字后面是省略号的话,可以使用代码实现。
整体效果,参考代码如下:
import 'package:flutter/material.dart';
// 无状态组件,生命周期只有一个build。在父组件更新的时候的,影响了子组件的时候,会执行子组件的build
class MainPage extends StatelessWidget {
Text showProductName() {
return Text("电脑");
}
Widget showProductDesc() {
return Text.rich(
TextSpan(
children: [
TextSpan(
text: "电脑",
style: TextStyle(fontSize: 15, color: Colors.redAccent),
),
TextSpan(
text: "很好用",
style: TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
);
}
@override
Widget build(Object context) {
return MaterialApp(
title: "无状态组件标题",
home: Scaffold(
appBar: AppBar(title: Text("标题")),
body: Column(children: [showProductName(), showProductDesc()]),
bottomNavigationBar: Container(
height: 100,
child: Center(child: Text("首页")),
),
),
);
}
}
void main() {
runApp(MainPage());
}
Image组件
主要是Image的几个方法,如下:
| 分类 | 作用说明 | |
|---|---|---|
| Image.asset() | 加载项目资源目录(assets)中的图片。需要在 pubspec.yaml文件中声明资源路径 | |
| Image.network() | 直接从网络地址加载图片 | |
| Image.file() | 加载设备本地存储中的图片文件 | |
| Image.memory() | 加载内存中的图片数据 |
以上价格方法的设置图片格式大小或者图片模式,是平铺还是什么的,均有以下属性。
| 分类 | 类型 | 作用说明 |
|---|---|---|
| width/ height | double | 设置图片显示区域的宽度和高度 |
| fit | BoxFit | 控制图片如何适应其显示区域,例如是否拉伸、裁剪或保持原比例 |
| alignment | AlignmentGeometry | 图片在其显示区域内的对齐方式,如 Alignment.center |
| repeat | ImageRepeat | 当图片小于显示区域时,设置是否以及如何重复平铺图片 |
举例 Image.asset(),现在yaml文件中设置,如果是图片文件夹,这么写即可。
flutter:
assets:
- lib/images/
使用: 如果是网络图片的话,可以不设置这里的yml。
Image.asset和Image.network的使用效果如下:
import 'package:flutter/material.dart';
// 无状态组件,生命周期只有一个build。在父组件更新的时候的,影响了子组件的时候,会执行子组件的build
class MainPage extends StatelessWidget {
// asset图片
Image showImageAsset() {
return Image.asset(
"lib/images/1.gif",
fit: BoxFit.cover,
width: 200,
height: 200,
);
}
// 网络图片
Image showImageNetwork() {
return Image.network(
"https://docs.flutter.dev/assets/images/dash/dash-fainting.gif",
fit: BoxFit.cover,
width: 200,
height: 200,
);
}
@override
Widget build(Object context) {
return MaterialApp(
title: "无状态组件标题",
home: Scaffold(
appBar: AppBar(title: Text("标题")),
body: Column(children: [showImageAsset(), showImageNetwork()]),
bottomNavigationBar: Container(
height: 100,
child: Center(child: Text("首页")),
),
),
);
}
}
void main() {
runApp(MainPage());
}
TextField
简单来说就是HTML代码中的input标签,只是把很多属性封装到了类里面,属性名字几乎没变。
| 属性 | 作用说明 |
|---|---|
| controller | 文本编辑器控制器,用于获取、设置文档内容及监听变化 |
| decortation | 当时输入框的外观、如标签、提示文字、图标、边框等 |
| style | 定义输入文本的样式 |
| maxLines | 最大行数 |
| onChanged | 输入内容发生变化时执行的回调函数 |
| onSubmitted | 用户提交输入时的回调函数 |
注意:表单必须用在有状态组件中。为了让类保持state的值。 使用 TextEditingController管理输入内容、onChanged可以监听数据变化. decoration属性下的 InputDecoration来定制如 边框、背景、提示文字 obscureText设置为 true可隐藏输入内容,用于密码输入框
代码如下
import 'package:flutter/material.dart';
class MainPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _MainPageState();
}
}
class _MainPageState extends State<MainPage> {
TextEditingController _userNameController = TextEditingController();
TextEditingController _passwordController = TextEditingController();
// 表单
Widget showForm() {
return Column(
children: [
TextField(
controller: _userNameController,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 20),
hintText: "用户名",
fillColor: const Color.fromARGB(245, 245, 245, 221),
),
),
TextField(
controller: _passwordController,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 20),
hintText: "密码",
fillColor: const Color.fromARGB(245, 245, 245, 221),
),
),
],
);
}
@override
Widget build(Object context) {
return MaterialApp(
title: "状态组件标题",
home: Scaffold(
appBar: AppBar(title: Text("标题")),
body: Column(children: [showForm()]),
bottomNavigationBar: Container(
height: 100,
child: Center(child: Text("首页")),
),
),
);
}
}
void main() {
runApp(MainPage());
}