mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
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 <img width="724" alt="image" src="https://github.com/flutter/flutter/assets/65075121/f173b327-a34b-49f0-a7b1-a212a376c5e3"> ### Setting expandedInsets = EdgeInsets.zero <img width="724" alt="image" src="https://github.com/flutter/flutter/assets/36861262/f141a3d3-80e3-4aeb-b7c8-d56ca77ca049">
This commit is contained in:
parent
b63196def2
commit
e868e2b383
@ -130,6 +130,7 @@ class SegmentedButton<T> 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<T> 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<T> extends State<SegmentedButton<T>> {
|
||||
surfaceTintColor: resolve<Color?>((ButtonStyle? style) => style?.surfaceTintColor),
|
||||
child: TextButtonTheme(
|
||||
data: TextButtonThemeData(style: segmentThemeStyle),
|
||||
child: _SegmentedButtonRenderWidget<T>(
|
||||
tapTargetVerticalPadding: tapTargetVerticalPadding,
|
||||
segments: widget.segments,
|
||||
enabledBorder: _enabled ? enabledBorder : disabledBorder,
|
||||
disabledBorder: disabledBorder,
|
||||
direction: direction,
|
||||
children: buttons,
|
||||
child: Padding(
|
||||
padding: widget.expandedInsets ?? EdgeInsets.zero,
|
||||
child: _SegmentedButtonRenderWidget<T>(
|
||||
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<T> 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<T> 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<T> extends MultiChildRenderObjectWidget {
|
||||
disabledBorder: disabledBorder,
|
||||
textDirection: direction,
|
||||
tapTargetVerticalPadding: tapTargetVerticalPadding,
|
||||
isExpanded: isExpanded,
|
||||
);
|
||||
}
|
||||
|
||||
@ -633,11 +648,13 @@ class _RenderSegmentedButton<T> 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<ButtonSegment<T>> get segments => _segments;
|
||||
List<ButtonSegment<T>> _segments;
|
||||
@ -689,6 +706,16 @@ class _RenderSegmentedButton<T> 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<T> 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);
|
||||
|
@ -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<int>(
|
||||
segments: const <ButtonSegment<int>>[
|
||||
ButtonSegment<int>(value: 1, label: Text('Segment 1')),
|
||||
ButtonSegment<int>(value: 2, label: Text('Segment 2')),
|
||||
],
|
||||
selected: const <int>{1},
|
||||
expandedInsets: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
// Get the width of the SegmentedButton.
|
||||
final RenderBox box = tester.renderObject(find.byType(SegmentedButton<int>));
|
||||
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<int>(
|
||||
segments: const <ButtonSegment<int>>[
|
||||
ButtonSegment<int>(value: 1, label: Text('Segment 1')),
|
||||
ButtonSegment<int>(value: 2, label: Text('Segment 2')),
|
||||
],
|
||||
selected: const <int>{1},
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
// Get the width of the SegmentedButton.
|
||||
final RenderBox box = tester.renderObject(find.byType(SegmentedButton<int>));
|
||||
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<MaterialState> enabled = const <MaterialState>{};
|
||||
|
Loading…
Reference in New Issue
Block a user