Unverified Commit bbb080b3 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Material Switch optionally adapts per platform: Switch.adaptive() (#22688)

parent d422e85f
......@@ -187,7 +187,7 @@ class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Switch(
Switch.adaptive(
value: switchValue,
onChanged: (bool value) {
setState(() {
......@@ -196,8 +196,8 @@ class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
}
),
// Disabled switches
const Switch(value: true, onChanged: null),
const Switch(value: false, onChanged: null)
const Switch.adaptive(value: true, onChanged: null),
const Switch.adaptive(value: false, onChanged: null),
],
),
);
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
......@@ -23,6 +24,8 @@ const double _kSwitchWidth = _kTrackWidth - 2 * _kTrackRadius + 2 * kRadialReact
const double _kSwitchHeight = 2 * kRadialReactionRadius + 8.0;
const double _kSwitchHeightCollapsed = 2 * kRadialReactionRadius;
enum _SwitchType { material, adaptive }
/// A material design switch.
///
/// Used to toggle the on/off state of a single setting.
......@@ -65,7 +68,30 @@ class Switch extends StatefulWidget {
this.activeThumbImage,
this.inactiveThumbImage,
this.materialTapTargetSize,
}) : super(key: key);
}) : _switchType = _SwitchType.material,
super(key: key);
/// Creates a [CupertinoSwitch] if the target platform is iOS, creates a
/// material design switch otherwise.
///
/// If a [CupertinoSwitch] is created, the following parameters are
/// ignored: [activeTrackColor], [inactiveThumbColor], [inactiveTrackColor],
/// [activeThumbImage], [inactiveThumbImage], [materialTapTargetSize].
///
/// The target platform is based on the current [Theme]: [ThemeData.platform].
const Switch.adaptive({
Key key,
@required this.value,
@required this.onChanged,
this.activeColor,
this.activeTrackColor,
this.inactiveThumbColor,
this.inactiveTrackColor,
this.activeThumbImage,
this.inactiveThumbImage,
this.materialTapTargetSize,
}) : _switchType = _SwitchType.adaptive,
super(key: key);
/// Whether this switch is on or off.
///
......@@ -104,22 +130,32 @@ class Switch extends StatefulWidget {
/// The color to use on the track when this switch is on.
///
/// Defaults to [ThemeData.toggleableActiveColor] with the opacity set at 50%.
///
/// Ignored if this switch is created with [Switch.adaptive].
final Color activeTrackColor;
/// The color to use on the thumb when this switch is off.
///
/// Defaults to the colors described in the Material design specification.
///
/// Ignored if this switch is created with [Switch.adaptive].
final Color inactiveThumbColor;
/// The color to use on the track when this switch is off.
///
/// Defaults to the colors described in the Material design specification.
///
/// Ignored if this switch is created with [Switch.adaptive].
final Color inactiveTrackColor;
/// An image to use on the thumb of this switch when the switch is on.
///
/// Ignored if this switch is created with [Switch.adaptive].
final ImageProvider activeThumbImage;
/// An image to use on the thumb of this switch when the switch is off.
///
/// Ignored if this switch is created with [Switch.adaptive].
final ImageProvider inactiveThumbImage;
/// Configures the minimum size of the tap target.
......@@ -131,6 +167,8 @@ class Switch extends StatefulWidget {
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize materialTapTargetSize;
final _SwitchType _switchType;
@override
_SwitchState createState() => _SwitchState();
......@@ -143,13 +181,25 @@ class Switch extends StatefulWidget {
}
class _SwitchState extends State<Switch> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
Size getSwitchSize(ThemeData theme) {
switch (widget.materialTapTargetSize ?? theme.materialTapTargetSize) {
case MaterialTapTargetSize.padded:
return const Size(_kSwitchWidth, _kSwitchHeight);
break;
case MaterialTapTargetSize.shrinkWrap:
return const Size(_kSwitchWidth, _kSwitchHeightCollapsed);
break;
}
assert(false);
return null;
}
Widget buildMaterialSwitch(BuildContext context) {
assert(debugCheckHasMaterial(context));
final ThemeData themeData = Theme.of(context);
final bool isDark = themeData.brightness == Brightness.dark;
final ThemeData theme = Theme.of(context);
final bool isDark = theme.brightness == Brightness.dark;
final Color activeThumbColor = widget.activeColor ?? themeData.toggleableActiveColor;
final Color activeThumbColor = widget.activeColor ?? theme.toggleableActiveColor;
final Color activeTrackColor = widget.activeTrackColor ?? activeThumbColor.withAlpha(0x80);
Color inactiveThumbColor;
......@@ -162,16 +212,6 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
inactiveThumbColor = widget.inactiveThumbColor ?? (isDark ? Colors.grey.shade800 : Colors.grey.shade400);
inactiveTrackColor = widget.inactiveTrackColor ?? (isDark ? Colors.white10 : Colors.black12);
}
Size size;
switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) {
case MaterialTapTargetSize.padded:
size = const Size(_kSwitchWidth, _kSwitchHeight);
break;
case MaterialTapTargetSize.shrinkWrap:
size = const Size(_kSwitchWidth, _kSwitchHeightCollapsed);
break;
}
final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
return _SwitchRenderObjectWidget(
value: widget.value,
......@@ -183,10 +223,46 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
inactiveTrackColor: inactiveTrackColor,
configuration: createLocalImageConfiguration(context),
onChanged: widget.onChanged,
additionalConstraints: additionalConstraints,
additionalConstraints: BoxConstraints.tight(getSwitchSize(theme)),
vsync: this,
);
}
Widget buildCupertinoSwitch(BuildContext context) {
final Size size = getSwitchSize(Theme.of(context));
return Container(
width: size.width, // Same size as the Material switch.
height: size.height,
alignment: Alignment.center,
child: CupertinoSwitch(
value: widget.value,
onChanged: widget.onChanged,
activeColor: widget.activeColor,
),
);
}
@override
Widget build(BuildContext context) {
switch (widget._switchType) {
case _SwitchType.material:
return buildMaterialSwitch(context);
case _SwitchType.adaptive: {
final ThemeData theme = Theme.of(context);
assert(theme.platform != null);
switch (theme.platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return buildMaterialSwitch(context);
case TargetPlatform.iOS:
return buildCupertinoSwitch(context);
}
}
}
assert(false);
return null;
}
}
class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
......@@ -485,4 +487,46 @@ void main() {
semanticsTester.dispose();
SystemChannels.accessibility.setMockMessageHandler(null);
});
testWidgets('Switch.adaptive', (WidgetTester tester) async {
bool value = false;
Widget buildFrame(TargetPlatform platform) {
return MaterialApp(
theme: ThemeData(platform: platform),
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch.adaptive(
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
);
}
await tester.pumpWidget(buildFrame(TargetPlatform.iOS));
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(value, isFalse);
await tester.tap(find.byType(Switch));
expect(value, isTrue);
await tester.pumpWidget(buildFrame(TargetPlatform.android));
await tester.pumpAndSettle(); // Finish the theme change animation.
expect(find.byType(CupertinoSwitch), findsNothing);
expect(value, isTrue);
await tester.tap(find.byType(Switch));
expect(value, isFalse);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment