SegmentedButton should not create new MaterialStatesController in every build. (#133949)

diff --git a/packages/flutter/lib/src/material/segmented_button.dart b/packages/flutter/lib/src/material/segmented_button.dart
index 1e23716..ea51328 100644
--- a/packages/flutter/lib/src/material/segmented_button.dart
+++ b/packages/flutter/lib/src/material/segmented_button.dart
@@ -96,7 +96,7 @@
 ///     [ToggleButtons].
 ///   * [Radio], an alternative way to present the user with a mutually exclusive set of options.
 ///   * [FilterChip], [ChoiceChip], which can be used when you need to show more than five options.
-class SegmentedButton<T> extends StatelessWidget {
+class SegmentedButton<T> extends StatefulWidget {
   /// Creates a const [SegmentedButton].
   ///
   /// [segments] must contain at least one segment, but it is recommended
@@ -235,27 +235,33 @@
   /// Defaults to an [Icon] with [Icons.check].
   final Widget? selectedIcon;
 
-  bool get _enabled => onSelectionChanged != null;
+  @override
+  State<SegmentedButton<T>> createState() => _SegmentedButtonState<T>();
+}
+
+class _SegmentedButtonState<T> extends State<SegmentedButton<T>> {
+  bool get _enabled => widget.onSelectionChanged != null;
+  final Map<ButtonSegment<T>, MaterialStatesController> _statesControllers = <ButtonSegment<T>, MaterialStatesController>{};
 
   void _handleOnPressed(T segmentValue) {
     if (!_enabled) {
       return;
     }
-    final bool onlySelectedSegment = selected.length == 1 && selected.contains(segmentValue);
-    final bool validChange = emptySelectionAllowed || !onlySelectedSegment;
+    final bool onlySelectedSegment = widget.selected.length == 1 && widget.selected.contains(segmentValue);
+    final bool validChange = widget.emptySelectionAllowed || !onlySelectedSegment;
     if (validChange) {
-      final bool toggle = multiSelectionEnabled || (emptySelectionAllowed && onlySelectedSegment);
+      final bool toggle = widget.multiSelectionEnabled || (widget.emptySelectionAllowed && onlySelectedSegment);
       final Set<T> pressedSegment = <T>{segmentValue};
       late final Set<T> updatedSelection;
       if (toggle) {
-        updatedSelection = selected.contains(segmentValue)
-          ? selected.difference(pressedSegment)
-          : selected.union(pressedSegment);
+        updatedSelection = widget.selected.contains(segmentValue)
+          ? widget.selected.difference(pressedSegment)
+          : widget.selected.union(pressedSegment);
       } else {
         updatedSelection = pressedSegment;
       }
-      if (!setEquals(updatedSelection, selected)) {
-        onSelectionChanged!(updatedSelection);
+      if (!setEquals(updatedSelection, widget.selected)) {
+        widget.onSelectionChanged!(updatedSelection);
       }
     }
   }
@@ -271,7 +277,7 @@
     final Set<MaterialState> currentState = _enabled ? enabledState : disabledState;
 
     P? effectiveValue<P>(P? Function(ButtonStyle? style) getProperty) {
-      late final P? widgetValue  = getProperty(style);
+      late final P? widgetValue  = getProperty(widget.style);
       late final P? themeValue   = getProperty(theme.style);
       late final P? defaultValue = getProperty(defaults.style);
       return widgetValue ?? themeValue ?? defaultValue;
@@ -305,25 +311,24 @@
       );
     }
 
-    final ButtonStyle segmentStyle = segmentStyleFor(style);
+    final ButtonStyle segmentStyle = segmentStyleFor(widget.style);
     final ButtonStyle segmentThemeStyle = segmentStyleFor(theme.style).merge(segmentStyleFor(defaults.style));
-    final Widget? selectedIcon = showSelectedIcon
-      ? this.selectedIcon ?? theme.selectedIcon ?? defaults.selectedIcon
+    final Widget? selectedIcon = widget.showSelectedIcon
+      ? widget.selectedIcon ?? theme.selectedIcon ?? defaults.selectedIcon
       : null;
 
     Widget buttonFor(ButtonSegment<T> segment) {
       final Widget label = segment.label ?? segment.icon ?? const SizedBox.shrink();
-      final bool segmentSelected = selected.contains(segment.value);
-      final Widget? icon = (segmentSelected && showSelectedIcon)
+      final bool segmentSelected = widget.selected.contains(segment.value);
+      final Widget? icon = (segmentSelected && widget.showSelectedIcon)
         ? selectedIcon
         : segment.label != null
           ? segment.icon
           : null;
-      final MaterialStatesController controller = MaterialStatesController(
-        <MaterialState>{
+      final MaterialStatesController controller = _statesControllers.putIfAbsent(segment, () => MaterialStatesController());
+      controller.value = <MaterialState>{
           if (segmentSelected) MaterialState.selected,
-        }
-      );
+      };
 
       final Widget button = icon != null
         ? TextButton.icon(
@@ -350,7 +355,7 @@
       return MergeSemantics(
         child: Semantics(
           checked: segmentSelected,
-          inMutuallyExclusiveGroup: multiSelectionEnabled ? null : true,
+          inMutuallyExclusiveGroup: widget.multiSelectionEnabled ? null : true,
           child: buttonWithTooltip,
         ),
       );
@@ -363,7 +368,7 @@
     final OutlinedBorder enabledBorder = resolvedEnabledBorder.copyWith(side: enabledSide);
     final OutlinedBorder disabledBorder = resolvedDisabledBorder.copyWith(side: disabledSide);
 
-    final List<Widget> buttons = segments.map(buttonFor).toList();
+    final List<Widget> buttons = widget.segments.map(buttonFor).toList();
 
     return Material(
       type: MaterialType.transparency,
@@ -374,7 +379,7 @@
       child: TextButtonTheme(
         data: TextButtonThemeData(style: segmentThemeStyle),
         child: _SegmentedButtonRenderWidget<T>(
-          segments: segments,
+          segments: widget.segments,
           enabledBorder: _enabled ? enabledBorder : disabledBorder,
           disabledBorder: disabledBorder,
           direction: direction,
@@ -383,6 +388,14 @@
       ),
     );
   }
+
+  @override
+  void dispose() {
+    for (final MaterialStatesController controller in _statesControllers.values) {
+      controller.dispose();
+    }
+    super.dispose();
+  }
 }
 class _SegmentedButtonRenderWidget<T> extends MultiChildRenderObjectWidget {
   const _SegmentedButtonRenderWidget({
diff --git a/packages/flutter/test/material/segmented_button_test.dart b/packages/flutter/test/material/segmented_button_test.dart
index 57b3158..cb302dc 100644
--- a/packages/flutter/test/material/segmented_button_test.dart
+++ b/packages/flutter/test/material/segmented_button_test.dart
@@ -9,6 +9,7 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter_test/flutter_test.dart';
+import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
 
 import '../widgets/semantics_tester.dart';
 
@@ -21,7 +22,9 @@
 
 void main() {
 
-  testWidgets('SegmentedButton is built with Material of type MaterialType.transparency', (WidgetTester tester) async {
+  testWidgetsWithLeakTracking('SegmentedButton is built with Material of type MaterialType.transparency',
+  leakTrackingTestConfig: LeakTrackingTestConfig.debugNotDisposed(),
+  (WidgetTester tester) async {
     final ThemeData theme = ThemeData(useMaterial3: true);
     await tester.pumpWidget(
       MaterialApp(