概述

#

`PrimaryScrollController` API 已更新,不再自动附加到桌面平台上的垂直 `ScrollView`。

背景

#

在此更改之前,如果 `ScrollView` 具有 `Axis.vertical` 滚动方向且尚未提供 `ScrollController`,则 `ScrollView.primary` 默认设置为 true。这使得常见的 UI 模式(例如 iOS 上的滚动到顶部功能)能够为 Flutter 应用开箱即用。然而,在桌面上,此默认设置通常会导致以下断言错误:

ScrollController attached to multiple ScrollViews.

尽管移动应用程序通常一次只显示一个 `ScrollView`,但桌面 UI 模式更可能并排显示多个 `ScrollView`。`PrimaryScrollController` 的先前实现与此模式冲突,导致常常出现无用的错误消息。为了解决这个问题,`PrimaryScrollController` 已更新,增加了额外的参数,并在依赖于它的多个小部件中提供了更好的错误消息。

变更说明

#

`ScrollView` 的先前实现导致所有平台上所有尚未具有 `ScrollController` 的垂直 `ScrollView` 默认将 `primary` 设置为 true。此默认行为并非总是清晰,尤其因为它独立于 `PrimaryScrollController` 本身。

dart
// Previously, this ListView would always result in primary being true,
// and attached to the PrimaryScrollController on all platforms.
Scaffold(
  body: ListView.builder(
    itemBuilder: (BuildContext context, int index) {
      return Text('Item $index');
    }
  ),
);

该实现将 `ScrollView.primary` 更改为可空,并将回退决策逻辑重新定位到 `PrimaryScrollController`。当 `primary` 为 null 且未提供 `ScrollController` 时,`ScrollView` 将查找 `PrimaryScrollController` 并转而调用 `shouldInherit` 来确定给定的 `ScrollView` 是否应使用 `PrimaryScrollController`。

`PrimaryScrollController` 类的新成员 `automaticallyInheritForPlatforms` 和 `scrollDirection` 在 `shouldInherit` 中进行评估,使用户能够清晰地控制 `PrimaryScrollController` 的行为。

默认情况下,移动平台保持向后兼容。`PrimaryScrollController.shouldInherit` 对垂直 `ScrollView` 返回 true。在桌面上,此默认返回 false。

dart
// Only on mobile platforms will this attach to the PrimaryScrollController by
// default.
Scaffold(
  body: ListView.builder(
    itemBuilder: (BuildContext context, int index) {
      return Text('Item $index');
    }
  ),
);

要更改默认设置,用户可以将 `ScrollView.primary` 设置为 true 或 false,以显式管理单个 `ScrollView` 的 `PrimaryScrollController`。对于跨多个 `ScrollView` 的行为,现在可以通过设置特定平台以及首选的继承滚动方向来配置 `PrimaryScrollController`。

使用 `PrimaryScrollController` 的小部件,例如 `NestedScrollView`、`Scrollbar` 和 `DropdownMenuButton`,现有功能不会发生任何变化。像 iOS 滚动到顶部这样的功能也将继续按预期工作,无需任何迁移。

桌面上的 `ScrollAction` 和 `ScrollIntent` 是唯一受此更改影响的类,需要进行迁移。默认情况下,如果当前 `Focus` 包含在 `Scrollable` 中,则 `PrimaryScrollController` 用于执行回退键盘滚动 `Shortcuts`。由于在桌面平台上并排显示多个 `ScrollView` 很常见,Flutter 无法决定“在此视图中哪个 `ScrollView` 应该是主要的并接收键盘滚动操作?”

如果在此更改之前存在多个 `ScrollView`,则会抛出相同的断言(`ScrollController attached to multiple ScrollViews.`)。现在,在桌面平台上,用户需要指定 `primary: true` 来指定哪个 `ScrollView` 是接收未处理键盘 `Shortcuts` 的回退。

迁移指南

#

迁移前的代码

dart
// These side-by-side ListViews would throw errors from Scrollbars and
// ScrollActions previously due to the PrimaryScrollController.
Scaffold(
  body: LayoutBuilder(
    builder: (context, constraints) {
      return Row(
        children: [
          SizedBox(
            height: constraints.maxHeight,
            width: constraints.maxWidth / 2,
            child: ListView.builder(
              itemBuilder: (BuildContext context, int index) {
                return Text('List 1 - Item $index');
              }
            ),
          ),
          SizedBox(
            height: constraints.maxHeight,
            width: constraints.maxWidth / 2,
            child: ListView.builder(
              itemBuilder: (BuildContext context, int index) {
                return Text('List 2 - Item $index');
              }
            ),
          ),
        ]
      );
    },
  ),
);

迁移后的代码

dart
// These side-by-side ListViews will no longer throw errors, but for
// default ScrollActions, one will need to be designated as primary.
Scaffold(
  body: LayoutBuilder(
    builder: (context, constraints) {
      return Row(
        children: [
          SizedBox(
            height: constraints.maxHeight,
            width: constraints.maxWidth / 2,
            child: ListView.builder(
              // This ScrollView will use the PrimaryScrollController
              primary: true,
              itemBuilder: (BuildContext context, int index) {
                return Text('List 1 - Item $index');
              }
            ),
          ),
          SizedBox(
            height: constraints.maxHeight,
            width: constraints.maxWidth / 2,
            child: ListView.builder(
              itemBuilder: (BuildContext context, int index) {
                return Text('List 2 - Item $index');
              }
            ),
          ),
        ]
      );
    },
  ),
);

时间线

#

发布版本:3.3.0-0.0.pre
稳定版本:3.3

参考资料

#

API 文档

设计文档

相关问题

相关 PR