Unverified Commit cb0a613e authored by Polina Cherkasova's avatar Polina Cherkasova Committed by GitHub

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

parent 85bece26
...@@ -96,7 +96,7 @@ class ButtonSegment<T> { ...@@ -96,7 +96,7 @@ class ButtonSegment<T> {
/// [ToggleButtons]. /// [ToggleButtons].
/// * [Radio], an alternative way to present the user with a mutually exclusive set of options. /// * [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. /// * [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]. /// Creates a const [SegmentedButton].
/// ///
/// [segments] must contain at least one segment, but it is recommended /// [segments] must contain at least one segment, but it is recommended
...@@ -235,27 +235,33 @@ class SegmentedButton<T> extends StatelessWidget { ...@@ -235,27 +235,33 @@ class SegmentedButton<T> extends StatelessWidget {
/// Defaults to an [Icon] with [Icons.check]. /// Defaults to an [Icon] with [Icons.check].
final Widget? selectedIcon; 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) { void _handleOnPressed(T segmentValue) {
if (!_enabled) { if (!_enabled) {
return; return;
} }
final bool onlySelectedSegment = selected.length == 1 && selected.contains(segmentValue); final bool onlySelectedSegment = widget.selected.length == 1 && widget.selected.contains(segmentValue);
final bool validChange = emptySelectionAllowed || !onlySelectedSegment; final bool validChange = widget.emptySelectionAllowed || !onlySelectedSegment;
if (validChange) { if (validChange) {
final bool toggle = multiSelectionEnabled || (emptySelectionAllowed && onlySelectedSegment); final bool toggle = widget.multiSelectionEnabled || (widget.emptySelectionAllowed && onlySelectedSegment);
final Set<T> pressedSegment = <T>{segmentValue}; final Set<T> pressedSegment = <T>{segmentValue};
late final Set<T> updatedSelection; late final Set<T> updatedSelection;
if (toggle) { if (toggle) {
updatedSelection = selected.contains(segmentValue) updatedSelection = widget.selected.contains(segmentValue)
? selected.difference(pressedSegment) ? widget.selected.difference(pressedSegment)
: selected.union(pressedSegment); : widget.selected.union(pressedSegment);
} else { } else {
updatedSelection = pressedSegment; updatedSelection = pressedSegment;
} }
if (!setEquals(updatedSelection, selected)) { if (!setEquals(updatedSelection, widget.selected)) {
onSelectionChanged!(updatedSelection); widget.onSelectionChanged!(updatedSelection);
} }
} }
} }
...@@ -271,7 +277,7 @@ class SegmentedButton<T> extends StatelessWidget { ...@@ -271,7 +277,7 @@ class SegmentedButton<T> extends StatelessWidget {
final Set<MaterialState> currentState = _enabled ? enabledState : disabledState; final Set<MaterialState> currentState = _enabled ? enabledState : disabledState;
P? effectiveValue<P>(P? Function(ButtonStyle? style) getProperty) { 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? themeValue = getProperty(theme.style);
late final P? defaultValue = getProperty(defaults.style); late final P? defaultValue = getProperty(defaults.style);
return widgetValue ?? themeValue ?? defaultValue; return widgetValue ?? themeValue ?? defaultValue;
...@@ -305,25 +311,24 @@ class SegmentedButton<T> extends StatelessWidget { ...@@ -305,25 +311,24 @@ class SegmentedButton<T> extends StatelessWidget {
); );
} }
final ButtonStyle segmentStyle = segmentStyleFor(style); final ButtonStyle segmentStyle = segmentStyleFor(widget.style);
final ButtonStyle segmentThemeStyle = segmentStyleFor(theme.style).merge(segmentStyleFor(defaults.style)); final ButtonStyle segmentThemeStyle = segmentStyleFor(theme.style).merge(segmentStyleFor(defaults.style));
final Widget? selectedIcon = showSelectedIcon final Widget? selectedIcon = widget.showSelectedIcon
? this.selectedIcon ?? theme.selectedIcon ?? defaults.selectedIcon ? widget.selectedIcon ?? theme.selectedIcon ?? defaults.selectedIcon
: null; : null;
Widget buttonFor(ButtonSegment<T> segment) { Widget buttonFor(ButtonSegment<T> segment) {
final Widget label = segment.label ?? segment.icon ?? const SizedBox.shrink(); final Widget label = segment.label ?? segment.icon ?? const SizedBox.shrink();
final bool segmentSelected = selected.contains(segment.value); final bool segmentSelected = widget.selected.contains(segment.value);
final Widget? icon = (segmentSelected && showSelectedIcon) final Widget? icon = (segmentSelected && widget.showSelectedIcon)
? selectedIcon ? selectedIcon
: segment.label != null : segment.label != null
? segment.icon ? segment.icon
: null; : null;
final MaterialStatesController controller = MaterialStatesController( final MaterialStatesController controller = _statesControllers.putIfAbsent(segment, () => MaterialStatesController());
<MaterialState>{ controller.value = <MaterialState>{
if (segmentSelected) MaterialState.selected, if (segmentSelected) MaterialState.selected,
} };
);
final Widget button = icon != null final Widget button = icon != null
? TextButton.icon( ? TextButton.icon(
...@@ -350,7 +355,7 @@ class SegmentedButton<T> extends StatelessWidget { ...@@ -350,7 +355,7 @@ class SegmentedButton<T> extends StatelessWidget {
return MergeSemantics( return MergeSemantics(
child: Semantics( child: Semantics(
checked: segmentSelected, checked: segmentSelected,
inMutuallyExclusiveGroup: multiSelectionEnabled ? null : true, inMutuallyExclusiveGroup: widget.multiSelectionEnabled ? null : true,
child: buttonWithTooltip, child: buttonWithTooltip,
), ),
); );
...@@ -363,7 +368,7 @@ class SegmentedButton<T> extends StatelessWidget { ...@@ -363,7 +368,7 @@ class SegmentedButton<T> extends StatelessWidget {
final OutlinedBorder enabledBorder = resolvedEnabledBorder.copyWith(side: enabledSide); final OutlinedBorder enabledBorder = resolvedEnabledBorder.copyWith(side: enabledSide);
final OutlinedBorder disabledBorder = resolvedDisabledBorder.copyWith(side: disabledSide); 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( return Material(
type: MaterialType.transparency, type: MaterialType.transparency,
...@@ -374,7 +379,7 @@ class SegmentedButton<T> extends StatelessWidget { ...@@ -374,7 +379,7 @@ class SegmentedButton<T> extends StatelessWidget {
child: TextButtonTheme( child: TextButtonTheme(
data: TextButtonThemeData(style: segmentThemeStyle), data: TextButtonThemeData(style: segmentThemeStyle),
child: _SegmentedButtonRenderWidget<T>( child: _SegmentedButtonRenderWidget<T>(
segments: segments, segments: widget.segments,
enabledBorder: _enabled ? enabledBorder : disabledBorder, enabledBorder: _enabled ? enabledBorder : disabledBorder,
disabledBorder: disabledBorder, disabledBorder: disabledBorder,
direction: direction, direction: direction,
...@@ -383,6 +388,14 @@ class SegmentedButton<T> extends StatelessWidget { ...@@ -383,6 +388,14 @@ class SegmentedButton<T> extends StatelessWidget {
), ),
); );
} }
@override
void dispose() {
for (final MaterialStatesController controller in _statesControllers.values) {
controller.dispose();
}
super.dispose();
}
} }
class _SegmentedButtonRenderWidget<T> extends MultiChildRenderObjectWidget { class _SegmentedButtonRenderWidget<T> extends MultiChildRenderObjectWidget {
const _SegmentedButtonRenderWidget({ const _SegmentedButtonRenderWidget({
......
...@@ -9,6 +9,7 @@ import 'dart:ui'; ...@@ -9,6 +9,7 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
...@@ -21,7 +22,9 @@ Widget boilerplate({required Widget child}) { ...@@ -21,7 +22,9 @@ Widget boilerplate({required Widget child}) {
void main() { 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); final ThemeData theme = ThemeData(useMaterial3: true);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment