mirror of
https://github.com/flutter/flutter
synced 2024-10-05 15:59:49 +00:00
Fix ExpansionTile
Expanded/Collapsed announcement is interrupted by VoiceOver (#143936)
fixes [`ExpansionTile` accessibility information doesn't read Expanded/Collapsed (iOS)](https://github.com/flutter/flutter/issues/132264) ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( appBar: AppBar( title: const Text('ExpansionTile'), ), body: const ExpansionTile( title: Text("Title"), children: <Widget>[ Placeholder(), ], ), floatingActionButton: FloatingActionButton( onPressed: () {}, child: const Icon(Icons.add), ), ), ); } } ``` </details> ### Before https://github.com/flutter/flutter/assets/48603081/542d8392-52dc-4319-92ba-215a7164db49 ### After https://github.com/flutter/flutter/assets/48603081/c9225144-4c12-4e92-bc41-4ff82b370ad7
This commit is contained in:
parent
388f3217e4
commit
81f969e807
|
@ -2,6 +2,9 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
|
@ -571,6 +574,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
|||
|
||||
bool _isExpanded = false;
|
||||
late ExpansionTileController _tileController;
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -597,6 +601,8 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
|||
void dispose() {
|
||||
_tileController._state = null;
|
||||
_animationController.dispose();
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -621,7 +627,19 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
|
|||
PageStorage.maybeOf(context)?.writeState(context, _isExpanded);
|
||||
});
|
||||
widget.onExpansionChanged?.call(_isExpanded);
|
||||
SemanticsService.announce(stateHint, textDirection);
|
||||
|
||||
if (defaultTargetPlatform == TargetPlatform.iOS) {
|
||||
// TODO(tahatesser): This is a workaround for VoiceOver interrupting
|
||||
// semantic announcements on iOS. https://github.com/flutter/flutter/issues/122101.
|
||||
_timer?.cancel();
|
||||
_timer = Timer(const Duration(seconds: 1), () {
|
||||
SemanticsService.announce(stateHint, textDirection);
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
});
|
||||
} else {
|
||||
SemanticsService.announce(stateHint, textDirection);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleTap() {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
@Tags(<String>['reduced-test-set'])
|
||||
library;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
@ -788,7 +789,41 @@ void main() {
|
|||
// "Collapsed".
|
||||
expect(tester.takeAnnouncements().first.message, localizations.expandedHint);
|
||||
handle.dispose();
|
||||
});
|
||||
}, skip: defaultTargetPlatform == TargetPlatform.iOS); // [intended] https://github.com/flutter/flutter/issues/122101.
|
||||
|
||||
// This is a regression test for https://github.com/flutter/flutter/issues/132264.
|
||||
testWidgets('ExpansionTile Semantics announcement is delayed on iOS', (WidgetTester tester) async {
|
||||
final SemanticsHandle handle = tester.ensureSemantics();
|
||||
const DefaultMaterialLocalizations localizations = DefaultMaterialLocalizations();
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Material(
|
||||
child: ExpansionTile(
|
||||
title: Text('Title'),
|
||||
children: <Widget>[
|
||||
SizedBox(height: 100, width: 100),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// There is no semantics announcement without tap action.
|
||||
expect(tester.takeAnnouncements(), isEmpty);
|
||||
|
||||
// Tap the title to expand ExpansionTile.
|
||||
await tester.tap(find.text('Title'));
|
||||
await tester.pump(const Duration(seconds: 1)); // Wait for the announcement to be made.
|
||||
|
||||
expect(tester.takeAnnouncements().first.message, localizations.collapsedHint);
|
||||
|
||||
// Tap the title to collapse ExpansionTile.
|
||||
await tester.tap(find.text('Title'));
|
||||
await tester.pump(const Duration(seconds: 1)); // Wait for the announcement to be made.
|
||||
|
||||
expect(tester.takeAnnouncements().first.message, localizations.expandedHint);
|
||||
handle.dispose();
|
||||
}, variant: TargetPlatformVariant.only(TargetPlatform.iOS));
|
||||
|
||||
testWidgets('Semantics with the onTapHint is an ancestor of ListTile', (WidgetTester tester) async {
|
||||
// This is a regression test for https://github.com/flutter/flutter/pull/121624
|
||||
|
|
Loading…
Reference in a new issue