diff --git a/packages/flutter/lib/src/material/segmented_button.dart b/packages/flutter/lib/src/material/segmented_button.dart index 8eacecd593..ba639de35f 100644 --- a/packages/flutter/lib/src/material/segmented_button.dart +++ b/packages/flutter/lib/src/material/segmented_button.dart @@ -130,6 +130,7 @@ class SegmentedButton extends StatefulWidget { this.onSelectionChanged, this.multiSelectionEnabled = false, this.emptySelectionAllowed = false, + this.expandedInsets, this.style, this.showSelectedIcon = true, this.selectedIcon, @@ -190,6 +191,13 @@ class SegmentedButton extends StatefulWidget { /// [onSelectionChanged] will not be called. final bool emptySelectionAllowed; + /// Determines the segmented button's size and padding based on [expandedInsets]. + /// + /// If null (default), the button adopts its intrinsic content size. When specified, + /// the button expands to fill its parent's space, with the [EdgeInsets] + /// defining the padding. + final EdgeInsets? expandedInsets; + /// A static convenience method that constructs a segmented button /// [ButtonStyle] given simple values. /// @@ -539,13 +547,17 @@ class SegmentedButtonState extends State> { surfaceTintColor: resolve((ButtonStyle? style) => style?.surfaceTintColor), child: TextButtonTheme( data: TextButtonThemeData(style: segmentThemeStyle), - child: _SegmentedButtonRenderWidget( - tapTargetVerticalPadding: tapTargetVerticalPadding, - segments: widget.segments, - enabledBorder: _enabled ? enabledBorder : disabledBorder, - disabledBorder: disabledBorder, - direction: direction, - children: buttons, + child: Padding( + padding: widget.expandedInsets ?? EdgeInsets.zero, + child: _SegmentedButtonRenderWidget( + tapTargetVerticalPadding: tapTargetVerticalPadding, + segments: widget.segments, + enabledBorder: _enabled ? enabledBorder : disabledBorder, + disabledBorder: disabledBorder, + direction: direction, + isExpanded: widget.expandedInsets != null, + children: buttons, + ), ), ), ); @@ -588,6 +600,7 @@ class _SegmentedButtonRenderWidget extends MultiChildRenderObjectWidget { required this.disabledBorder, required this.direction, required this.tapTargetVerticalPadding, + required this.isExpanded, required super.children, }) : assert(children.length == segments.length); @@ -596,6 +609,7 @@ class _SegmentedButtonRenderWidget extends MultiChildRenderObjectWidget { final OutlinedBorder disabledBorder; final TextDirection direction; final double tapTargetVerticalPadding; + final bool isExpanded; @override RenderObject createRenderObject(BuildContext context) { @@ -605,6 +619,7 @@ class _SegmentedButtonRenderWidget extends MultiChildRenderObjectWidget { disabledBorder: disabledBorder, textDirection: direction, tapTargetVerticalPadding: tapTargetVerticalPadding, + isExpanded: isExpanded, ); } @@ -633,11 +648,13 @@ class _RenderSegmentedButton extends RenderBox with required OutlinedBorder disabledBorder, required TextDirection textDirection, required double tapTargetVerticalPadding, + required bool isExpanded, }) : _segments = segments, _enabledBorder = enabledBorder, _disabledBorder = disabledBorder, _textDirection = textDirection, - _tapTargetVerticalPadding = tapTargetVerticalPadding; + _tapTargetVerticalPadding = tapTargetVerticalPadding, + _isExpanded = isExpanded; List> get segments => _segments; List> _segments; @@ -689,6 +706,16 @@ class _RenderSegmentedButton extends RenderBox with markNeedsLayout(); } + bool get isExpanded => _isExpanded; + bool _isExpanded; + set isExpanded(bool value) { + if (value == _isExpanded) { + return; + } + _isExpanded = value; + markNeedsLayout(); + } + @override double computeMinIntrinsicWidth(double height) { RenderBox? child = firstChild; @@ -770,13 +797,18 @@ class _RenderSegmentedButton extends RenderBox with Size _calculateChildSize(BoxConstraints constraints) { double maxHeight = 0; - double childWidth = constraints.minWidth / childCount; RenderBox? child = firstChild; - while (child != null) { - childWidth = math.max(childWidth, child.getMaxIntrinsicWidth(double.infinity)); - child = childAfter(child); + double childWidth; + if (_isExpanded) { + childWidth = constraints.maxWidth / childCount; + } else { + childWidth = constraints.minWidth / childCount; + while (child != null) { + childWidth = math.max(childWidth, child.getMaxIntrinsicWidth(double.infinity)); + child = childAfter(child); + } + childWidth = math.min(childWidth, constraints.maxWidth / childCount); } - childWidth = math.min(childWidth, constraints.maxWidth / childCount); child = firstChild; while (child != null) { final double boxHeight = child.getMaxIntrinsicHeight(childWidth); diff --git a/packages/flutter/test/material/segmented_button_test.dart b/packages/flutter/test/material/segmented_button_test.dart index 0fc3cadf57..d764d74ac0 100644 --- a/packages/flutter/test/material/segmented_button_test.dart +++ b/packages/flutter/test/material/segmented_button_test.dart @@ -6,6 +6,7 @@ // machines. import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -855,6 +856,59 @@ void main() { ) ); }); + + testWidgets('SegmentedButton expands to fill the available width when expandedInsets is not null', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Center( + child: SegmentedButton( + segments: const >[ + ButtonSegment(value: 1, label: Text('Segment 1')), + ButtonSegment(value: 2, label: Text('Segment 2')), + ], + selected: const {1}, + expandedInsets: EdgeInsets.zero, + ), + ), + ), + )); + + // Get the width of the SegmentedButton. + final RenderBox box = tester.renderObject(find.byType(SegmentedButton)); + final double segmentedButtonWidth = box.size.width; + + // Get the width of the parent widget. + final double screenWidth = tester.getSize(find.byType(Scaffold)).width; + + // The width of the SegmentedButton must be equal to the width of the parent widget. + expect(segmentedButtonWidth, equals(screenWidth)); + }); + + testWidgets('SegmentedButton does not expand when expandedInsets is null', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: Center( + child: SegmentedButton( + segments: const >[ + ButtonSegment(value: 1, label: Text('Segment 1')), + ButtonSegment(value: 2, label: Text('Segment 2')), + ], + selected: const {1}, + ), + ), + ), + )); + + // Get the width of the SegmentedButton. + final RenderBox box = tester.renderObject(find.byType(SegmentedButton)); + final double segmentedButtonWidth = box.size.width; + + // Get the width of the parent widget. + final double screenWidth = tester.getSize(find.byType(Scaffold)).width; + + // The width of the SegmentedButton must be less than the width of the parent widget. + expect(segmentedButtonWidth, lessThan(screenWidth)); + }, skip: kIsWeb && !isCanvasKit); // https://github.com/flutter/flutter/issues/145527 } Set enabled = const {};