Flutter——布局
liuian 2025-05-21 14:57 31 浏览
上一节介绍了Flutter构建界面的一些要素,并通过一些代码和运行结果让大家对Flutter能有一些基本了解。从这一节开始,将对Flutter构建界面的要素进行逐一展开。
不管我们之前是做web开发、桌面端开发、移动客户端开发,其实都知道布局,它是我们构建界面的基础。那么在Flutter中学习布局,需要理解核心要点有哪些呢?
- 部件是用来构建UI的类。
- 部件用于布局和UI元素。
- 组合简单的部件来构建复杂的部件。
Flutter布局机制的核心是widget,即部件。在Flutter中,几乎所有东西都是部件——甚至布局模型也是部件。在Flutter应用程序中看到的图像、图标和文本都是部件。还有看不到的东西也是部件,例如排列、约束和对齐可见部件的行、列和网格。
通过组合部件来创建布局,从而构建更复杂的部件。例如,下图显示了可视化布局,显示了一行3列,每列包含一个图标和一个标签。
下面是这个UI的部件树:
上图中大部分应该与我们所期望的一样,但是可能会对Container(粉红色显示)感到疑惑。Container是一个部件类,允许自定义它的子部件。当需要添加填充、边距、边框或背景颜色时,可以使用Container来命名它的一些功能。
在本例中,每个Text小部件都放置在一个Container中,并添加边距,整个Row也被放置在一个Container中,以实现在行周围添加填充。
本例中UI的其余部分由属性控制。使用color属性设置图标的颜色,使用Text.style属性设置字体、颜色、粗细等。列和行也具有一些属性,这些属性允许您指定它们的子元素如何垂直或水平对齐,以及子元素应该占用多少空间。
实现一个简单的布局
如何在Flutter中布局一个部件?后续将展示如何创建和显示一个简单的部件,它还显示了一个简单的Hello World应用程序的完整代码。在Flutter中,只需要几个步骤就可以在屏幕上放置文本、图标或图像。
1.1 选择一个布局部件
根据希望如何对齐或约束可见小部件的方式,从各种布局小部件中进行选择,因为这些特征通常会传递给所包含的部件。这个例子使用Center来水平和垂直地居中它的内容。
1.2 创建一个可见部件
例如,创建一个Text部件:
Text('Hello World'),
创建一个icon部件‘
Icon(
Icons.star,
color: Colors.red[500],
),
1.3 将可见部件添加到布局部件
所有布局部件都有以下两种:
- 一个子属性,如果它们有一个子属性,例如Center、Container。
- 子属性接受一个部件列表,例如Row、Column、ListView、Stack。
将Text部件添加到Center部件:
const Center(
child: Text('Hello World'),
),
1.4 将布局部件添加到页面
Flutter应用程序本身就是一个部件,大多数部件都有一个build()方法。在应用程序的build()方法中实例化并返回一个部件,并将显示该部件。
对于Material应用,可以使用Scaffold部件,它提供了一个默认的banner、背景色,并提供了用于添加drawers、snack bars和bottom sheets的API,然后可以将Center小部件直接添加到主页的body属性中。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
const String appTitle = 'Flutter layout demo';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: const Text(appTitle),
),
body: const Center(
child: Text('Hello World'),
),
),
);
}
}
如果想要构建苹果风格的界面,可以使用CupertinoApp和CupertinoPageScaffold,具体代码就不详细展示了。
如果以上两种风格我们都不需要,我们可以直接使用Container即可,但是它不包括AppBar、标题或背景颜色等待。如果需要使用这些功能,就必须自己构建它们。下面的代码实现的功能是:将背景颜色更改为白色,文本更改为深灰色。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(color: Colors.white),
child: const Center(
child: Text(
'Hello World',
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 32,
color: Colors.black87,
),
),
),
);
}
}
运行结果:
垂直和水平布局多个部件
最常见的布局模式之一是垂直或水平排列部件。可以使Row部件水平排列部件,使用Column部件垂直排列部件。以下是这两个布局的
- Row和Column各有一个子部件列表。
- 子部件本身可以是行、列或其他复杂部件。
- 可以指定行或列如何垂直和水平对齐其子元素。
- 可以拉伸或约束特定的部件。
- 可以指定子部件如何使用行或列的可用空间。
可以使用mainAxisAlignment和crossAxisAlignment属性控制行或列如何对齐其子行或列。
对于Row,主轴水平运行,交叉轴垂直运行。
对于Column,主轴垂直运行,十字轴水平运行。
在下面的例子中,这3张图片的宽度都是100px。渲染框(在本例中是整个屏幕)的宽度超过300px,因此将主轴对齐方式设置为spaceEvenly将每个图像前后的自由水平空间均匀划分。
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
Image.asset('images/pic2.jpg'),
Image.asset('images/pic3.jpg'),
],
);
效果:
垂直方向代码如下:
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
Image.asset('images/pic2.jpg'),
Image.asset('images/pic3.jpg'),
],
);
效果:
2.1 组件大小
在Flutter中当布局太大而不适合设备时,受影响的边缘会出现黄黑条纹图案。下面是一个行太宽的例子:
通过使用Expanded部件,可以调整小部件的大小,使其适合一行或一列。要修复前面的示例,即图像行对于呈现框来说太宽,请使用Expanded部件包装每个图像。
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Image.asset('images/pic1.jpg'),
),
Expanded(
child: Image.asset('images/pic2.jpg'),
),
Expanded(
child: Image.asset('images/pic3.jpg'),
),
],
);
效果:
如果我们希望一个部件占用的空间是同类部件的两倍。为此,请使用Expanded部件的flex属性,这是一个决定小部件伸缩系数的整数。默认的伸缩因子是1。下面的代码将中间图像的伸缩系数设置为2:
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Image.asset('images/pic1.jpg'),
),
Expanded(
flex: 2,
child: Image.asset('images/pic2.jpg'),
),
Expanded(
child: Image.asset('images/pic3.jpg'),
),
],
);
效果:
2.2 组件填充方式
默认情况下,一行或列沿其主轴占用尽可能多的空间,但如果你想让子组件挤在一起,可通过设置mainAxisSize为MainAxisSize.min。
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
const Icon(Icons.star, color: Colors.black),
const Icon(Icons.star, color: Colors.black),
],
)
效果:
2.3 嵌套行和列
下面一段代码:
final stars = Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
const Icon(Icons.star, color: Colors.black),
const Icon(Icons.star, color: Colors.black),
],
);
final ratings = Container(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
stars,
const Text(
'170 Reviews',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w800,
fontFamily: 'Roboto',
letterSpacing: 0.5,
fontSize: 20,
),
),
],
),
);
部件树:
效果:
其他常用布局部件
Flutter有一个丰富的布局部件库。这里有一些最常用的部件,以达到快速实现各种效果的目的,而不用我们去重复的造轮子。当然Flutter还有更多其他的部件部件,后续章节会单独列出来进行介绍。
标准部件
- Container: 为部件添加填充、边距、边框、背景色或其他装饰。
- GridView: 将部件作为一个可滚动的网格展开。
- ListView: 将部件以可滚动列表的形式展开。
- Stack: 将一个部件重叠在另一个部件的顶部。
Material部件
- Card: 将相关信息组织到圆角和阴影框中。
- ListTile: 将最多3行文本,以及可选的前后图标组织成一行。
3.1 Container
许多布局可以自由地使用Container来使用填充来分隔部件,或者添加边框或边距。可以通过将整个布局放入Container中并更改其背景颜色或图像来更改设备的背景。以下是Container的概要:
- 添加padding,margin,border
- 更改背景颜色或图像
- 包含单个子部件,但该子部件可以是行、列,甚至是部件树的根
通过以下代码,进一步理解Container部件:
Widget _buildImageColumn() {
return Container(
decoration: const BoxDecoration(
color: Colors.black26,
),
child: Column(
children: [
_buildImageRow(1),
_buildImageRow(3),
],
),
);
}
Widget _buildDecoratedImage(int imageIndex) => Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 10, color: Colors.black38),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
margin: const EdgeInsets.all(4),
child: Image.asset('images/pic$imageIndex.jpg'),
),
);
Widget _buildImageRow(int imageIndex) => Row(
children: [
_buildDecoratedImage(imageIndex),
_buildDecoratedImage(imageIndex + 1),
],
);
效果:
3.2 GridView
使用GridView将部件布置为二维列表。GridView提供了两个预制列表,或者您可以构建自己的自定义网格。当GridView检测到它的内容太长而不适合渲染框时,它会自动滚动。以下是GridView的概要:
- 在网格中布局部件
- 检测列内容何时超过呈现框并自动提供滚动
- 构建自定义网格,或使用提供的网格之一:
GridView.count 允许指定列的数量
GridView.extent 允许指定贴图的最大像素宽度
Widget _buildGrid() => GridView.extent(
maxCrossAxisExtent: 150,
padding: const EdgeInsets.all(4),
mainAxisSpacing: 4,
crossAxisSpacing: 4,
children: _buildGridTileList(30));
List<Container> _buildGridTileList(int count) => List.generate(
count, (i) => Container(child: Image.asset('images/pic$i.jpg')));
使用GridView.extent创建一个最大150像素宽的网格,效果如下:
3.3 ListView
ListView是一个类似列的部件,当它的内容对于呈现框来说太长时,它会自动提供滚动。
- 一个特殊的Column,并以特定的规则组织内部的部件
- 可以水平布局,也可以垂直布局
- 检测其内容,并决定何时提供滚动
Widget _buildList() {
return ListView(
children: [
_tile('CineArts at the Empire', '85 W Portal Ave', Icons.theaters),
_tile('The Castro Theater', '429 Castro St', Icons.theaters),
_tile('Alamo Drafthouse Cinema', '2550 Mission St', Icons.theaters),
_tile('Roxie Theater', '3117 16th St', Icons.theaters),
_tile('United Artists Stonestown Twin', '501 Buckingham Way',
Icons.theaters),
_tile('AMC Metreon 16', '135 4th St #3000', Icons.theaters),
const Divider(),
_tile('K\'s Kitchen', '757 Monterey Blvd', Icons.restaurant),
_tile('Emmy\'s Restaurant', '1923 Ocean Ave', Icons.restaurant),
_tile('Chaiya Thai Restaurant', '272 Claremont Blvd', Icons.restaurant),
_tile('La Ciccia', '291 30th St', Icons.restaurant),
],
);
}
ListTile _tile(String title, String subtitle, IconData icon) {
return ListTile(
title: Text(title,
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
)),
subtitle: Text(subtitle),
leading: Icon(
icon,
color: Colors.blue[500],
),
);
}
运行效果:
3.4 Stack
使用Stack将部件排列在基本部件之上,部件可以完全或部分地与基本部件重叠。概要如下:
- 用于将一个部件叠加到另一个部件
- 子部件列表中的第一个部件是基本部件,随后的子部件被覆盖在该基本部件的顶部
- Stack的内容不能滚动
Widget _buildStack() {
return Stack(
alignment: const Alignment(0.6, 0.6),
children: [
const CircleAvatar(
backgroundImage: AssetImage('images/pic.jpg'),
radius: 100,
),
Container(
decoration: const BoxDecoration(
color: Colors.black45,
),
child: const Text(
'Mia B',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
);
}
运行效果:
3.5 Card
Material库中的Card包含相关的信息,几乎可以由任何小部件组成,但通常与ListTile一起使用。Card有一个子节点,但它的子节点可以是列、行、列表、网格或其他支持多个子节点的小部件。默认情况下,Card将其大小缩小为0 × 0像素。您可以使用SizedBox来限制卡片的大小。
在Flutter中,Card具有圆角和阴影,使其具有3D效果。改变Card的elevation属性可以让你控制器阴影效果。
Widget _buildCard() {
return SizedBox(
height: 210,
child: Card(
child: Column(
children: [
ListTile(
title: const Text(
'1625 Main Street',
style: TextStyle(fontWeight: FontWeight.w500),
),
subtitle: const Text('My City, CA 99984'),
leading: Icon(
Icons.restaurant_menu,
color: Colors.blue[500],
),
),
const Divider(),
ListTile(
title: const Text(
'(408) 555-1212',
style: TextStyle(fontWeight: FontWeight.w500),
),
leading: Icon(
Icons.contact_phone,
color: Colors.blue[500],
),
),
ListTile(
title: const Text('costa@example.com'),
leading: Icon(
Icons.contact_mail,
color: Colors.blue[500],
),
),
],
),
),
);
}
效果:
3.6 ListTile
ListTile 是Material库中的一个专门的行部件,可以轻松地创建包含最多3行文本和可选的前后图标的行。ListTile最常用于Card或ListView,但也可以用于其他地方。具体代码和效果,可参考上面的Card代码。
布局约束
在Flutter中,"Constraints"(约束)通常指的是一个描述 Widget 可以使用的空间大小的对象。布局系统使用约束来决定 Widget 的大小以及其在屏幕上的位置。
BoxConstraints(盒子约束): BoxConstraints 是描述 Widget 可以使用的空间范围的对象。它包含了最小宽度、最大宽度、最小高度、最大高度等属性。这些属性用于定义 Widget 可以占用的空间的范围。
ConstrainedBox: ConstrainedBox 是一个 Widget,它允许您为其子 Widget 指定额外的约束条件,强制子 Widget 在给定的约束条件下进行布局。
IntrinsicHeight 和 IntrinsicWidth: 这两个 Widget 允许其子 Widget 自适应其固有的高度或宽度。它们在包含的 Widget 可以根据其内容自动调整大小时非常有用。
MediaQuery: MediaQuery 允许您查询当前应用程序的屏幕大小和其他屏幕相关的信息,从而确定约束条件。
LayoutBuilder: LayoutBuilder 是一个用于构建依赖于父 Widget 约束的 Widget 的常见工具。它接受一个构建函数,该函数会接收约束参数并返回一个 Widget。
这一小节主要列举了一些布局约束组件和基本概念,布局约束涉及的内容比较多,后续会专门使用一个章节进行详细介绍。
小结
这一节,主要介绍了Flutter的布局部件,也是我们构建Flutter UI的基础。用户界面通过Widget树的层次结构构建,常用的布局Widget包括Container、Row、Column、Stack、Expanded等,它们可以用于灵活排列和组织子Widget。Padding和Margin通过EdgeInsets类用于定义内边距和外边距,调整Widget的间距。ListView和GridView分别用于垂直和二维网格排列子Widget。Flutter提供丰富的布局工具和技术,适用于各种应用场景,通过递归构建Widget树来实现整个界面的绘制。
- 上一篇:Flutter 如何实现性能优化
- 下一篇:Flutter 之 ListView
相关推荐
- 总结下SpringData JPA 的常用语法
-
SpringDataJPA常用有两种写法,一个是用Jpa自带方法进行CRUD,适合简单查询场景、例如查询全部数据、根据某个字段查询,根据某字段排序等等。另一种是使用注解方式,@Query、@Modi...
- 解决JPA在多线程中事务无法生效的问题
-
在使用SpringBoot2.x和JPA的过程中,如果在多线程环境下发现查询方法(如@Query或findAll)以及事务(如@Transactional)无法生效,通常是由于S...
- PostgreSQL系列(一):数据类型和基本类型转换
-
自从厂子里出来后,数据库的主力就从Oracle变成MySQL了。有一说一哈,贵确实是有贵的道理,不是开源能比的。后面的工作里面基本上就是主MySQL,辅MongoDB、ES等NoSQL。最近想写一点跟...
- 基于MCP实现text2sql
-
目的:基于MCP实现text2sql能力参考:https://blog.csdn.net/hacker_Lees/article/details/146426392服务端#选用开源的MySQLMCP...
- ORACLE 错误代码及解决办法
-
ORA-00001:违反唯一约束条件(.)错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。ORA-00017:请求会话以设置跟踪事件ORA-00018:超出最大会话数ORA-00...
- 从 SQLite 到 DuckDB:查询快 5 倍,存储减少 80%
-
作者丨Trace译者丨明知山策划丨李冬梅Trace从一开始就使用SQLite将所有数据存储在用户设备上。这是一个非常不错的选择——SQLite高度可靠,并且多种编程语言都提供了广泛支持...
- 010:通过 MCP PostgreSQL 安全访问数据
-
项目简介提供对PostgreSQL数据库的只读访问功能。该服务器允许大型语言模型(LLMs)检查数据库的模式结构,并执行只读查询操作。核心功能提供对PostgreSQL数据库的只读访问允许L...
- 发现了一个好用且免费的SQL数据库工具(DBeaver)
-
缘起最近Ai不是大火么,想着自己也弄一些开源的框架来捣腾一下。手上用着Mac,但Mac都没有显卡的,对于学习Ai训练模型不方便,所以最近新购入了一台4090的拯救者,打算用来好好学习一下Ai(呸,以上...
- 微软发布.NET 10首个预览版:JIT编译器再进化、跨平台开发更流畅
-
IT之家2月26日消息,微软.NET团队昨日(2月25日)发布博文,宣布推出.NET10首个预览版更新,重点改进.NETRuntime、SDK、libraries、C#、AS...
- 数据库管理工具Navicat Premium最新版发布啦
-
管理多个数据库要么需要使用多个客户端应用程序,要么找到一个可以容纳你使用的所有数据库的应用程序。其中一个工具是NavicatPremium。它不仅支持大多数主要的数据库管理系统(DBMS),而且它...
- 50+AI新品齐发,微软Build放大招:拥抱Agent胜算几何?
-
北京时间5月20日凌晨,如果你打开微软Build2025开发者大会的直播,最先吸引你的可能不是一场原本属于AI和开发者的技术盛会,而是开场不久后的尴尬一幕:一边是几位微软员工在台下大...
- 揭秘:一条SQL语句的执行过程是怎么样的?
-
数据库系统能够接受SQL语句,并返回数据查询的结果,或者对数据库中的数据进行修改,可以说几乎每个程序员都使用过它。而MySQL又是目前使用最广泛的数据库。所以,解析一下MySQL编译并执行...
- 各家sql工具,都闹过哪些乐子?
-
相信这些sql工具,大家都不陌生吧,它们在业内绝对算得上第一梯队的产品了,但是你知道,他们都闹过什么乐子吗?首先登场的是Navicat,这款强大的数据库管理工具,曾经让一位程序员朋友“火”了一把。Na...
- 详解PG数据库管理工具--pgadmin工具、安装部署及相关功能
-
概述今天主要介绍一下PG数据库管理工具--pgadmin,一起来看看吧~一、介绍pgAdmin4是一款为PostgreSQL设计的可靠和全面的数据库设计和管理软件,它允许连接到特定的数据库,创建表和...
- Enpass for Mac(跨平台密码管理软件)
-
还在寻找密码管理软件吗?密码管理软件有很多,但是综合素质相当优秀且完全免费的密码管理软件却并不常见,EnpassMac版是一款免费跨平台密码管理软件,可以通过这款软件高效安全的保护密码文件,而且可以...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
飞牛OS入门安装遇到问题,如何解决?
-
系统C盘清理:微信PC端文件清理,扩大C盘可用空间步骤
-
10款高性能NAS丨双十一必看,轻松搞定虚拟机、Docker、软路由
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)