From 2b2bbfa69f423f2292c9c8bf0457bd13a6c007d0 Mon Sep 17 00:00:00 2001 From: Ethan Saadia Date: Sat, 16 May 2020 17:07:02 -0500 Subject: [PATCH] Add option for ExpansionTile to maintain the state of its children when collapsed (#57172) --- .../lib/src/material/expansion_tile.dart | 27 +++++++++++-- .../test/material/expansion_tile_test.dart | 40 +++++++++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/packages/flutter/lib/src/material/expansion_tile.dart b/packages/flutter/lib/src/material/expansion_tile.dart index 5dcfa9cc492..7d00cf30eac 100644 --- a/packages/flutter/lib/src/material/expansion_tile.dart +++ b/packages/flutter/lib/src/material/expansion_tile.dart @@ -41,10 +41,12 @@ class ExpansionTile extends StatefulWidget { this.children = const [], 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 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, ); } diff --git a/packages/flutter/test/material/expansion_tile_test.dart b/packages/flutter/test/material/expansion_tile_test.dart index 014aa75ec83..084776fcb8e 100644 --- a/packages/flutter/test/material/expansion_tile_test.dart +++ b/packages/flutter/test/material/expansion_tile_test.dart @@ -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 [ + ExpansionTile( + title: Text('Tile 1'), + initiallyExpanded: false, + maintainState: true, + children: [ + Text('Maintaining State'), + ], + ), + ExpansionTile( + title: Text('Title 2'), + initiallyExpanded: false, + maintainState: false, + children: [ + 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(