Flutter 自适应布局一套代码适配手机和平板(十二)

前言

上一篇文章中,我们搭建了 Rolodex 通讯录项目并创建了数据模型。但应用目前只有一个"Hello Rolodex!"占位页面。

今天这篇文章基于官方教程的「Adaptive Layouts」章节,我们将学习一个非常实用的技能------自适应布局 。同一个应用,在手机上是导航跳转式的界面,在平板或电脑上自动变成侧边栏 + 详情面板的并排布局。而实现这一切的核心,就是 LayoutBuilder


一、为什么需要自适应布局?

Flutter 可以运行在手机、平板、电脑和网页上,但这些设备的屏幕尺寸差异巨大。如果你只为手机设计界面,在大屏幕上就会浪费大量空间;如果只为大屏设计,在手机上又会挤成一团。

自适应布局的思路是:根据屏幕宽度,自动选择不同的布局方案。


二、认识 LayoutBuilder

LayoutBuilder 是实现自适应布局的核心组件。它能告诉你父组件给了多少可用空间,然后你根据这个空间大小决定返回什么 Widget。

kotlin 复制代码
// LayoutBuilder 的基本用法
LayoutBuilder(
  // builder 回调中的 constraints 包含了可用空间信息
  // constraints.maxWidth → 最大可用宽度
  // constraints.maxHeight → 最大可用高度
  builder: (context, constraints) {
    // 判断屏幕是否够宽
    if (constraints.maxWidth > 600) {
      // 宽屏:返回并排布局
      return Row(children: [sidebar, details]);
    } else {
      // 窄屏:返回单页布局
      return ContactGroupsPage();
    }
  },
)

你可以把 LayoutBuilder 想象成一把"量尺"------它先量一下有多少空间,然后你根据测量结果决定"摆什么家具"。


三、创建页面占位组件

在实现自适应布局之前,先创建两个占位页面。

3.1 联系人分组页面

创建 lib/screens/contact_groups.dart

scala 复制代码
// ContactGroupsPage:联系人分组页面
// 在小屏幕上作为主页面显示
// 在大屏幕上作为侧边栏的内容
import 'package:flutter/cupertino.dart';

class ContactGroupsPage extends StatelessWidget {
  const ContactGroupsPage({super.key});

  @override
  Widget build(BuildContext context) {
    // CupertinoPageScaffold → iOS 风格的页面脚手架
    return const CupertinoPageScaffold(
      // extraLightBackgroundGray → iOS 风格的浅灰色背景
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: Center(
        // 占位文字,后续课程会替换为真正的分组列表
        child: Text('Contact Groups will go here'),
      ),
    );
  }
}

3.2 联系人列表页面

创建 lib/screens/contacts.dart

scala 复制代码
// ContactListsPage:联系人列表页面
// 显示某个分组内的所有联系人
// listId 参数指定要显示哪个分组
import 'package:flutter/cupertino.dart';

class ContactListsPage extends StatelessWidget {
  const ContactListsPage({super.key, required this.listId});

  // 分组 ID:决定显示哪个分组的联系人
  final int listId;

  @override
  Widget build(BuildContext context) {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: Center(
        // 占位文字,后续课程会替换为真正的联系人列表
        child: Text('Lists of contacts will go here'),
      ),
    );
  }
}

四、构建自适应布局组件

4.1 基本结构

创建 lib/screens/adaptive_layout.dart

scala 复制代码
import 'package:flutter/cupertino.dart';
import 'contact_groups.dart';

// 屏幕宽度断点:600 像素
// 低于此值视为小屏(手机),高于此值视为大屏(平板/电脑)
// 600px 是业界常用的手机/平板分界线
const largeScreenMinWidth = 600;

// AdaptiveLayout:自适应布局组件
// 使用 StatefulWidget 因为需要追踪当前选中的分组
class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  // 当前选中的联系人分组 ID(大屏幕布局需要知道右边显示哪个分组)
  int selectedListId = 0;

  // 当用户点击某个分组时调用
  // 更新选中的分组 ID,触发界面重绘
  void _onContactListSelected(int listId) {
    setState(() {
      selectedListId = listId;
    });
  }

  @override
  Widget build(BuildContext context) {
    // ===== LayoutBuilder:核心!=====
    // 它提供父组件的尺寸约束(constraints)
    // 我们根据 maxWidth 判断屏幕大小,返回不同的布局
    return LayoutBuilder(
      builder: (context, constraints) {
        // 判断:可用宽度是否大于 600 像素
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;

        if (isLargeScreen) {
          // 大屏幕 → 并排布局(侧边栏 + 详情面板)
          return _buildLargeScreenLayout();
        } else {
          // 小屏幕 → 导航式布局(只显示分组列表)
          return const ContactGroupsPage();
        }
      },
    );
  }

  // ===== 大屏幕布局:侧边栏 + 详情面板 =====
  Widget _buildLargeScreenLayout() {
    return CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      // SafeArea 确保内容不会被系统 UI 遮挡
      // 如状态栏、刘海屏的异形区域等
      child: SafeArea(
        child: Row(
          children: [
            // ===== 左侧:侧边栏(固定宽度 320px)=====
            SizedBox(
              width: 320,
              // 后续会替换为真正的分组列表组件
              child: Text('Sidebar placeholder'),
            ),

            // ===== 中间:分隔线 =====
            // 1 像素宽的垂直线,分隔左右两栏
            Container(
              width: 1,
              color: CupertinoColors.separator,
            ),

            // ===== 右侧:详情面板(占满剩余空间)=====
            // Expanded 让详情面板自动填满 Row 中剩余的宽度
            Expanded(
              // 后续会替换为真正的联系人列表组件
              child: Text('Details placeholder'),
            ),
          ],
        ),
      ),
    );
  }
}

