Skip to content

Commit

Permalink
Introduce Switch.padding (#149884)
Browse files Browse the repository at this point in the history
fixes [Switch has some padding that leads to uncentered UI](flutter/flutter#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](flutter/flutter#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">
  • Loading branch information
TahaTesser authored Jul 16, 2024
1 parent 22a5c6c commit e1cd7b1
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 18 deletions.
12 changes: 9 additions & 3 deletions dev/tools/gen_defaults/lib/switch_template.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
''';

Expand Down
68 changes: 54 additions & 14 deletions packages/flutter/lib/src/material/switch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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();
Expand All @@ -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,
),
};
}

Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -2021,6 +2052,9 @@ class _SwitchDefaultsM2 extends SwitchThemeData {

@override
double get splashRadius => kRadialReactionRadius;

@override
EdgeInsetsGeometry? get padding => EdgeInsets.zero;
}

// BEGIN GENERATED TOKEN PROPERTIES - Switch
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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
12 changes: 11 additions & 1 deletion packages/flutter/lib/src/material/switch_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class SwitchThemeData with Diagnosticable {
this.overlayColor,
this.splashRadius,
this.thumbIcon,
this.padding,
});

/// {@macro flutter.material.switch.thumbColor}
Expand Down Expand Up @@ -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({
Expand All @@ -106,6 +110,7 @@ class SwitchThemeData with Diagnosticable {
MaterialStateProperty<Color?>? overlayColor,
double? splashRadius,
MaterialStateProperty<Icon?>? thumbIcon,
EdgeInsetsGeometry? padding,
}) {
return SwitchThemeData(
thumbColor: thumbColor ?? this.thumbColor,
Expand All @@ -117,6 +122,7 @@ class SwitchThemeData with Diagnosticable {
overlayColor: overlayColor ?? this.overlayColor,
splashRadius: splashRadius ?? this.splashRadius,
thumbIcon: thumbIcon ?? this.thumbIcon,
padding: padding ?? this.padding,
);
}

Expand All @@ -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),
);
}

Expand All @@ -151,6 +158,7 @@ class SwitchThemeData with Diagnosticable {
overlayColor,
splashRadius,
thumbIcon,
padding,
);

@override
Expand All @@ -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
Expand All @@ -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));
}
}

Expand Down
28 changes: 28 additions & 0 deletions packages/flutter/test/material/switch_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
Loading

0 comments on commit e1cd7b1

Please sign in to comment.