Add option for ExpansionTile to maintain the state of its children when collapsed (#57172)

This commit is contained in:
Ethan Saadia 2020-05-16 17:07:02 -05:00 committed by GitHub
parent 859f2f9bd4
commit 2b2bbfa69f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 4 deletions

View file

@ -41,10 +41,12 @@ class ExpansionTile extends StatefulWidget {
this.children = const <Widget>[],
this.trailing,
this.initiallyExpanded = false,
this.maintainState = false,
this.tilePadding,
this.expandedCrossAxisAlignment,
this.expandedAlignment,
}) : assert(initiallyExpanded != null),
assert(maintainState != null),
assert(
expandedCrossAxisAlignment != CrossAxisAlignment.baseline,
'CrossAxisAlignment.baseline is not supported since the expanded children '
@ -88,6 +90,13 @@ class ExpansionTile extends StatefulWidget {
/// Specifies if the list tile is initially expanded (true) or collapsed (false, the default).
final bool initiallyExpanded;
/// Specifies whether the state of the children is maintained when the tile expands and collapses.
///
/// When true, the children are kept in the tree while the tile is collapsed.
/// When false (default), the children are removed from the tree when the tile is
/// collapsed and recreated upon expansion.
final bool maintainState;
/// Specifies padding for the [ListTile].
///
/// Analogous to [ListTile.contentPadding], this property defines the insets for
@ -253,13 +262,23 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
@override
Widget build(BuildContext context) {
final bool closed = !_isExpanded && _controller.isDismissed;
final bool shouldRemoveChildren = closed && !widget.maintainState;
final Widget result = Offstage(
child: TickerMode(
child: Column(
crossAxisAlignment: widget.expandedCrossAxisAlignment ?? CrossAxisAlignment.center,
children: widget.children,
),
enabled: !closed,
),
offstage: closed
);
return AnimatedBuilder(
animation: _controller.view,
builder: _buildChildren,
child: closed ? null : Column(
crossAxisAlignment: widget.expandedCrossAxisAlignment ?? CrossAxisAlignment.center,
children: widget.children,
),
child: shouldRemoveChildren ? null : result,
);
}

View file

@ -230,6 +230,46 @@ void main() {
expect(find.text('Subtitle'), findsOneWidget);
});
testWidgets('ExpansionTile maintainState', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
platform: TargetPlatform.iOS,
dividerColor: _dividerColor,
),
home: Material(
child: SingleChildScrollView(
child: Column(
children: const <Widget>[
ExpansionTile(
title: Text('Tile 1'),
initiallyExpanded: false,
maintainState: true,
children: <Widget>[
Text('Maintaining State'),
],
),
ExpansionTile(
title: Text('Title 2'),
initiallyExpanded: false,
maintainState: false,
children: <Widget>[
Text('Discarding State'),
],
),
],
),
),
),
));
// This text should be offstage while ExpansionTile collapsed
expect(find.text('Maintaining State', skipOffstage: false), findsOneWidget);
expect(find.text('Maintaining State'), findsNothing);
// This text shouldn't be there while ExpansionTile collapsed
expect(find.text('Discarding State'), findsNothing);
});
testWidgets('ExpansionTile padding test', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Material(