Flutter 系列教程:布局基础 (下) - Stack 绝对定位和 Expanded 弹性布局

在上篇教程中,我们学习了如何使用 Container, Row, 和 Column 来创建有序的线性布局。现在,我们将学习另外两种强大的布局工具,它们将让你的布局能力提升到一个全新的维度。

学习目标

  • 掌握 Stack Widget,实现组件的堆叠效果。
  • 学会使用 Positioned Widget 在 Stack 中进行精确定位。
  • 理解 ExpandedFlexible 的作用,在 RowColumn 中实现弹性布局。
  • 学会使用 flex 属性按比例分配空间。

1. StackPositioned:层叠与绝对定位

你可以将 Stack Widget 想象成一叠扑克牌。它的 children 列表中的第一个 Widget 在最底层,最后一个 Widget 在最顶层。这使得我们可以轻松地实现组件之间的覆盖和堆叠效果。

Stack 核心属性

  • children: 一个 Widget 列表,是需要被堆叠的子组件。
  • alignment: 控制那些没有被Positioned包裹 的子组件的对齐方式。默认值是 Alignment.topLeft (左上角)。

Positioned Widget:Stack 的灵魂伴侣

Stack 本身只是简单地将组件堆叠在一起,但它的真正威力来自于与 Positioned Widget 的结合。Positioned 只能用在 Stackchildren ,它允许你相对于 Stack 的四个角来精确定位一个子组件。

  • 核心属性 : top, bottom, left, right
    • Positioned(top: 10, left: 10, child: ...): 将子组件定位在距离 Stack 顶部 10 像素、左侧 10 像素的位置。
    • 如果你同时设置了对立的属性,比如 left: 10right: 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. ExpandedFlexible:弹性布局

在上一篇中,我们学习了 RowColumn。但有一个常见问题:如果 Row 中的子组件宽度总和超过了屏幕宽度,Flutter 就会报错并显示一个黄黑相间的"溢出"警告。

ExpandedFlexible 就是为了解决这个问题而生的。它们只能用在 Row, Column, 或 Flexchildren,作用是让其子组件"瓜分"主轴上的剩余空间。

Expanded:强势的填充者

Expanded Widget 会强制 其子组件填满所有可用的剩余空间。如果 RowColumn 中有多个 Expanded 组件,它们会根据 flex 属性按比例分配空间。

  • 核心属性 flex : 一个整数,代表分配空间的"权重"或"比例"。默认值为 1。
    • 如果有两个 Expanded,一个 flex: 2,一个 flex: 1,那么前者会占据剩余空间的 2/3,后者占据 1/3。

Flexible:灵活的适应者

FlexibleExpanded 类似,但它不强制其子组件填满空间。它允许子组件在可用空间内拥有自己的尺寸,但不能超过可用空间(从而避免溢出)。

  • ExpandedFlexible 的一个常用特例,它等价于 Flexible(fit: FlexFit.tight)
  • Flexible 默认的 fitFlexFit.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 (垂直),用于有序排列。
  • 层叠布局 : StackPositioned,用于实现组件的堆叠和绝对定位。
  • 弹性布局 : ExpandedFlexible,用于在 RowColumn 中按比例分配空间。

熟练运用这些组件,你就可以像搭积木一样,自由地组合出任何你想要的复杂界面。它们是你构建所有 Flutter 应用的坚实基础。

在接下来的教程中,我们将学习如何构建可滚动的列表视图 (ListView) 和网格视图 (GridView),这对于展示大量数据至关重要。我们下篇教程见!

相关推荐
徐子颐6 分钟前
从 Vibe Coding 到 Agent Coding:Cursor 2.0 开启下一代 AI 开发范式
前端
小月鸭19 分钟前
如何理解HTML语义化
前端·html
jump68042 分钟前
url输入到网页展示会发生什么?
前端
诸葛韩信1 小时前
我们需要了解的Web Workers
前端
brzhang1 小时前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
游戏开发爱好者81 小时前
iOS 上架要求全解析,App Store 审核标准、开发者准备事项与开心上架(Appuploader)跨平台免 Mac 实战指南
android·macos·ios·小程序·uni-app·iphone·webview
qixingchao1 小时前
iOS SwiftUI 动画开发指南
ios·swiftui·swift
yivifu1 小时前
JavaScript Selection API详解
java·前端·javascript
这儿有一堆花1 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
十二春秋1 小时前
场景模拟:基础路由配置
前端