跳转至正文

LayoutBuilder 与自适应布局

学习如何使用 LayoutBuilder widget。

学习如何创建能够适应不同屏幕宽度的布局。

你将完成的内容

使用 LayoutBuilder 创建响应式布局
检测屏幕尺寸以选择不同布局
为大屏幕构建侧边栏与详情布局

步骤

1

简介

现代应用需要在各种尺寸的屏幕上都能良好运行。在本页中,你将学习如何创建能够适应不同屏幕宽度的布局。此应用在大屏幕上显示侧边栏,在小屏幕上使用基于导航的 UI。具体而言,此应用处理两种屏幕尺寸:

  • 大屏幕(平板、桌面):并排显示联系人分组和联系人详情。

  • 小屏幕(手机):使用导航在联系人分组和详情之间切换。

2

创建联系人分组页面

首先,为联系人分组屏幕创建 ContactGroupsPage widget 的基本结构。创建 lib/screens/contact_groups.dart 并添加以下基本结构:

dart
import 'package:flutter/cupertino.dart';

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

  @override
  Widget build(BuildContext context) {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: Center(child: Text('Contact Groups will go here')),
    );
  }
}
3

创建联系人页面

同样,创建 lib/screens/contacts.dart 以最终显示各个联系人:

dart
import 'package:flutter/cupertino.dart';

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

  final int listId;

  @override
  Widget build(BuildContext context) {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: Center(child: Text('Lists of contacts will go here')),
    );
  }
}

ContaactsListPage widget 和 ContactGroupsPage widget 是实现自适应布局 widget 所需的占位页面,你将在下一步完成该布局。

4

构建自适应布局基础

创建 lib/screens/adaptive_layout.dart,并以以下基本结构开始:

dart
import 'package:flutter/cupertino.dart';

import 'contact_groups.dart';

class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

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

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  @override
  Widget build(BuildContext context) {
    return const ContactGroupsPage(); // Temporary placeholder
  }
}

这是一个 StatefulWidget,因为自适应布局最终需要管理当前选中的联系人分组。

接下来,在 lib/screens/adaptive_layout.dart 中添加屏幕尺寸检测逻辑:

dart
import 'package:flutter/cupertino.dart';

import 'contact_groups.dart';

const largeScreenMinWidth = 600;

class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

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

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;

        if (isLargeScreen) {
          return const Text('Large screen layout'); // Temporary
        } else {
          return const ContactGroupsPage();
        }
      },
    );
  }
}

LayoutBuilder widget 提供有关父级尺寸约束的信息。在 builder 回调中,你会收到一个 BoxConstraints 对象,它会告知你最大可用宽度和高度。

通过检查 constraints.maxWidth > largeScreenMinWidth,你可以决定显示哪种布局。 600 像素的阈值是常用的断点,用于区分手机尺寸屏幕与平板尺寸屏幕。

5

更新主应用

更新 main.dart 以使用自适应布局,这样你就能看到更改效果:

dart
import 'package:flutter/cupertino.dart';

import 'data/contact_group.dart';
import '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),
        ),
      ),
      home: AdaptiveLayout(),
    );
  }
}

如果你在 Chrome 中运行,可以调整浏览器窗口大小以查看布局变化。

6

添加列表选择功能

大屏幕布局需要跟踪选中的联系人分组。在 lib/screens/adaptive_layout.dart 中使用以下代码更新状态对象:

dart

import 'package:flutter/cupertino.dart';

import 'contact_groups.dart';

const largeScreenMinWidth = 600;

class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

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

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  int selectedListId = 0;

  void _onContactListSelected(int listId) {
    setState(() {
      selectedListId = listId;
    });
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;

        if (isLargeScreen) {
          return const Text('Large screen layout');
        } else {
          return const ContactGroupsPage();
        }
      },
    );
  }
}

selectedListId 变量跟踪当前选中的联系人分组, _onContactListSelected 在用户做出选择时更新此值。

7

构建大屏幕布局

