Skip to content

Commit 539c5b9

Browse files
authored
fix the issue that can't set app bar traversal order for flexible space (#162910)
fix flutter/flutter#98570 ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent 5fa05ab commit 539c5b9

File tree

2 files changed

+191
-2
lines changed

2 files changed

+191
-2
lines changed

packages/flutter/lib/src/material/app_bar.dart

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
218218
this.titleTextStyle,
219219
this.systemOverlayStyle,
220220
this.forceMaterialTransparency = false,
221+
this.useDefaultSemanticsOrder = true,
221222
this.clipBehavior,
222223
this.actionsPadding,
223224
}) : assert(elevation == null || elevation >= 0.0),
@@ -744,6 +745,24 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
744745
/// {@endtemplate}
745746
final bool forceMaterialTransparency;
746747

748+
/// {@template flutter.material.appbar.useDefaultSemanticsOrder}
749+
/// Whether to use the default semantic ordering for the app bar's children for
750+
/// accessibility traversal order.
751+
///
752+
/// If this is set to true, the app bar will use the default semantic ordering,
753+
/// which places the flexible space after the main content in the semantics tree.
754+
/// This affects how screen readers and other assistive technologies navigate the app bar's content.
755+
///
756+
/// Set this to false if you want to customize semantics traversal order in the app bar.
757+
/// You can then assign [SemanticsSortKey]s to app bar's children to control the order.
758+
///
759+
/// Defaults to true.
760+
///
761+
/// See also:
762+
/// * [SemanticsSortKey], which are keys used to define the accessibility traversal order.
763+
/// {@endtemplate}
764+
final bool useDefaultSemanticsOrder;
765+
747766
/// {@macro flutter.material.Material.clipBehavior}
748767
final Clip? clipBehavior;
749768