4.2 布局结构解析

大屏幕布局的 Widget 树:

arduino 复制代码
CupertinoPageScaffold
  └── SafeArea
        └── Row(横向排列三个部分)
              ├── SizedBox (width: 320)    ← 侧边栏,固定 320px
              │     └── 分组列表
              ├── Container (width: 1)     ← 分隔线,1px
              └── Expanded                 ← 详情面板,占满剩余空间
                    └── 联系人列表

这是一个经典的「主从布局」(master-detail)模式------左边是列表(master),右边是详情(detail),在桌面和平板应用中非常常见。


五、更新 main.dart

main.dart 中的占位页面替换为 AdaptiveLayout

scala 复制代码
import 'package:flutter/cupertino.dart';
import 'package:rolodex/data/contact_group.dart';
// 导入自适应布局组件
import 'package:rolodex/screens/adaptive_layout.dart';

final contactGroupsModel = ContactGroupsModel();

void main() {
  runApp(const RolodexApp());
}

class RolodexApp extends StatelessWidget {
  const RolodexApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      title: 'Rolodex',
      theme: CupertinoThemeData(
        barBackgroundColor: CupertinoDynamicColor.withBrightness(
          color: Color(0xFFF9F9F9),
          darkColor: Color(0xFF1D1D1D),
        ),
      ),
      // 使用 AdaptiveLayout 替代之前的占位页面
      home: AdaptiveLayout(),
    );
  }
}

六、测试自适应效果

热重载后,在 Chrome 中测试:

  • 拖宽浏览器窗口(> 600px) :看到"Sidebar placeholder"和"Details placeholder"并排显示
  • 拖窄浏览器窗口(< 600px) :只看到"Contact Groups will go here"

拖动窗口边缘时,布局会实时切换------这就是 LayoutBuilder 的魔力。它在每次父组件尺寸变化时都会重新调用 builder 函数。


七、本节知识点小结

LayoutBuilder: 提供父组件的尺寸约束(BoxConstraints)。通过检查 constraints.maxWidth 判断屏幕大小,根据结果返回不同的布局 Widget。是实现自适应布局的核心工具。

断点(Breakpoint): 区分不同屏幕尺寸的阈值。600px 是常用的手机/平板分界线。低于 600px 用导航式布局,高于 600px 用并排式布局。

主从布局(Master-Detail): 大屏幕的经典模式------左侧固定宽度的列表(master),右侧 Expanded 的详情面板(detail)。用 Row + SizedBox + Expanded 实现。

SafeArea: 确保内容不被系统 UI 遮挡(状态栏、刘海屏、底部指示器等)。在全屏布局中应始终使用。


八、下一步学习

自适应布局的骨架已搭好,但侧边栏和详情面板还都是占位文字。下一课我们将学习 Scrolling and Slivers(滚动和 Sliver),用高级滚动技术填充联系人列表,实现可折叠的搜索栏和字母索引。

我们下篇文章见!

参考资料:Flutter 官方教程 - Adaptive Layouts

相关推荐
牛奶2 小时前
HTTP裸奔,HTTPS穿盔甲——它们有什么区别?
前端·http·https
梓言2 小时前
tailwindcss构建执行npm exec tailwindcss init -p 报错
前端
哈罗哈皮2 小时前
龙虾(openclaw)本地快速安装及使用教程
前端·aigc·ai编程
用户23115444530582 小时前
React中实现“双向绑定”效果的几种方式
前端
HelloReader2 小时前
Flutter Sliver 高级滚动打造 iOS 通讯录体验(十三)
前端
a1117762 小时前
程序化几何背景生成器(html 开源)
前端·开源·html
浮笙若有梦2 小时前
我开源了一个比 Ant Design Table 更好用的高性能虚拟表格
前端·vue.js
一只程序熊3 小时前
vite-cool-unix-ctx] Unexpected token l in JSON at position 0
java·服务器·前端
张元清3 小时前
React Hooks vs Vue Composables:2026 年全面对比
前端·javascript·面试