跳到主要内容

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来控制横线多个子组件的对齐方式。 总结:

属性类型作用说明
mainAxisAlignmentMainAxisAlignment控制子组件在主轴(水平方向)上的排列方式,如顶部对齐、居中或均匀分布。
crossAxisAlignmentCrossAxisAlignment控制子组件在交叉轴(垂直方向)上的对齐方式,如左对齐、右对齐或拉伸填满。
mainAxisSizeMainAxisSize决定 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根据自身大小调整,不强制占满空间

允许沿一个主轴(水平或垂直)排列其子组件,灵活地控制这些子组件在主轴上的尺寸比例和空间分配

总结是:

属性类型作用说明
directionAxis.horizontal/Axis.vertical主轴方向,决定子组件的排列方向
mainAxisAlignmentMainAxisAlignment子组件在主轴方向上的对齐方式。
crossAxisAlignmntCrossAxisAlignment子组件在交叉轴方向上的对齐方式
mainAxisSizeMainAxisSizeFlex容器自身在主轴上的尺寸策略
如代码所示:

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;自动换行的意思。

流式布局组件,当子组件在主轴方向上排列不下时,它会自动换行(或换列)

属性常用值作用说明
directionAxis.horizontal(水平)/Axis.vertical(垂直)设置主轴方向,即排列方向。
spacing数值主轴方向上,子组件之间的间距
runSpacing数值交叉轴方向上,行(或列)之间的间距
alignmentWrapAlignment子组件在主轴方向上的对齐方式。
runAlignmentWrapAlignment交叉轴方向上的对齐方式

代码如下:

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/ heightdouble设置图片显示区域的宽度和高度
fitBoxFit控制图片如何适应其显示区域,例如是否拉伸、裁剪或保持原比例
alignmentAlignmentGeometry图片在其显示区域内的对齐方式,如 Alignment.center
repeatImageRepeat当图片小于显示区域时,设置是否以及如何重复平铺图片

举例 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());
}