现在,在 lib/screens/adaptive_layout.dart 中实现大屏幕的并排布局。首先,将临时文本替换为包含正确布局的 widget。

dart

import 'package:flutter/cupertino.dart';

import 'contact_groups.dart';

const largeScreenMinWidth = 600;

class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

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

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  int selectedListId = 0;

  void _onContactListSelected(int listId) {
    setState(() {
      selectedListId = listId;
    });
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;

        if (isLargeScreen) {
          return _buildLargeScreenLayout();
        } else {
          // For small screens, use the original, navigation-style approach.
          return const ContactGroupsPage();
        }
      },
    );
  }

  Widget _buildLargeScreenLayout() {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: SafeArea(child: Row(children: [Text('Sidebar'), Text('Details')])),
    );
  }
}

大屏幕布局使用 Row 将侧边栏和详情并排放置。 SafeArea 确保内容不会与状态栏等系统 UI 元素重叠。

现在,在 lib/screens/adaptive_layout.dart 中设置两个面板的尺寸并添加视觉分隔线:

dart
Widget _buildLargeScreenLayout() {
  return CupertinoPageScaffold(
    backgroundColor: CupertinoColors.extraLightBackgroundGray,
    child: SafeArea(
      child: Row(
        children: [
          const SizedBox(width: 320, child: Text('Sidebar placeholder')),
          Container(width: 1, color: CupertinoColors.separator),
          const Expanded(child: Text('Details placeholder')),
        ],
      ),
    ),
  );
}

此布局创建以下内容:

  • 用于联系人分组的固定宽度侧边栏(320 像素)。

  • 面板之间的 1 像素分隔线。

  • 使用 Expanded widget 占据剩余空间的详情面板。

8

测试自适应布局

热重载应用并测试响应式行为。如果你在 Chrome 中运行,可以调整浏览器窗口大小以查看布局变化:

  • 宽窗口 (> 600px):并排显示侧边栏和详情的占位文本。

  • 窄窗口 (< 600px):仅显示联系人分组页面。

目前侧边栏和主内容区域都显示占位文本。

在下一课中,你将实现 sliver 以填充联系人列表内容。

9

回顾

你完成的内容

以下是你本课构建与学习内容的摘要。
使用 LayoutBuilder 创建了响应式布局

LayoutBuilder 在其 builder 回调中提供父级的尺寸约束。通过检查 constraints.maxWidth,你可以根据可用空间决定显示哪种布局。

检测屏幕尺寸以选择不同布局

你使用 600 像素断点来区分手机尺寸屏幕与平板尺寸屏幕。这一常用阈值帮助你的应用调整 UI,以便在每种设备上提供最佳体验。

为大屏幕构建了侧边栏与详情布局

在大屏幕上,你使用 Row 并排显示固定宽度侧边栏和 Expanded 详情面板。这一经典模式可以最大化平板和桌面上的屏幕空间。

10

自测

自适应布局测验

1 / 2
LayoutBuilder 向其 builder 回调提供哪些信息?
  1. 设备的操作系统和屏幕方向。

    不正确。

    LayoutBuilder 提供尺寸约束,而非操作系统或方向信息。

  2. 父级的尺寸约束,包括最大宽度和高度。

    正确!

    LayoutBuilder 的 builder 会收到 BoxConstraints,告知你来自父级的可用空间。

  3. 当前的主题颜色和排版。

    不正确。

    主题数据来自 Theme.of(context),而非 LayoutBuilder。

  4. widget 树中子 widget 的数量。

    不正确。

    LayoutBuilder 提供布局约束,而非 widget 树信息。

在大屏幕布局中,可以使用哪个 widget 将侧边栏和详情面板并排放置?
  1. Column

    不正确。

    Column 垂直排列 widget,而非并排。

  2. Row

    正确!

    Row 水平排列其子级,非常适合将侧边栏和详情面板并排放置。

  3. Stack

    不正确。

    Stack 将 widget 层叠在一起,而非并排。

  4. ListView

    不正确。

    ListView 用于可滚动列表,而非并排布局。