Introduce Switch.padding
(#149884)
fixes [Switch has some padding that leads to uncentered UI](https://github.com/flutter/flutter/issues/148498) ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ ColoredBox( color: Colors.amber, child: Switch( padding: EdgeInsets.zero, value: true, materialTapTargetSize: MaterialTapTargetSize.padded, onChanged: (bool value) {}, ), ), const SizedBox(height: 16), ColoredBox( color: Colors.amber, child: Switch( value: true, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, onChanged: (bool value) {}, ), ), ], ), ), ), ); } } ``` </details> ### Default Switch size <img width="476" alt="Screenshot 2024-07-11 at 13 25 05" src="https://github.com/flutter/flutter/assets/48603081/f9f3f6c6-443d-4bd5-81d4-5e314554b032"> ### Update Switch size using the new `Switch.padding` to address [Switch has some padding that leads to uncentered UI](https://github.com/flutter/flutter/issues/148498) <img width="476" alt="Screenshot 2024-07-11 at 13 24 40" src="https://github.com/flutter/flutter/assets/48603081/aea0717b-e852-4b8d-b703-c8c4999d4863">
This commit is contained in:
parent
22a5c6cb0a
commit
e1cd7b11f6
@ -137,6 +137,9 @@ class _${blockName}DefaultsM3 extends SwitchThemeData {
|
||||
|
||||
@override
|
||||
double get splashRadius => ${getToken('md.comp.switch.state-layer.size')} / 2;
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry? get padding => const EdgeInsets.symmetric(horizontal: 4);
|
||||
}
|
||||
|
||||
class _SwitchConfigM3 with _SwitchConfig {
|
||||
@ -192,13 +195,13 @@ class _SwitchConfigM3 with _SwitchConfig {
|
||||
double get pressedThumbRadius => ${getToken('md.comp.switch.pressed.handle.width')} / 2;
|
||||
|
||||
@override
|
||||
double get switchHeight => _kSwitchMinSize + 8.0;
|
||||
double get switchHeight => switchMinSize.height + 8.0;
|
||||
|
||||
@override
|
||||
double get switchHeightCollapsed => _kSwitchMinSize;
|
||||
double get switchHeightCollapsed => switchMinSize.height;
|
||||
|
||||
@override
|
||||
double get switchWidth => trackWidth - 2 * (trackHeight / 2.0) + _kSwitchMinSize;
|
||||
double get switchWidth => 52.0;
|
||||
|
||||
@override
|
||||
double get thumbRadiusWithIcon => ${getToken('md.comp.switch.with-icon.handle.width')} / 2;
|
||||
@ -223,6 +226,9 @@ class _SwitchConfigM3 with _SwitchConfig {
|
||||
// Hand coded default based on the animation specs.
|
||||
@override
|
||||
double? get thumbOffset => null;
|
||||
|
||||
@override
|
||||
Size get switchMinSize => const Size(kMinInteractiveDimension, kMinInteractiveDimension - 8.0);
|
||||
}
|
||||
''';
|
||||
|
||||
|
@ -23,8 +23,6 @@ import 'theme_data.dart';
|
||||
// bool _giveVerse = true;
|
||||
// late StateSetter setState;
|
||||
|
||||
const double _kSwitchMinSize = kMinInteractiveDimension - 8.0;
|
||||
|
||||
enum _SwitchType { material, adaptive }
|
||||
|
||||
/// A Material Design switch.
|
||||
@ -124,6 +122,7 @@ class Switch extends StatelessWidget {
|
||||
this.focusNode,
|
||||
this.onFocusChange,
|
||||
this.autofocus = false,
|
||||
this.padding,
|
||||
}) : _switchType = _SwitchType.material,
|
||||
applyCupertinoTheme = false,
|
||||
assert(activeThumbImage != null || onActiveThumbImageError == null),
|
||||
@ -177,6 +176,7 @@ class Switch extends StatelessWidget {
|
||||
this.focusNode,
|
||||
this.onFocusChange,
|
||||
this.autofocus = false,
|
||||
this.padding,
|
||||
this.applyCupertinoTheme,
|
||||
}) : assert(activeThumbImage != null || onActiveThumbImageError == null),
|
||||
assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
|
||||
@ -552,9 +552,16 @@ class Switch extends StatelessWidget {
|
||||
/// {@macro flutter.widgets.Focus.autofocus}
|
||||
final bool autofocus;
|
||||
|
||||
/// The amount of space to surround the child inside the bounds of the [Switch].
|
||||
///
|
||||
/// Defaults to horizontal padding of 4 pixels. If [ThemeData.useMaterial3] is false,
|
||||
/// then there is no padding by default.
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
Size _getSwitchSize(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
SwitchThemeData switchTheme = SwitchTheme.of(context);
|
||||
final SwitchThemeData defaults = theme.useMaterial3 ? _SwitchDefaultsM3(context) : _SwitchDefaultsM2(context);
|
||||
if (_switchType == _SwitchType.adaptive) {
|
||||
final Adaptation<SwitchThemeData> switchAdaptation = theme.getAdaptation<SwitchThemeData>()
|
||||
?? const _SwitchThemeAdaptation();
|
||||
@ -565,9 +572,18 @@ class Switch extends StatelessWidget {
|
||||
final MaterialTapTargetSize effectiveMaterialTapTargetSize = materialTapTargetSize
|
||||
?? switchTheme.materialTapTargetSize
|
||||
?? theme.materialTapTargetSize;
|
||||
final EdgeInsetsGeometry effectivePadding = padding
|
||||
?? switchTheme.padding
|
||||
?? defaults.padding!;
|
||||
return switch (effectiveMaterialTapTargetSize) {
|
||||
MaterialTapTargetSize.padded => Size(switchConfig.switchWidth, switchConfig.switchHeight),
|
||||
MaterialTapTargetSize.shrinkWrap => Size(switchConfig.switchWidth, switchConfig.switchHeightCollapsed),
|
||||
MaterialTapTargetSize.padded => Size(
|
||||
switchConfig.switchWidth + effectivePadding.horizontal,
|
||||
switchConfig.switchHeight + effectivePadding.vertical,
|
||||
),
|
||||
MaterialTapTargetSize.shrinkWrap => Size(
|
||||
switchConfig.switchWidth + effectivePadding.horizontal,
|
||||
switchConfig.switchHeightCollapsed + effectivePadding.vertical,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@ -789,7 +805,11 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
return widget.size.width - _kSwitchMinSize;
|
||||
final _SwitchConfig config = Theme.of(context).useMaterial3 ? _SwitchConfigM3(context) : _SwitchConfigM2();
|
||||
final double trackInnerStart = config.trackHeight / 2.0;
|
||||
final double trackInnerEnd = config.trackWidth - trackInnerStart;
|
||||
final double trackInnerLength = trackInnerEnd - trackInnerStart;
|
||||
return trackInnerLength;
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
final _SwitchConfig config = _SwitchConfigCupertino(context);
|
||||
@ -799,7 +819,11 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
|
||||
return trackInnerLength;
|
||||
}
|
||||
case _SwitchType.material:
|
||||
return widget.size.width - _kSwitchMinSize;
|
||||
final _SwitchConfig config = Theme.of(context).useMaterial3 ? _SwitchConfigM3(context) : _SwitchConfigM2();
|
||||
final double trackInnerStart = config.trackHeight / 2.0;
|
||||
final double trackInnerEnd = config.trackWidth - trackInnerStart;
|
||||
final double trackInnerLength = trackInnerEnd - trackInnerStart;
|
||||
return trackInnerLength;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1782,6 +1806,7 @@ mixin _SwitchConfig {
|
||||
double? get thumbOffset;
|
||||
Size get transitionalThumbSize;
|
||||
int get toggleDuration;
|
||||
Size get switchMinSize;
|
||||
}
|
||||
|
||||
// Hand coded defaults for iOS/macOS Switch
|
||||
@ -1862,10 +1887,10 @@ class _SwitchConfigCupertino with _SwitchConfig {
|
||||
double get pressedThumbRadius => 14.0;
|
||||
|
||||
@override
|
||||
double get switchHeight => _kSwitchMinSize + 8.0;
|
||||
double get switchHeight => switchMinSize.height + 8.0;
|
||||
|
||||
@override
|
||||
double get switchHeightCollapsed => _kSwitchMinSize;
|
||||
double get switchHeightCollapsed => switchMinSize.height;
|
||||
|
||||
@override
|
||||
double get switchWidth => 60.0;
|
||||
@ -1904,6 +1929,9 @@ class _SwitchConfigCupertino with _SwitchConfig {
|
||||
// Hand coded default based on the animation specs.
|
||||
@override
|
||||
double? get thumbOffset => null;
|
||||
|
||||
@override
|
||||
Size get switchMinSize => const Size.square(kMinInteractiveDimension - 8.0);
|
||||
}
|
||||
|
||||
// Hand coded defaults based on Material Design 2.
|
||||
@ -1923,13 +1951,13 @@ class _SwitchConfigM2 with _SwitchConfig {
|
||||
double get pressedThumbRadius => 10.0;
|
||||
|
||||
@override
|
||||
double get switchHeight => _kSwitchMinSize + 8.0;
|
||||
double get switchHeight => switchMinSize.height + 8.0;
|
||||
|
||||
@override
|
||||
double get switchHeightCollapsed => _kSwitchMinSize;
|
||||
double get switchHeightCollapsed => switchMinSize.height;
|
||||
|
||||
@override
|
||||
double get switchWidth => trackWidth - 2 * (trackHeight / 2.0) + _kSwitchMinSize;
|
||||
double get switchWidth => trackWidth - 2 * (trackHeight / 2.0) + switchMinSize.width;
|
||||
|
||||
@override
|
||||
double get thumbRadiusWithIcon => 10.0;
|
||||
@ -1951,6 +1979,9 @@ class _SwitchConfigM2 with _SwitchConfig {
|
||||
|
||||
@override
|
||||
int get toggleDuration => 200;
|
||||
|
||||
@override
|
||||
Size get switchMinSize => const Size.square(kMinInteractiveDimension - 8.0);
|
||||
}
|
||||
|
||||
class _SwitchDefaultsM2 extends SwitchThemeData {
|
||||
@ -2021,6 +2052,9 @@ class _SwitchDefaultsM2 extends SwitchThemeData {
|
||||
|
||||
@override
|
||||
double get splashRadius => kRadialReactionRadius;
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry? get padding => EdgeInsets.zero;
|
||||
}
|
||||
|
||||
// BEGIN GENERATED TOKEN PROPERTIES - Switch
|
||||
@ -2156,6 +2190,9 @@ class _SwitchDefaultsM3 extends SwitchThemeData {
|
||||
|
||||
@override
|
||||
double get splashRadius => 40.0 / 2;
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry? get padding => const EdgeInsets.symmetric(horizontal: 4);
|
||||
}
|
||||
|
||||
class _SwitchConfigM3 with _SwitchConfig {
|
||||
@ -2211,13 +2248,13 @@ class _SwitchConfigM3 with _SwitchConfig {
|
||||
double get pressedThumbRadius => 28.0 / 2;
|
||||
|
||||
@override
|
||||
double get switchHeight => _kSwitchMinSize + 8.0;
|
||||
double get switchHeight => switchMinSize.height + 8.0;
|
||||
|
||||
@override
|
||||
double get switchHeightCollapsed => _kSwitchMinSize;
|
||||
double get switchHeightCollapsed => switchMinSize.height;
|
||||
|
||||
@override
|
||||
double get switchWidth => trackWidth - 2 * (trackHeight / 2.0) + _kSwitchMinSize;
|
||||
double get switchWidth => 52.0;
|
||||
|
||||
@override
|
||||
double get thumbRadiusWithIcon => 24.0 / 2;
|
||||
@ -2242,6 +2279,9 @@ class _SwitchConfigM3 with _SwitchConfig {
|
||||
// Hand coded default based on the animation specs.
|
||||
@override
|
||||
double? get thumbOffset => null;
|
||||
|
||||
@override
|
||||
Size get switchMinSize => const Size(kMinInteractiveDimension, kMinInteractiveDimension - 8.0);
|
||||
}
|
||||
|
||||
// END GENERATED TOKEN PROPERTIES - Switch
|
||||
|
@ -46,6 +46,7 @@ class SwitchThemeData with Diagnosticable {
|
||||
this.overlayColor,
|
||||
this.splashRadius,
|
||||
this.thumbIcon,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
/// {@macro flutter.material.switch.thumbColor}
|
||||
@ -94,6 +95,9 @@ class SwitchThemeData with Diagnosticable {
|
||||
/// It is overridden by [Switch.thumbIcon].
|
||||
final MaterialStateProperty<Icon?>? thumbIcon;
|
||||
|
||||
/// If specified, overrides the default value of [Switch.padding].
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
/// Creates a copy of this object but with the given fields replaced with the
|
||||
/// new values.
|
||||
SwitchThemeData copyWith({
|
||||
@ -106,6 +110,7 @@ class SwitchThemeData with Diagnosticable {
|
||||
MaterialStateProperty<Color?>? overlayColor,
|
||||
double? splashRadius,
|
||||
MaterialStateProperty<Icon?>? thumbIcon,
|
||||
EdgeInsetsGeometry? padding,
|
||||
}) {
|
||||
return SwitchThemeData(
|
||||
thumbColor: thumbColor ?? this.thumbColor,
|
||||
@ -117,6 +122,7 @@ class SwitchThemeData with Diagnosticable {
|
||||
overlayColor: overlayColor ?? this.overlayColor,
|
||||
splashRadius: splashRadius ?? this.splashRadius,
|
||||
thumbIcon: thumbIcon ?? this.thumbIcon,
|
||||
padding: padding ?? this.padding,
|
||||
);
|
||||
}
|
||||
|
||||
@ -137,6 +143,7 @@ class SwitchThemeData with Diagnosticable {
|
||||
overlayColor: MaterialStateProperty.lerp<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
|
||||
splashRadius: lerpDouble(a?.splashRadius, b?.splashRadius, t),
|
||||
thumbIcon: t < 0.5 ? a?.thumbIcon : b?.thumbIcon,
|
||||
padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
|
||||
);
|
||||
}
|
||||
|
||||
@ -151,6 +158,7 @@ class SwitchThemeData with Diagnosticable {
|
||||
overlayColor,
|
||||
splashRadius,
|
||||
thumbIcon,
|
||||
padding,
|
||||
);
|
||||
|
||||
@override
|
||||
@ -170,7 +178,8 @@ class SwitchThemeData with Diagnosticable {
|
||||
&& other.mouseCursor == mouseCursor
|
||||
&& other.overlayColor == overlayColor
|
||||
&& other.splashRadius == splashRadius
|
||||
&& other.thumbIcon == thumbIcon;
|
||||
&& other.thumbIcon == thumbIcon
|
||||
&& other.padding == padding;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -185,6 +194,7 @@ class SwitchThemeData with Diagnosticable {
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
|
||||
properties.add(DoubleProperty('splashRadius', splashRadius, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<Icon?>>('thumbIcon', thumbIcon, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4091,6 +4091,34 @@ void main() {
|
||||
|
||||
focusNode.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Switch.padding is respected', (WidgetTester tester) async {
|
||||
Widget buildSwitch({ EdgeInsets? padding }) {
|
||||
return MaterialApp(
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: Switch(
|
||||
padding: padding,
|
||||
value: true,
|
||||
onChanged: (_) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildSwitch());
|
||||
|
||||
expect(tester.getSize(find.byType(Switch)), const Size(60.0, 48.0));
|
||||
|
||||
await tester.pumpWidget(buildSwitch(padding: EdgeInsets.zero));
|
||||
|
||||
expect(tester.getSize(find.byType(Switch)), const Size(52.0, 48.0));
|
||||
|
||||
await tester.pumpWidget(buildSwitch(padding: const EdgeInsets.all(4.0)));
|
||||
|
||||
expect(tester.getSize(find.byType(Switch)), const Size(60.0, 56.0));
|
||||
});
|
||||
}
|
||||
|
||||
class DelayedImageProvider extends ImageProvider<DelayedImageProvider> {
|
||||
|
@ -29,6 +29,7 @@ void main() {
|
||||
expect(themeData.overlayColor, null);
|
||||
expect(themeData.splashRadius, null);
|
||||
expect(themeData.thumbIcon, null);
|
||||
expect(themeData.padding, null);
|
||||
|
||||
const SwitchTheme theme = SwitchTheme(data: SwitchThemeData(), child: SizedBox());
|
||||
expect(theme.data.thumbColor, null);
|
||||
@ -40,6 +41,7 @@ void main() {
|
||||
expect(theme.data.overlayColor, null);
|
||||
expect(theme.data.splashRadius, null);
|
||||
expect(theme.data.thumbIcon, null);
|
||||
expect(theme.data.padding, null);
|
||||
});
|
||||
|
||||
testWidgets('Default SwitchThemeData debugFillProperties', (WidgetTester tester) async {
|
||||
@ -66,6 +68,7 @@ void main() {
|
||||
overlayColor: MaterialStatePropertyAll<Color>(Color(0xfffffff2)),
|
||||
splashRadius: 1.0,
|
||||
thumbIcon: MaterialStatePropertyAll<Icon>(Icon(IconData(123))),
|
||||
padding: EdgeInsets.all(4.0),
|
||||
).debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
@ -82,6 +85,7 @@ void main() {
|
||||
expect(description[6], 'overlayColor: WidgetStatePropertyAll(Color(0xfffffff2))');
|
||||
expect(description[7], 'splashRadius: 1.0');
|
||||
expect(description[8], 'thumbIcon: WidgetStatePropertyAll(Icon(IconData(U+0007B)))');
|
||||
expect(description[9], 'padding: EdgeInsets.all(4.0)');
|
||||
});
|
||||
|
||||
testWidgets('Material2 - Switch is themeable', (WidgetTester tester) async {
|
||||
@ -1041,6 +1045,40 @@ void main() {
|
||||
..rrect(color: localThemeThumbColor)
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('SwitchTheme padding is respected', (WidgetTester tester) async {
|
||||
Widget buildSwitch({ EdgeInsets? padding }) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(
|
||||
switchTheme: SwitchThemeData(
|
||||
padding: padding,
|
||||
),
|
||||
),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: Switch(
|
||||
value: true,
|
||||
onChanged: (_) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildSwitch());
|
||||
|
||||
expect(tester.getSize(find.byType(Switch)), const Size(60.0, 48.0));
|
||||
|
||||
await tester.pumpWidget(buildSwitch(padding: EdgeInsets.zero));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getSize(find.byType(Switch)), const Size(52.0, 48.0));
|
||||
|
||||
await tester.pumpWidget(buildSwitch(padding: const EdgeInsets.all(4.0)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getSize(find.byType(Switch)), const Size(60.0, 56.0));
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _pointGestureToSwitch(WidgetTester tester) async {
|
||||
|
Loading…
x
Reference in New Issue
Block a user