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.onSelectionChanged,
|
||||||
this.multiSelectionEnabled = false,
|
this.multiSelectionEnabled = false,
|
||||||
this.emptySelectionAllowed = false,
|
this.emptySelectionAllowed = false,
|
||||||
|
this.expandedInsets,
|
||||||
this.style,
|
this.style,
|
||||||
this.showSelectedIcon = true,
|
this.showSelectedIcon = true,
|
||||||
this.selectedIcon,
|
this.selectedIcon,
|
||||||
@ -190,6 +191,13 @@ class SegmentedButton<T> extends StatefulWidget {
|
|||||||
/// [onSelectionChanged] will not be called.
|
/// [onSelectionChanged] will not be called.
|
||||||
final bool emptySelectionAllowed;
|
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
|
/// A static convenience method that constructs a segmented button
|
||||||
/// [ButtonStyle] given simple values.
|
/// [ButtonStyle] given simple values.
|
||||||
///
|
///
|
||||||
@ -539,13 +547,17 @@ class SegmentedButtonState<T> extends State<SegmentedButton<T>> {
|
|||||||
surfaceTintColor: resolve<Color?>((ButtonStyle? style) => style?.surfaceTintColor),
|
surfaceTintColor: resolve<Color?>((ButtonStyle? style) => style?.surfaceTintColor),
|
||||||
child: TextButtonTheme(
|
child: TextButtonTheme(
|
||||||
data: TextButtonThemeData(style: segmentThemeStyle),
|
data: TextButtonThemeData(style: segmentThemeStyle),
|
||||||
child: _SegmentedButtonRenderWidget<T>(
|
child: Padding(
|
||||||
tapTargetVerticalPadding: tapTargetVerticalPadding,
|
padding: widget.expandedInsets ?? EdgeInsets.zero,
|
||||||
segments: widget.segments,
|
child: _SegmentedButtonRenderWidget<T>(
|
||||||
enabledBorder: _enabled ? enabledBorder : disabledBorder,
|
tapTargetVerticalPadding: tapTargetVerticalPadding,
|
||||||
disabledBorder: disabledBorder,
|
segments: widget.segments,
|
||||||
direction: direction,
|
enabledBorder: _enabled ? enabledBorder : disabledBorder,
|
||||||
children: buttons,
|
disabledBorder: disabledBorder,
|
||||||
|
direction: direction,
|
||||||
|
isExpanded: widget.expandedInsets != null,
|
||||||
|
children: buttons,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -588,6 +600,7 @@ class _SegmentedButtonRenderWidget<T> extends MultiChildRenderObjectWidget {
|
|||||||
required this.disabledBorder,
|
required this.disabledBorder,
|
||||||
required this.direction,
|
required this.direction,
|
||||||
required this.tapTargetVerticalPadding,
|
required this.tapTargetVerticalPadding,
|
||||||
|
required this.isExpanded,
|
||||||
required super.children,
|
required super.children,
|
||||||
}) : assert(children.length == segments.length);
|
}) : assert(children.length == segments.length);
|
||||||
|
|
||||||
@ -596,6 +609,7 @@ class _SegmentedButtonRenderWidget<T> extends MultiChildRenderObjectWidget {
|
|||||||
final OutlinedBorder disabledBorder;
|
final OutlinedBorder disabledBorder;
|
||||||
final TextDirection direction;
|
final TextDirection direction;
|
||||||
final double tapTargetVerticalPadding;
|
final double tapTargetVerticalPadding;
|
||||||
|
final bool isExpanded;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RenderObject createRenderObject(BuildContext context) {
|
RenderObject createRenderObject(BuildContext context) {
|
||||||
@ -605,6 +619,7 @@ class _SegmentedButtonRenderWidget<T> extends MultiChildRenderObjectWidget {
|
|||||||
disabledBorder: disabledBorder,
|
disabledBorder: disabledBorder,
|
||||||
textDirection: direction,
|
textDirection: direction,
|
||||||
tapTargetVerticalPadding: tapTargetVerticalPadding,
|
tapTargetVerticalPadding: tapTargetVerticalPadding,
|
||||||
|
isExpanded: isExpanded,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -633,11 +648,13 @@ class _RenderSegmentedButton<T> extends RenderBox with
|
|||||||
required OutlinedBorder disabledBorder,
|
required OutlinedBorder disabledBorder,
|
||||||
required TextDirection textDirection,
|
required TextDirection textDirection,
|
||||||
required double tapTargetVerticalPadding,
|
required double tapTargetVerticalPadding,
|
||||||
|
required bool isExpanded,
|
||||||
}) : _segments = segments,
|
}) : _segments = segments,
|
||||||
_enabledBorder = enabledBorder,
|
_enabledBorder = enabledBorder,
|
||||||
_disabledBorder = disabledBorder,
|
_disabledBorder = disabledBorder,
|
||||||
_textDirection = textDirection,
|
_textDirection = textDirection,
|
||||||
_tapTargetVerticalPadding = tapTargetVerticalPadding;
|
_tapTargetVerticalPadding = tapTargetVerticalPadding,
|
||||||
|
_isExpanded = isExpanded;
|
||||||
|
|
||||||
List<ButtonSegment<T>> get segments => _segments;
|
List<ButtonSegment<T>> get segments => _segments;
|
||||||
List<ButtonSegment<T>> _segments;
|
List<ButtonSegment<T>> _segments;
|
||||||
@ -689,6 +706,16 @@ class _RenderSegmentedButton<T> extends RenderBox with
|
|||||||
markNeedsLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get isExpanded => _isExpanded;
|
||||||
|
bool _isExpanded;
|
||||||
|
set isExpanded(bool value) {
|
||||||
|
if (value == _isExpanded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isExpanded = value;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double computeMinIntrinsicWidth(double height) {
|
double computeMinIntrinsicWidth(double height) {
|
||||||
RenderBox? child = firstChild;
|
RenderBox? child = firstChild;
|
||||||
@ -770,13 +797,18 @@ class _RenderSegmentedButton<T> extends RenderBox with
|
|||||||
|
|
||||||
Size _calculateChildSize(BoxConstraints constraints) {
|
Size _calculateChildSize(BoxConstraints constraints) {
|
||||||
double maxHeight = 0;
|
double maxHeight = 0;
|
||||||
double childWidth = constraints.minWidth / childCount;
|
|
||||||
RenderBox? child = firstChild;
|
RenderBox? child = firstChild;
|
||||||
while (child != null) {
|
double childWidth;
|
||||||
childWidth = math.max(childWidth, child.getMaxIntrinsicWidth(double.infinity));
|
if (_isExpanded) {
|
||||||
child = childAfter(child);
|
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;
|
child = firstChild;
|
||||||
while (child != null) {
|
while (child != null) {
|
||||||
final double boxHeight = child.getMaxIntrinsicHeight(childWidth);
|
final double boxHeight = child.getMaxIntrinsicHeight(childWidth);
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
// machines.
|
// machines.
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
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';
|
||||||
@ -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>{};
|
Set<MaterialState> enabled = const <MaterialState>{};
|
||||||
|
Loading…
Reference in New Issue
Block a user