Android 预测回退
概述
#为了支持 Android 14 的预测回退功能,一系列预先配置的 API 已取代了即时导航 API,例如 WillPopScope
和 Navigator.willPop
。
背景
#Android 14 引入了 预测回退功能,该功能允许用户在有效的回退手势期间预览当前路由之前的界面,并决定是继续回退还是取消手势。这与 Flutter 允许开发者在接收到回退手势后取消回退手势的导航 API 不兼容。
在预测回退中,当用户启动手势时,回退动画会立即开始,并在提交之前。Flutter 应用没有机会在此时间决定是否允许回退。这必须提前知道。
因此,所有允许 Flutter 应用开发者在接收到回退手势时取消回退导航的 API 都已弃用。它们已被等效的 API 取代,这些 API 始终维护一个布尔状态,决定是否可能回退导航。当可能时,预测回退动画会照常发生。否则,导航会被停止。在这两种情况下,应用开发者都会收到有关是否尝试回退以及是否成功的回调。
PopScope
#PopScope
类直接取代 WillPopScope
以启用预测回退。它不是在发生回退时决定是否可能回退,而是通过 canPop
布尔值提前设置。您仍然可以使用 onPopInvoked
来监听回退。
PopScope(
canPop: _myPopDisableEnableLogic(),
onPopInvoked: (bool didPop) {
// Handle the pop. If `didPop` is false, it was blocked.
},
)
Form.canPop 和 Form.onPopInvoked
#这两个新参数基于 PopScope
,并取代了已弃用的 Form.onWillPop
参数。它们与 PopScope
的用法与上面相同。
Form(
canPop: _myPopDisableEnableLogic(),
onPopInvoked: (bool didPop) {
// Handle the pop. If `didPop` is false, it was blocked.
},
)
Route.popDisposition
#此 getter 同步返回路由的 RoutePopDisposition
,该属性描述了回退行为。
if (myRoute.popDisposition == RoutePopDisposition.doNotPop) {
// Back gestures are disabled.
}
ModalRoute.registerPopEntry 和 ModalRoute.unregisterPopEntry
#使用这些方法注册 PopScope
小部件,以便在路由决定是否可以回退时进行评估。当实现自定义 PopScope
小部件时,可能会用到此功能。
@override
void didChangeDependencies() {
super.didChangeDependencies();
final ModalRoute<dynamic>? nextRoute = ModalRoute.of(context);
if (nextRoute != _route) {
_route?.unregisterPopEntry(this);
_route = nextRoute;
_route?.registerPopEntry(this);
}
}
迁移指南
#从 WillPopScope
迁移到 PopScope
#WillPopScope
小部件的直接替代品是 PopScope
小部件。在许多情况下,onWillPop
中在回退手势发生时运行的逻辑可以在构建时完成并设置为 canPop
。
迁移前的代码
WillPopScope(
onWillPop: () async {
return _myCondition;
},
child: ...
),
迁移后的代码
PopScope(
canPop: _myCondition,
child: ...
),
对于需要通知回退尝试的情况,onPopInvoked
方法可以以类似于 onWillPop
的方式使用。请注意,虽然 onWillPop
在回退被处理之前被调用,并且有能力取消它,但 onPopInvoked
在回退处理完成后被调用。
迁移前的代码
WillPopScope(
onWillPop: () async {
_myHandleOnPopMethod();
return true;
},
child: ...
),
迁移后的代码
PopScope(
canPop: true,
onPopInvoked: (bool didPop) {
_myHandleOnPopMethod();
},
child: ...
),
为嵌套 Navigator 从 WillPopScope 迁移到 NavigatorPopHandler
#WillPopScope
的一个非常常见的用例是在使用嵌套 Navigator
小部件时正确处理回退手势。使用 PopScope
也可以做到这一点,但现在有一个包装器小部件使此操作更加容易:NavigatorPopHandler
。
迁移前的代码
WillPopScope(
onWillPop: () async => !(await _nestedNavigatorKey.currentState!.maybePop()),
child: Navigator(
key: _nestedNavigatorKey,
…
),
)
迁移后的代码
NavigatorPopHandler(
onPop: () => _nestedNavigatorKey.currentState!.pop(),
child: Navigator(
key: _nestedNavigatorKey,
…
),
)
从 Form.onWillPop 迁移到 Form.canPop 和 Form.onPopInvoked
#以前,Form
在底层使用了 WillPopScope
实例并暴露了其 onWillPop
方法。这已被 PopScope
取代,后者暴露了其 canPop
和 onPopInvoked
方法。迁移与上面详细介绍的从 WillPopScope
迁移到 PopScope
的方法相同。
从 Route.willPop 迁移到 Route.popDisposition
#Route
的 willPop
方法返回一个 Future<RoutePopDisposition>
,以便适应回退可能被取消的事实。现在由于这不再是必需的,此逻辑已简化为同步 getter。
迁移前的代码
if (await myRoute.willPop() == RoutePopDisposition.doNotPop) {
...
}
迁移后的代码
if (myRoute.popDisposition == RoutePopDisposition.doNotPop) {
...
}
从 ModalRoute.add/removeScopedWillPopCallback 迁移到 ModalRoute.(un)registerPopEntry
#在内部,ModalRoute
通过使用 addScopedWillPopCallback
和 removeScopedWillPopCallback
注册来跟踪其小部件树中 WillPopScope
的存在。由于 PopScope
取代了 WillPopScope
,这些方法已分别被 registerPopEntry
和 unregisterPopEntry
取代。
PopEntry
由 PopScope
实现,以便仅向 ModalRoute
暴露最少必要信息。任何编写自己的 PopScope
的人应该实现 PopEntry
并将其小部件注册和注销到其封闭的 ModalRoute
。
迁移前的代码
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (widget.onWillPop != null) {
_route?.removeScopedWillPopCallback(widget.onWillPop!);
}
_route = ModalRoute.of(context);
if (widget.onWillPop != null) {
_route?.addScopedWillPopCallback(widget.onWillPop!);
}
}
迁移后的代码
@override
void didChangeDependencies() {
super.didChangeDependencies();
_route?.unregisterPopEntry(this);
_route = ModalRoute.of(context);
_route?.registerPopEntry(this);
}
从 ModalRoute.hasScopedWillPopCallback 迁移到 ModalRoute.popDisposition
#此方法以前用于非常类似于预测回退但发生在 Cupertino 库中的用例,其中某些回退转换允许取消导航。当存在 WillPopScope
小部件可能取消回退时,路由转换将被禁用。
现在 API 要求提前决定,这不再需要基于 PopScope
小部件的存在来推测。ModalRoute
是否被 PopScope
小部件阻止回退的确切逻辑已内置到 ModalRoute.popDisposition
中。
迁移前的代码
if (_route.hasScopedWillPopCallback) {
// Disable predictive route transitions.
}
迁移后的代码
if (_route.popDisposition == RoutePopDisposition.doNotPop) {
// Disable predictive route transitions.
}
迁移确认回退对话框
#WillPopScope
有时用于在接收到回退手势时显示确认对话框。使用 PopScope
仍然可以按类似模式完成此操作。
迁移前的代码
WillPopScope(
onWillPop: () async {
final bool? shouldPop = await _showBackDialog();
return shouldPop ?? false;
},
child: child,
)
迁移后的代码
return PopScope(
canPop: false,
onPopInvoked: (bool didPop) async {
if (didPop) {
return;
}
final NavigatorState navigator = Navigator.of(context);
final bool? shouldPop = await _showBackDialog();
if (shouldPop ?? false) {
navigator.pop();
}
},
child: child,
)
支持预测回退
#- 运行 Android 14 (API 级别 34) 或更高版本。
- 在设备上的“开发者选项”中启用预测回退功能标志。在未来的 Android 版本中这将不再需要。
- 在
android/app/src/main/AndroidManifest.xml
中设置android:enableOnBackInvokedCallback="true"
。如果需要,请参阅 Android 官方指南,了解如何迁移 Android 应用以支持预测回退。 - 确保您使用的是 Flutter
3.14.0-7.0.pre
或更高版本。 - 确保您的 Flutter 应用不使用
WillPopScope
小部件。使用它会禁用预测回退。如有需要,请使用PopScope
代替。 - 运行应用并执行回退手势(从屏幕左侧滑动)。
时间线
#首次发布版本:3.14.0-7.0.pre
稳定版本:3.16
参考资料
#API 文档
PopScope
NavigatorPopHandler
PopEntry
Form.canPop
Form.onPopInvoked
Route.popDisposition
ModalRoute.registerPopEntry
ModalRoute.unregisterPopEntry
相关问题
相关 PR