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

diff --git a/packages/flutter/lib/src/material/expansion_tile.dart b/packages/flutter/lib/src/material/expansion_tile.dart
index 5dcfa9c..7d00cf3 100644
--- a/packages/flutter/lib/src/material/expansion_tile.dart
+++ b/packages/flutter/lib/src/material/expansion_tile.dart
@@ -41,10 +41,12 @@
     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 @@
   /// 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 @@
   @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 014aa75..084776f 100644
--- a/packages/flutter/test/material/expansion_tile_test.dart
+++ b/packages/flutter/test/material/expansion_tile_test.dart
@@ -230,6 +230,46 @@
     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(