mirror of
https://github.com/flutter/flutter
synced 2024-08-24 18:36:03 +00:00
Fix SegmentedButton
default size and default tappable size (#142243)
fix https://github.com/flutter/flutter/issues/121493 `SegmentedButton` uses `TextButton` for each segments. When we have `MaterialTapTargetSize.padded` for `TextButton`, we make sure the minimum tap target size is 48.0( this value can be adjusted by visual density), even tough the actual button size is smaller. When `SegmentedButton` paints segments by using `MultiChildRenderObjectWidget`, it also includes the tap target size so the button that it actually draws always has the same height as the height of the tap target size. To fix it, this PR firstly calculate the actual height of a text button in `SegmentedButton` class, then we can get the height delta if there is. Then the the value of (Segmented button render box height - the delta) would be the actual button size that we should see. For now, we are not able to customize the min, max, fixed size in [`SegmentedButton` style](https://api.flutter.dev/flutter/material/SegmentedButton/style.html). So the standard button height is always 40 and can only be customized by `style.visualDensity` and `style.tapTargetSize`; `SegmentedButton` only simulates the `TextButton` behavior when `TextButton`'s height is its default value. ![Screenshot 2024-01-25 at 11 45 42â¯AM](https://github.com/flutter/flutter/assets/36861262/7451fa96-6d45-4cd3-a894-ca71e776c8ef) https://github.com/flutter/flutter/assets/36861262/15ca6034-e6e0-4cc6-8fe3-808b4bd6a920
This commit is contained in:
parent
a6c3ad2310
commit
7ff5f81a2e
|
@ -3,14 +3,17 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'button_style.dart';
|
||||
import 'button_style_button.dart';
|
||||
import 'color_scheme.dart';
|
||||
import 'colors.dart';
|
||||
import 'constants.dart';
|
||||
import 'icons.dart';
|
||||
import 'ink_well.dart';
|
||||
import 'material.dart';
|
||||
|
@ -511,18 +514,33 @@ class SegmentedButtonState<T> extends State<SegmentedButton<T>> {
|
|||
final BorderSide disabledSide = resolve<BorderSide?>((ButtonStyle? style) => style?.side, disabledState) ?? BorderSide.none;
|
||||
final OutlinedBorder enabledBorder = resolvedEnabledBorder.copyWith(side: enabledSide);
|
||||
final OutlinedBorder disabledBorder = resolvedDisabledBorder.copyWith(side: disabledSide);
|
||||
final VisualDensity resolvedVisualDensity = segmentStyle.visualDensity ?? segmentThemeStyle.visualDensity ?? Theme.of(context).visualDensity;
|
||||
final EdgeInsetsGeometry resolvedPadding = resolve<EdgeInsetsGeometry?>((ButtonStyle? style) => style?.padding, enabledState) ?? EdgeInsets.zero;
|
||||
final MaterialTapTargetSize resolvedTapTargetSize = segmentStyle.tapTargetSize ?? segmentThemeStyle.tapTargetSize ?? Theme.of(context).materialTapTargetSize;
|
||||
final double fontSize = resolve<TextStyle?>((ButtonStyle? style) => style?.textStyle, enabledState)?.fontSize ?? 20.0;
|
||||
|
||||
final List<Widget> buttons = widget.segments.map(buttonFor).toList();
|
||||
|
||||
final Offset densityAdjustment = resolvedVisualDensity.baseSizeAdjustment;
|
||||
const double textButtonMinHeight = 40.0;
|
||||
|
||||
final double adjustButtonMinHeight = textButtonMinHeight + densityAdjustment.dy;
|
||||
final double effectiveVerticalPadding = resolvedPadding.vertical + densityAdjustment.dy * 2;
|
||||
final double effectedButtonHeight = max(fontSize + effectiveVerticalPadding, adjustButtonMinHeight);
|
||||
final double tapTargetVerticalPadding = switch (resolvedTapTargetSize) {
|
||||
MaterialTapTargetSize.shrinkWrap => 0.0,
|
||||
MaterialTapTargetSize.padded => max(0, kMinInteractiveDimension + densityAdjustment.dy - effectedButtonHeight)
|
||||
};
|
||||
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
shape: enabledBorder.copyWith(side: BorderSide.none),
|
||||
elevation: resolve<double?>((ButtonStyle? style) => style?.elevation)!,
|
||||
shadowColor: resolve<Color?>((ButtonStyle? style) => style?.shadowColor),
|
||||
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,
|
||||
|
@ -569,6 +587,7 @@ class _SegmentedButtonRenderWidget<T> extends MultiChildRenderObjectWidget {
|
|||
required this.enabledBorder,
|
||||
required this.disabledBorder,
|
||||
required this.direction,
|
||||
required this.tapTargetVerticalPadding,
|
||||
required super.children,
|
||||
}) : assert(children.length == segments.length);
|
||||
|
||||
|
@ -576,6 +595,7 @@ class _SegmentedButtonRenderWidget<T> extends MultiChildRenderObjectWidget {
|
|||
final OutlinedBorder enabledBorder;
|
||||
final OutlinedBorder disabledBorder;
|
||||
final TextDirection direction;
|
||||
final double tapTargetVerticalPadding;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
|
@ -584,6 +604,7 @@ class _SegmentedButtonRenderWidget<T> extends MultiChildRenderObjectWidget {
|
|||
enabledBorder: enabledBorder,
|
||||
disabledBorder: disabledBorder,
|
||||
textDirection: direction,
|
||||
tapTargetVerticalPadding: tapTargetVerticalPadding,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -611,10 +632,12 @@ class _RenderSegmentedButton<T> extends RenderBox with
|
|||
required OutlinedBorder enabledBorder,
|
||||
required OutlinedBorder disabledBorder,
|
||||
required TextDirection textDirection,
|
||||
required double tapTargetVerticalPadding,
|
||||
}) : _segments = segments,
|
||||
_enabledBorder = enabledBorder,
|
||||
_disabledBorder = disabledBorder,
|
||||
_textDirection = textDirection;
|
||||
_textDirection = textDirection,
|
||||
_tapTargetVerticalPadding = tapTargetVerticalPadding;
|
||||
|
||||
List<ButtonSegment<T>> get segments => _segments;
|
||||
List<ButtonSegment<T>> _segments;
|
||||
|
@ -656,6 +679,16 @@ class _RenderSegmentedButton<T> extends RenderBox with
|
|||
markNeedsLayout();
|
||||
}
|
||||
|
||||
double get tapTargetVerticalPadding => _tapTargetVerticalPadding;
|
||||
double _tapTargetVerticalPadding;
|
||||
set tapTargetVerticalPadding(double value) {
|
||||
if (value == _tapTargetVerticalPadding) {
|
||||
return;
|
||||
}
|
||||
_tapTargetVerticalPadding = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicWidth(double height) {
|
||||
RenderBox? child = firstChild;
|
||||
|
@ -799,7 +832,8 @@ class _RenderSegmentedButton<T> extends RenderBox with
|
|||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
final Rect borderRect = offset & size;
|
||||
|
||||
final Rect borderRect = (offset + Offset(0, tapTargetVerticalPadding / 2)) & (Size(size.width, size.height - tapTargetVerticalPadding));
|
||||
final Path borderClipPath = enabledBorder.getInnerPath(borderRect, textDirection: textDirection);
|
||||
RenderBox? child = firstChild;
|
||||
RenderBox? previousChild;
|
||||
|
|
|
@ -751,7 +751,6 @@ void main() {
|
|||
of: find.byType(SegmentedButton<int>),
|
||||
matching: find.byType(Material),
|
||||
).first);
|
||||
expect(material.shape, styleFromStyle.shape?.resolve(enabled)?.copyWith(side: BorderSide.none));
|
||||
expect(material.elevation, styleFromStyle.elevation?.resolve(enabled));
|
||||
expect(material.shadowColor, styleFromStyle.shadowColor?.resolve(enabled));
|
||||
expect(material.surfaceTintColor, styleFromStyle.surfaceTintColor?.resolve(enabled));
|
||||
|
@ -813,6 +812,49 @@ void main() {
|
|||
state = tester.state(find.byType(SegmentedButton<int>));
|
||||
expect(state.statesControllers.values.first.value, states);
|
||||
});
|
||||
|
||||
testWidgets('Min button hit target height is 48.0 and min (painted) button height is 40 '
|
||||
'by default with standard density and MaterialTapTargetSize.padded', (WidgetTester tester) async {
|
||||
final ThemeData theme = ThemeData();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SegmentedButton<int>(
|
||||
segments: const <ButtonSegment<int>>[
|
||||
ButtonSegment<int>(value: 0, label: Text('Day'), icon: Icon(Icons.calendar_view_day)),
|
||||
ButtonSegment<int>(value: 1, label: Text('Week'), icon: Icon(Icons.calendar_view_week)),
|
||||
ButtonSegment<int>(value: 2, label: Text('Month'), icon: Icon(Icons.calendar_view_month)),
|
||||
ButtonSegment<int>(value: 3, label: Text('Year'), icon: Icon(Icons.calendar_today)),
|
||||
],
|
||||
selected: const <int>{0},
|
||||
onSelectionChanged: (Set<int> value) {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(theme.visualDensity, VisualDensity.standard);
|
||||
expect(theme.materialTapTargetSize, MaterialTapTargetSize.padded);
|
||||
|
||||
final Finder button = find.byType(SegmentedButton<int>);
|
||||
expect(tester.getSize(button).height, 48.0);
|
||||
expect(
|
||||
find.byType(SegmentedButton<int>),
|
||||
paints..rrect(
|
||||
style: PaintingStyle.stroke,
|
||||
strokeWidth: 1.0,
|
||||
// Button border height is button.bottom(43.5) - button.top(4.5) + stoke width(1) = 40.
|
||||
rrect: RRect.fromLTRBR(0.5, 4.5, 497.5, 43.5, const Radius.circular(19.5))
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Set<MaterialState> enabled = const <MaterialState>{};
|
||||
|
|
Loading…
Reference in a new issue