在上篇教程中,我们学习了如何使用 Container
, Row
, 和 Column
来创建有序的线性布局。现在,我们将学习另外两种强大的布局工具,它们将让你的布局能力提升到一个全新的维度。
学习目标
- 掌握
Stack
Widget,实现组件的堆叠效果。 - 学会使用
Positioned
Widget 在Stack
中进行精确定位。 - 理解
Expanded
和Flexible
的作用,在Row
和Column
中实现弹性布局。 - 学会使用
flex
属性按比例分配空间。
1. Stack
和 Positioned
:层叠与绝对定位
你可以将 Stack
Widget 想象成一叠扑克牌。它的 children
列表中的第一个 Widget 在最底层,最后一个 Widget 在最顶层。这使得我们可以轻松地实现组件之间的覆盖和堆叠效果。
Stack
核心属性
children
: 一个Widget
列表,是需要被堆叠的子组件。alignment
: 控制那些没有被Positioned
包裹 的子组件的对齐方式。默认值是Alignment.topLeft
(左上角)。
Positioned
Widget:Stack
的灵魂伴侣
Stack
本身只是简单地将组件堆叠在一起,但它的真正威力来自于与 Positioned
Widget 的结合。Positioned
只能用在 Stack
的 children
中 ,它允许你相对于 Stack
的四个角来精确定位一个子组件。
- 核心属性 :
top
,bottom
,left
,right
。Positioned(top: 10, left: 10, child: ...)
: 将子组件定位在距离Stack
顶部 10 像素、左侧 10 像素的位置。- 如果你同时设置了对立的属性,比如
left: 10
和right: 10
,那么Positioned
会拉伸其子组件以填满这两个边界之间的空间。
代码示例:创建一个用户头像卡片
这个例子将展示如何在一个背景图片上层叠用户的名字和状态。
less
import 'package.flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Stack & Positioned Demo')),
body: Center(
child: Container(
width: 300,
height: 200,
color: Colors.grey,
child: Stack(
// alignment: Alignment.center, // 对齐所有非 Positioned 的子组件
children: <Widget>[
// 底层:背景图片
Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: NetworkImage('https://picsum.photos/300/200'),
fit: BoxFit.cover,
),
),
),
// 顶层 1: 定位在左下角的用户名称
Positioned(
bottom: 10,
left: 10,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
color: Colors.black.withOpacity(0.5),
child: const Text(
'Flutter Pro',
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
),
// 顶层 2: 定位在右上角的状态图标
const Positioned(
top: 10,
right: 10,
child: Icon(
Icons.check_circle,
color: Colors.greenAccent,
size: 30,
),
),
],
),
),
),
),
);
}
}
ddxx
2. Expanded
和 Flexible
:弹性布局
在上一篇中,我们学习了 Row
和 Column
。但有一个常见问题:如果 Row
中的子组件宽度总和超过了屏幕宽度,Flutter 就会报错并显示一个黄黑相间的"溢出"警告。
Expanded
和 Flexible
就是为了解决这个问题而生的。它们只能用在 Row
, Column
, 或 Flex
的 children
中,作用是让其子组件"瓜分"主轴上的剩余空间。
Expanded
:强势的填充者
Expanded
Widget 会强制 其子组件填满所有可用的剩余空间。如果 Row
或 Column
中有多个 Expanded
组件,它们会根据 flex
属性按比例分配空间。
- 核心属性
flex
: 一个整数,代表分配空间的"权重"或"比例"。默认值为 1。- 如果有两个
Expanded
,一个flex: 2
,一个flex: 1
,那么前者会占据剩余空间的 2/3,后者占据 1/3。
- 如果有两个
Flexible
:灵活的适应者
Flexible
与 Expanded
类似,但它不强制其子组件填满空间。它允许子组件在可用空间内拥有自己的尺寸,但不能超过可用空间(从而避免溢出)。
Expanded
是Flexible
的一个常用特例,它等价于Flexible(fit: FlexFit.tight)
。- 而
Flexible
默认的fit
是FlexFit.loose
,表示"尽可能大,但不要超过剩余空间"。
新手建议 :在绝大多数情况下,你需要的都是
Expanded
。当你希望一个组件在有足够空间时保持自身大小,在空间不足时进行收缩,才需要考虑使用Flexible
。
代码示例:构建一个按比例划分的导航栏
这个例子将创建一个 Row
,其中包含一个固定宽度的图标和两个按比例分配空间的文本按钮。
less
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Expanded Demo')),
body: Column(
children: [
// --- 示例 1: 平均分配空间 ---
Container(
height: 100,
color: Colors.blue,
child: Row(
children: [
Expanded(child: Container(color: Colors.red)),
Expanded(child: Container(color: Colors.green)),
Expanded(child: Container(color: Colors.yellow)),
],
),
),
const SizedBox(height: 20),
// --- 示例 2: 按比例分配空间 (2:3:1) ---
Container(
height: 100,
color: Colors.teal,
child: Row(
children: [
Expanded(
flex: 2, // 占据 2/6 的空间
child: Container(color: Colors.red),
),
Expanded(
flex: 3, // 占据 3/6 的空间
child: Container(color: Colors.green),
),
Expanded(
flex: 1, // 占据 1/6 的空间
child: Container(color: Colors.yellow),
),
],
),
),
],
),
),
);
}
}
动手试试 :修改上面代码中 flex
的值,观察布局是如何相应变化的。你也可以尝试将 Expanded
替换为 Flexible
,看看效果有何不同。
总结
至此,我们已经掌握了 Flutter 中最核心的几大布局 Widget:
- 线性布局 :
Row
(水平) 和Column
(垂直),用于有序排列。 - 层叠布局 :
Stack
和Positioned
,用于实现组件的堆叠和绝对定位。 - 弹性布局 :
Expanded
和Flexible
,用于在Row
和Column
中按比例分配空间。
熟练运用这些组件,你就可以像搭积木一样,自由地组合出任何你想要的复杂界面。它们是你构建所有 Flutter 应用的坚实基础。
在接下来的教程中,我们将学习如何构建可滚动的列表视图 (ListView
) 和网格视图 (GridView
),这对于展示大量数据至关重要。我们下篇教程见!