可滚动的 AlertDialog (不再弃用)

概述

Summary

AlertDialog 现在会在绘制溢出时自动变为可滚动状态。

An AlertDialog now scrolls automatically when it overflows.

上下文

Context

在此更改之前,当 AlertDialog widget 中的内容过高时,会显示绘制溢出,使内容被剪裁。这会导致以下问题:

Before this change, when an AlertDialog widget’s contents were too tall, the display overflowed, causing the contents to be clipped. This resulted in the following issues:

  • 无法查看被剪裁的内容。

    There was no way to view the portion of the content that was clipped.

  • 大多数 AlertDialog 的内容下方都有按钮,用于提示用户执行操作。如果内容溢出,遮盖了按钮,用户可能不知道它们的存在。

    Most alert dialogs have buttons beneath the content to prompt users for actions. If the content overflowed, obscuring the buttons, users might be unaware of their existence.

更改描述

Description of change

在改动前,可以使用下面的方法在 Column widget 中连续地列出标题和内容 widget。

The previous approach listed the title and content widgets consecutively in a Column widget.

Column(
  mainAxisSize: MainAxisSize.min,
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: <Widget>[
    if (title != null)
      Padding(
        padding: titlePadding ?? EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0),
        child: DefaultTextStyle(
          style: titleTextStyle ?? dialogTheme.titleTextStyle ?? theme.textTheme.title,
          child: Semantics(
          child: title,
          namesRoute: true,
          container: true,
          ),
        ),
      ),
    if (content != null)
      Flexible(
        child: Padding(
        padding: contentPadding,
        child: DefaultTextStyle(
          style: contentTextStyle ?? dialogTheme.contentTextStyle ?? theme.textTheme.subhead,
          child: content,
        ),
      ),
    ),
    // ...
  ],
);

在改动后,它们被包裹在按钮栏上方的 SingleChildScrollView 中,作为同一个可滚动的模块的一部分,按钮栏将显示在对话框底部。

The new approach wraps both widgets in a SingleChildScrollView above the button bar, making both widgets part of the same scrollable and exposing the button bar at the bottom of the dialog.

Column(
  mainAxisSize: MainAxisSize.min,
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: <Widget>[
    if (title != null || content != null)
      SingleChildScrollView(
        child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.stretch,
         children: <Widget>[
           if (title != null)
             titleWidget,
             if (content != null)
             contentWidget,
         ],
       ),
     ),
   // ...
  ],
),

迁移指南

Migration guide

此更改可能会导致以下问题:

You might see the following issues as a result of this change:

Semantics tests might fail because of the addition of a SingleChildScrollView.
Manual testing of the Talkback and VoiceOver features show that they still exhibit the same (correct) behavior as before.

由于添加了 SingleChildScrollView,语义测试可能会失败。
TalkbackVoiceOver 功能的手动测试表明,它们仍然表现出与以前相同的(正确的)行为。

Golden tests might fail.
This change might have caused diffs in (previously passing) golden tests since the SingleChildScrollView now nests both the title and content widgets. Some Flutter projects have taken to creating semantics tests by taking goldens of semantics nodes used in Flutter’s debug build.

Golden Test 可能会失败。
由于 SingleChildScrollView 现在嵌套了标题和内容 widget,因此可能导致 Golden Test 出现不同的结果。一些 Flutter 项目已经开始通过获取 Flutter debug 构建过程中使用的语义节点 goldens 来创建语义测试。


滚动容器上附加的所有语义 golden 变动都是符合预期的,它们不会造成其他后果。


Any semantics golden updates that reflect the scrolling container addition are expected and these diffs should be safe to accept.

语义树示例:

Sample resulting Semantics tree:

flutter:        ├─SemanticsNode#30 <-- SingleChildScrollView
flutter:          │ flags: hasImplicitScrolling
flutter:          │ scrollExtentMin: 0.0
flutter:          │ scrollPosition: 0.0
flutter:          │ scrollExtentMax: 0.0
flutter:          │
flutter:          ├─SemanticsNode#31 <-- title
flutter:          │   flags: namesRoute
flutter:          │   label: "Hello"
flutter:          │
flutter:          └─SemanticsNode#32 <-- contents
flutter:              label: "Huge content"
Layout changes might result because of the scroll view.
If the dialog was already overflowing, this change corrects the problem. This layout change is expected.

滚动视图可能导致布局更改。
该变更会修复对话框发生绘制溢出的问题。这种布局上的变化是意料之中的。


当代码中有 SingleChildScrollView 嵌套在 AlertDialog.content 时,那么对话框应正常展示。但如果不是有意为之,则应将其移除,否则可能导致代码更为不可读。


A nested SingleChildScrollView in AlertDialog.content should work properly if left in the code, but should be removed if unintended, since it might cause confusion.

迁移前的代码:

Code before migration:

AlertDialog(
  title: Text(
    'Very, very large title that is also scrollable',
    textScaleFactor: 5,
  ),
  content: SingleChildScrollView( // won't be scrollable
    child: Text('Scrollable content', textScaleFactor: 5),
  ),
  actions: <Widget>[
    TextButton(child: Text('Button 1'), onPressed: () {}),
    TextButton(child: Text('Button 2'), onPressed: () {}),
  ],
)

迁移后的代码:

Code after migration:

AlertDialog(
  title: Text('Very, very large title', textScaleFactor: 5),
  content: Text('Very, very large content', textScaleFactor: 5),
  actions: <Widget>[
    TextButton(child: Text('Button 1'), onPressed: () {}),
    TextButton(child: Text('Button 2'), onPressed: () {}),
  ],
)

时间线

Timeline

发布于版本:1.16.3
发布于稳定版本:1.17

Landed in version: 1.16.3
In stable release: 1.17

参考文献

References

设计文档

Design doc:

API 文档:

API documentation:

相关 issue:

Relevant issue:

相关 PRs:

Relevant PRs: