fix the issue that can't set app bar traversal order for flexible space (#162910)

fix https://github.com/flutter/flutter/issues/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
This commit is contained in:
Hannah Jin 2025-02-13 12:57:41 -08:00 committed by GitHub
parent 5fa05ab6df
commit 539c5b93af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 191 additions and 2 deletions

View File

@ -218,6 +218,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
this.titleTextStyle,
this.systemOverlayStyle,
this.forceMaterialTransparency = false,
this.useDefaultSemanticsOrder = true,
this.clipBehavior,
this.actionsPadding,
}) : assert(elevation == null || elevation >= 0.0),
@ -744,6 +745,24 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
/// {@endtemplate}
final bool forceMaterialTransparency;
/// {@template flutter.material.appbar.useDefaultSemanticsOrder}
/// Whether to use the default semantic ordering for the app bar's children for
/// accessibility traversal order.
///
/// If this is set to true, the app bar will use the default semantic ordering,
/// which places the flexible space after the main content in the semantics tree.
/// This affects how screen readers and other assistive technologies navigate the app bar's content.
///
/// Set this to false if you want to customize semantics traversal order in the app bar.
/// You can then assign [SemanticsSortKey]s to app bar's children to control the order.
///
/// Defaults to true.
///
/// See also:
/// * [SemanticsSortKey], which are keys used to define the accessibility traversal order.
/// {@endtemplate}
final bool useDefaultSemanticsOrder;
/// {@macro flutter.material.Material.clipBehavior}
final Clip? clipBehavior;
@ -1150,12 +1169,12 @@ class _AppBarState extends State<AppBar> {
fit: StackFit.passthrough,
children: <Widget>[
Semantics(
sortKey: const OrdinalSortKey(1.0),
sortKey: widget.useDefaultSemanticsOrder ? const OrdinalSortKey(1.0) : null,
explicitChildNodes: true,
child: widget.flexibleSpace,
),
Semantics(
sortKey: const OrdinalSortKey(0.0),
sortKey: widget.useDefaultSemanticsOrder ? const OrdinalSortKey(0.0) : null,
explicitChildNodes: true,
// Creates a material widget to prevent the flexibleSpace from
// obscuring the ink splashes produced by appBar children.
@ -1238,6 +1257,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
required this.titleTextStyle,
required this.systemOverlayStyle,
required this.forceMaterialTransparency,
required this.useDefaultSemanticsOrder,
required this.clipBehavior,
required this.variant,
required this.accessibleNavigation,
@ -1277,6 +1297,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
final SystemUiOverlayStyle? systemOverlayStyle;
final double _bottomHeight;
final bool forceMaterialTransparency;
final bool useDefaultSemanticsOrder;
final Clip? clipBehavior;
final _SliverAppVariant variant;
final bool accessibleNavigation;
@ -1369,6 +1390,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
titleTextStyle: titleTextStyle,
systemOverlayStyle: systemOverlayStyle,
forceMaterialTransparency: forceMaterialTransparency,
useDefaultSemanticsOrder: useDefaultSemanticsOrder,
actionsPadding: actionsPadding,
),
);
@ -1408,6 +1430,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
titleTextStyle != oldDelegate.titleTextStyle ||
systemOverlayStyle != oldDelegate.systemOverlayStyle ||
forceMaterialTransparency != oldDelegate.forceMaterialTransparency ||
useDefaultSemanticsOrder != oldDelegate.useDefaultSemanticsOrder ||
accessibleNavigation != oldDelegate.accessibleNavigation ||
actionsPadding != oldDelegate.actionsPadding;
}
@ -1548,6 +1571,7 @@ class SliverAppBar extends StatefulWidget {
this.titleTextStyle,
this.systemOverlayStyle,
this.forceMaterialTransparency = false,
this.useDefaultSemanticsOrder = true,
this.clipBehavior,
this.actionsPadding,
}) : assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
@ -1617,6 +1641,7 @@ class SliverAppBar extends StatefulWidget {
this.titleTextStyle,
this.systemOverlayStyle,
this.forceMaterialTransparency = false,
this.useDefaultSemanticsOrder = true,
this.clipBehavior,
this.actionsPadding,
}) : assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
@ -1686,6 +1711,7 @@ class SliverAppBar extends StatefulWidget {
this.titleTextStyle,
this.systemOverlayStyle,
this.forceMaterialTransparency = false,
this.useDefaultSemanticsOrder = true,
this.clipBehavior,
this.actionsPadding,
}) : assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
@ -1948,6 +1974,11 @@ class SliverAppBar extends StatefulWidget {
/// This property is used to configure an [AppBar].
final bool forceMaterialTransparency;
/// {@macro flutter.material.appbar.useDefaultSemanticsOrder}
///
/// This property is used to configure an [AppBar].
final bool useDefaultSemanticsOrder;
/// {@macro flutter.material.Material.clipBehavior}
final Clip? clipBehavior;
@ -2107,6 +2138,7 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
titleTextStyle: widget.titleTextStyle,
systemOverlayStyle: widget.systemOverlayStyle,
forceMaterialTransparency: widget.forceMaterialTransparency,
useDefaultSemanticsOrder: widget.useDefaultSemanticsOrder,
clipBehavior: widget.clipBehavior,
variant: widget._variant,
accessibleNavigation: MediaQuery.of(context).accessibleNavigation,

View File

@ -1267,6 +1267,163 @@ void main() {
semantics.dispose();
});
testWidgets('AppBar has default semantics order', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
MaterialApp(
home: Center(
child: AppBar(
leading: Semantics(sortKey: const OrdinalSortKey(0), child: const Text('Leading')),
title: Semantics(sortKey: const OrdinalSortKey(2), child: const Text('Title')),
flexibleSpace: Semantics(
sortKey: const OrdinalSortKey(1),
child: const Text('Flexible Space'),
),
),
),
),
);
expect(
semantics,
hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
id: 1,
textDirection: TextDirection.ltr,
children: <TestSemantics>[
TestSemantics(
id: 2,
children: <TestSemantics>[
TestSemantics(
id: 3,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
id: 4,
children: <TestSemantics>[
TestSemantics(
id: 7,
children: <TestSemantics>[
TestSemantics(
id: 8,
label: 'Leading',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 9,
flags: <SemanticsFlag>[
SemanticsFlag.isHeader,
SemanticsFlag.namesRoute,
],
label: 'Title',
textDirection: TextDirection.ltr,
),
],
),
TestSemantics(
id: 5,
children: <TestSemantics>[
TestSemantics(
id: 6,
label: 'Flexible Space',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
],
),
],
),
ignoreRect: true,
ignoreTransform: true,
),
);
semantics.dispose();
});
testWidgets('AppBar can customize sort keys for flexible space', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
MaterialApp(
home: Center(
child: AppBar(
leading: Semantics(sortKey: const OrdinalSortKey(0), child: const Text('Leading')),
title: Semantics(sortKey: const OrdinalSortKey(2), child: const Text('Title')),
flexibleSpace: Semantics(
sortKey: const OrdinalSortKey(1),
child: const Text('Flexible Space'),
),
useDefaultSemanticsOrder: false,
),
),
),
);
expect(
semantics,
hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
id: 1,
textDirection: TextDirection.ltr,
children: <TestSemantics>[
TestSemantics(
id: 2,
children: <TestSemantics>[
TestSemantics(
id: 3,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
id: 4,
children: <TestSemantics>[
TestSemantics(
id: 6,
label: 'Leading',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 5,
label: 'Flexible Space',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 7,
flags: <SemanticsFlag>[
SemanticsFlag.isHeader,
SemanticsFlag.namesRoute,
],
label: 'Title',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
],
),
ignoreRect: true,
ignoreTransform: true,
),
);
semantics.dispose();
});
testWidgets('Material3 - AppBar draws a light system bar for a dark background', (
WidgetTester tester,