From e868e2b3834f92a3d7a5896c482d4cc51b1f351c Mon Sep 17 00:00:00 2001
From: Furkan Acar <65075121+AcarFurkan@users.noreply.github.com>
Date: Wed, 3 Apr 2024 19:16:52 +0300
Subject: [PATCH] Add SegmentedButton expand feature (#142804)
fix #139486
The `expandedInsets` property enhancement for the `SegmentedButton` widget introduces flexibility in button layout design within Flutter applications. When `expandedInsets` is not null, this property allows the `SegmentedButton` to expand, filling the available horizontal space of its parent container and also have the specified insets, thus ensuring a uniform distribution of segment widths across the button. Conversely, with `expandedInsets` is null, the widget sizes itself based on the intrinsic sizes of its segments, preserving the natural width of each segment. This addition enhances the adaptability of the `SegmentedButton` to various UI designs, enabling developers to achieve both expansive and compact button layouts seamlessly.
### Setting expandedInsets = null
### Setting expandedInsets = EdgeInsets.zero
---
.../lib/src/material/segmented_button.dart | 58 ++++++++++++++-----
.../test/material/segmented_button_test.dart | 54 +++++++++++++++++
2 files changed, 99 insertions(+), 13 deletions(-)
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 {};