模态路由中 Overlay 条目的语义顺序
概述
#我们改变了模态路由中 Overlay 条目的语义遍历顺序。现在,无障碍读屏软件(如 TalkBack 或 VoiceOver)会先聚焦模态路由的作用域,而不是其模态障碍物。
背景
#模态路由有两个 Overlay 条目:作用域(scope)和模态障碍物(modal barrier)。作用域是模态路由的实际内容,而模态障碍物是路由的背景,如果其作用域未覆盖整个屏幕。如果模态路由的 barrierDismissible
返回 true
,模态障碍物将变得可聚焦,因为用户可以通过点击模态障碍物来关闭模态路由。此更改特意将无障碍焦点设置为先聚焦作用域,然后再聚焦模态障碍物。
变更说明
#我们在模态路由的两个 Overlay 条目上方添加了额外的语义节点。这些语义节点表示这两个 Overlay 条目的语义遍历顺序。这也改变了语义树的结构。
迁移指南
#如果更新后,您的测试因语义树更改而失败,您可以预期在模态路由 Overlay 条目上方出现一个新节点来迁移您的代码。
迁移前的代码
dart
import 'dart:ui';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart';
void main() {
testWidgets('example test', (WidgetTester tester) async {
final SemanticsHandle handle =
tester.binding.pipelineOwner.ensureSemantics();
// Build our app and trigger a frame.
await tester.pumpWidget(MaterialApp(home: Scaffold(body: Text('test'))));
final SemanticsNode root =
tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode;
final SemanticsNode firstNode = getChild(root);
expect(firstNode.rect, Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));
// Fixes the test by expecting an additional node above the scope route.
final SemanticsNode secondNode = getChild(firstNode);
expect(secondNode.rect, Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));
final SemanticsNode thirdNode = getChild(secondNode);
expect(thirdNode.rect, Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));
expect(thirdNode.hasFlag(SemanticsFlag.scopesRoute), true);
final SemanticsNode forthNode = getChild(thirdNode);
expect(forthNode.rect, Rect.fromLTRB(0.0, 0.0, 56.0, 14.0));
expect(forthNode.label, 'test');
handle.dispose();
});
}
SemanticsNode getChild(SemanticsNode node) {
SemanticsNode child;
bool visiter(SemanticsNode target) {
child = target;
return false;
}
node.visitChildren(visiter);
return child;
}
迁移后的代码
dart
import 'dart:ui';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart';
void main() {
testWidgets('example test', (WidgetTester tester) async {
final SemanticsHandle handle =
tester.binding.pipelineOwner.ensureSemantics();
// Build our app and trigger a frame.
await tester.pumpWidget(MaterialApp(home: Scaffold(body: Text('test'))));
final SemanticsNode root =
tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode;
final SemanticsNode firstNode = getChild(root);
expect(firstNode.rect, Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));
// Fixes the test by expecting an additional node above the scope route.
final SemanticsNode secondNode = getChild(firstNode);
expect(secondNode.rect, Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));
final SemanticsNode thirdNode = getChild(secondNode);
expect(thirdNode.rect, Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));
expect(thirdNode.hasFlag(SemanticsFlag.scopesRoute), true);
final SemanticsNode forthNode = getChild(thirdNode);
expect(forthNode.rect, Rect.fromLTRB(0.0, 0.0, 56.0, 14.0));
expect(forthNode.label, 'test');
handle.dispose();
});
}
SemanticsNode getChild(SemanticsNode node) {
SemanticsNode child;
bool visiter(SemanticsNode target) {
child = target;
return false;
}
node.visitChildren(visiter);
return child;
}
时间线
#已在版本中落地:1.19.0
稳定版本中:1.20
参考资料
#API 文档
相关议题
相关 PR