@@ -1150,12 +1169,12 @@ class _AppBarState extends State<AppBar> {
11501169
fit: StackFit.passthrough,
11511170
children: <Widget>[
11521171
Semantics(
1153-
sortKey: const OrdinalSortKey(1.0),
1172+
sortKey: widget.useDefaultSemanticsOrder ? const OrdinalSortKey(1.0) : null,
11541173
explicitChildNodes: true,
11551174
child: widget.flexibleSpace,
11561175
),
11571176
Semantics(
1158-
sortKey: const OrdinalSortKey(0.0),
1177+
sortKey: widget.useDefaultSemanticsOrder ? const OrdinalSortKey(0.0) : null,
11591178
explicitChildNodes: true,
11601179
// Creates a material widget to prevent the flexibleSpace from
11611180
// obscuring the ink splashes produced by appBar children.
@@ -1238,6 +1257,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
12381257
required this.titleTextStyle,
12391258
required this.systemOverlayStyle,
12401259
required this.forceMaterialTransparency,
1260+
required this.useDefaultSemanticsOrder,
12411261
required this.clipBehavior,
12421262
required this.variant,
12431263
required this.accessibleNavigation,
@@ -1277,6 +1297,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
12771297
final SystemUiOverlayStyle? systemOverlayStyle;
12781298
final double _bottomHeight;
12791299
final bool forceMaterialTransparency;
1300+
final bool useDefaultSemanticsOrder;
12801301
final Clip? clipBehavior;
12811302
final _SliverAppVariant variant;
12821303
final bool accessibleNavigation;
@@ -1369,6 +1390,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
13691390
titleTextStyle: titleTextStyle,
13701391
systemOverlayStyle: systemOverlayStyle,
13711392
forceMaterialTransparency: forceMaterialTransparency,
1393+
useDefaultSemanticsOrder: useDefaultSemanticsOrder,
13721394
actionsPadding: actionsPadding,
13731395
),
13741396
);
@@ -1408,6 +1430,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
14081430
titleTextStyle != oldDelegate.titleTextStyle ||
14091431
systemOverlayStyle != oldDelegate.systemOverlayStyle ||
14101432
forceMaterialTransparency != oldDelegate.forceMaterialTransparency ||
1433+
useDefaultSemanticsOrder != oldDelegate.useDefaultSemanticsOrder ||
14111434
accessibleNavigation != oldDelegate.accessibleNavigation ||
14121435
actionsPadding != oldDelegate.actionsPadding;
14131436
}
@@ -1548,6 +1571,7 @@ class SliverAppBar extends StatefulWidget {
15481571
this.titleTextStyle,
15491572
this.systemOverlayStyle,
15501573
this.forceMaterialTransparency = false,
1574+
this.useDefaultSemanticsOrder = true,
15511575
this.clipBehavior,
15521576
this.actionsPadding,
15531577
}) : assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
@@ -1617,6 +1641,7 @@ class SliverAppBar extends StatefulWidget {
16171641
this.titleTextStyle,
16181642
this.systemOverlayStyle,
16191643
this.forceMaterialTransparency = false,
1644+
this.useDefaultSemanticsOrder = true,
16201645
this.clipBehavior,
16211646
this.actionsPadding,
16221647
}) : assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
@@ -1686,6 +1711,7 @@ class SliverAppBar extends StatefulWidget {
16861711
this.titleTextStyle,
16871712
this.systemOverlayStyle,
16881713
this.forceMaterialTransparency = false,
1714+
this.useDefaultSemanticsOrder = true,
16891715
this.clipBehavior,
16901716
this.actionsPadding,
16911717
}) : assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
@@ -1948,6 +1974,11 @@ class SliverAppBar extends StatefulWidget {
19481974
/// This property is used to configure an [AppBar].
19491975
final bool forceMaterialTransparency;
19501976

1977+
/// {@macro flutter.material.appbar.useDefaultSemanticsOrder}
1978+
///
1979+
/// This property is used to configure an [AppBar].
1980+
final bool useDefaultSemanticsOrder;
1981+
19511982
/// {@macro flutter.material.Material.clipBehavior}
19521983
final Clip? clipBehavior;
19531984

@@ -2107,6 +2138,7 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
21072138
titleTextStyle: widget.titleTextStyle,
21082139
systemOverlayStyle: widget.systemOverlayStyle,
21092140
forceMaterialTransparency: widget.forceMaterialTransparency,
2141+
useDefaultSemanticsOrder: widget.useDefaultSemanticsOrder,
21102142
clipBehavior: widget.clipBehavior,
21112143
variant: widget._variant,
21122144
accessibleNavigation: MediaQuery.of(context).accessibleNavigation,

packages/flutter/test/material/app_bar_test.dart

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,163 @@ void main() {
12671267

12681268
semantics.dispose();
12691269
});
1270+
testWidgets('AppBar has default semantics order', (WidgetTester tester) async {
1271+
final SemanticsTester semantics = SemanticsTester(tester);
1272+
1273+
await tester.pumpWidget(
1274+
MaterialApp(
1275+
home: Center(
1276+
child: AppBar(
1277+
leading: Semantics(sortKey: const OrdinalSortKey(0), child: const Text('Leading')),
1278+
title: Semantics(sortKey: const OrdinalSortKey(2), child: const Text('Title')),
1279+
flexibleSpace: Semantics(
1280+
sortKey: const OrdinalSortKey(1),
1281+
child: const Text('Flexible Space'),
1282+
),
1283+
),
1284+
),
1285+
),
1286+
);
1287+
1288+
expect(
1289+
semantics,
1290+
hasSemantics(
1291+
TestSemantics.root(
1292+
children: <TestSemantics>[
1293+
TestSemantics(
1294+
id: 1,
1295+
textDirection: TextDirection.ltr,
1296+
children: <TestSemantics>[
1297+
TestSemantics(
1298+
id: 2,
1299+
children: <TestSemantics>[
1300+
TestSemantics(
1301+
id: 3,
1302+
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
1303+
children: <TestSemantics>[
1304+
TestSemantics(
1305+
id: 4,
1306+
children: <TestSemantics>[
1307+
TestSemantics(
1308+
id: 7,
1309+
children: <TestSemantics>[
1310+
TestSemantics(
1311+
id: 8,
1312+
label: 'Leading',
1313+
textDirection: TextDirection.ltr,
1314+
),
1315+
TestSemantics(
1316+
id: 9,
1317+
flags: <SemanticsFlag>[
1318+
SemanticsFlag.isHeader,
1319+
SemanticsFlag.namesRoute,
1320+
],
1321+
label: 'Title',
1322+
textDirection: TextDirection.ltr,
1323+
),
1324+
],
1325+
),
1326+
TestSemantics(
1327+
id: 5,
1328+
children: <TestSemantics>[
1329+
TestSemantics(
1330+
id: 6,
1331+
label: 'Flexible Space',
1332+
textDirection: TextDirection.ltr,
1333+
),
1334+
],
1335+
),
1336+
],
1337+
),
1338+
],
1339+
),
1340+
],
1341+
),
1342+
],
1343+
),
1344+
],
1345+
),
1346+
ignoreRect: true,
1347+
ignoreTransform: true,
1348+
),
1349+
);
1350+
1351+
semantics.dispose();
1352+
});
1353+
testWidgets('AppBar can customize sort keys for flexible space', (WidgetTester tester) async {
1354+
final SemanticsTester semantics = SemanticsTester(tester);
1355+
1356+
await tester.pumpWidget(
1357+
MaterialApp(
1358+
home: Center(
1359+
child: AppBar(
1360+
leading: Semantics(sortKey: const OrdinalSortKey(0), child: const Text('Leading')),
1361+
title: Semantics(sortKey: const OrdinalSortKey(2), child: const Text('Title')),
1362+
flexibleSpace: Semantics(
1363+
sortKey: const OrdinalSortKey(1),
1364+
child: const Text('Flexible Space'),
1365+
),
1366+
useDefaultSemanticsOrder: false,
1367+
),
1368+
),
1369+
),
1370+
);
1371+
1372+
expect(
1373+
semantics,
1374+
hasSemantics(
1375+
TestSemantics.root(
1376+
children: <TestSemantics>[
1377+
TestSemantics(
1378+
id: 1,
1379+
textDirection: TextDirection.ltr,
1380+
children: <TestSemantics>[
1381+
TestSemantics(
1382+
id: 2,
1383+
children: <TestSemantics>[
1384+
TestSemantics(
1385+
id: 3,
1386+
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
1387+
children: <TestSemantics>[
1388+
TestSemantics(
1389+
id: 4,
1390+
children: <TestSemantics>[
1391+
TestSemantics(
1392+
id: 6,
1393+
label: 'Leading',
1394+
textDirection: TextDirection.ltr,
1395+
),
1396+
TestSemantics(
1397+
id: 5,
1398+
label: 'Flexible Space',
1399+
textDirection: TextDirection.ltr,
1400+
),
1401+
TestSemantics(
1402+
id: 7,
1403+
flags: <SemanticsFlag>[
1404+
SemanticsFlag.isHeader,
1405+
SemanticsFlag.namesRoute,
1406+
],
1407+
label: 'Title',
1408+
textDirection: TextDirection.ltr,
1409+
),
1410+
],
1411+
),
1412+
],
1413+
),
1414+
],
1415+
),
1416+
],
1417+
),
1418+
],
1419+
),
1420+
ignoreRect: true,
1421+
ignoreTransform: true,
1422+
),
1423+
);
1424+
1425+
semantics.dispose();
1426+
});
12701427

12711428
testWidgets('Material3 - AppBar draws a light system bar for a dark background', (
12721429
WidgetTester tester,

0 commit comments

Comments
 (0)