Add adaptive constructor to Radio and RadioListTile (#123816)
Add adaptive constructor to Radio and RadioListTile
This commit is contained in:
parent
d6593dee96
commit
c8a3dbaf06
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
import 'color_scheme.dart';
|
import 'color_scheme.dart';
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
@ -20,6 +20,8 @@ import 'toggleable.dart';
|
|||||||
// late SingingCharacter? _character;
|
// late SingingCharacter? _character;
|
||||||
// late StateSetter setState;
|
// late StateSetter setState;
|
||||||
|
|
||||||
|
enum _RadioType { material, adaptive }
|
||||||
|
|
||||||
const double _kOuterRadius = 8.0;
|
const double _kOuterRadius = 8.0;
|
||||||
const double _kInnerRadius = 4.5;
|
const double _kInnerRadius = 4.5;
|
||||||
|
|
||||||
@ -93,7 +95,40 @@ class Radio<T> extends StatefulWidget {
|
|||||||
this.visualDensity,
|
this.visualDensity,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.autofocus = false,
|
this.autofocus = false,
|
||||||
});
|
}) : _radioType = _RadioType.material;
|
||||||
|
|
||||||
|
/// Creates an adaptive [Radio] based on whether the target platform is iOS
|
||||||
|
/// or macOS, following Material design's
|
||||||
|
/// [Cross-platform guidelines](https://material.io/design/platform-guidance/cross-platform-adaptation.html).
|
||||||
|
///
|
||||||
|
/// On iOS and macOS, this constructor creates a [CupertinoRadio], which has
|
||||||
|
/// matching functionality and presentation as Material checkboxes, and are the
|
||||||
|
/// graphics expected on iOS. On other platforms, this creates a Material
|
||||||
|
/// design [Radio].
|
||||||
|
///
|
||||||
|
/// If a [CupertinoRadio] is created, the following parameters are ignored:
|
||||||
|
/// [mouseCursor], [fillColor], [hoverColor], [overlayColor], [splashRadius],
|
||||||
|
/// [materialTapTargetSize], [visualDensity].
|
||||||
|
///
|
||||||
|
/// The target platform is based on the current [Theme]: [ThemeData.platform].
|
||||||
|
const Radio.adaptive({
|
||||||
|
super.key,
|
||||||
|
required this.value,
|
||||||
|
required this.groupValue,
|
||||||
|
required this.onChanged,
|
||||||
|
this.mouseCursor,
|
||||||
|
this.toggleable = false,
|
||||||
|
this.activeColor,
|
||||||
|
this.fillColor,
|
||||||
|
this.focusColor,
|
||||||
|
this.hoverColor,
|
||||||
|
this.overlayColor,
|
||||||
|
this.splashRadius,
|
||||||
|
this.materialTapTargetSize,
|
||||||
|
this.visualDensity,
|
||||||
|
this.focusNode,
|
||||||
|
this.autofocus = false,
|
||||||
|
}) : _radioType = _RadioType.adaptive;
|
||||||
|
|
||||||
/// The value represented by this radio button.
|
/// The value represented by this radio button.
|
||||||
final T value;
|
final T value;
|
||||||
@ -309,6 +344,8 @@ class Radio<T> extends StatefulWidget {
|
|||||||
/// {@macro flutter.widgets.Focus.autofocus}
|
/// {@macro flutter.widgets.Focus.autofocus}
|
||||||
final bool autofocus;
|
final bool autofocus;
|
||||||
|
|
||||||
|
final _RadioType _radioType;
|
||||||
|
|
||||||
bool get _selected => value == groupValue;
|
bool get _selected => value == groupValue;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -366,6 +403,33 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
assert(debugCheckHasMaterial(context));
|
assert(debugCheckHasMaterial(context));
|
||||||
|
switch (widget._radioType) {
|
||||||
|
case _RadioType.material:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case _RadioType.adaptive:
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
switch (theme.platform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
case TargetPlatform.fuchsia:
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
case TargetPlatform.windows:
|
||||||
|
break;
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
return CupertinoRadio<T>(
|
||||||
|
value: widget.value,
|
||||||
|
groupValue: widget.groupValue,
|
||||||
|
onChanged: widget.onChanged,
|
||||||
|
toggleable: widget.toggleable,
|
||||||
|
activeColor: widget.activeColor,
|
||||||
|
focusColor: widget.focusColor,
|
||||||
|
focusNode: widget.focusNode,
|
||||||
|
autofocus: widget.autofocus,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final RadioThemeData radioTheme = RadioTheme.of(context);
|
final RadioThemeData radioTheme = RadioTheme.of(context);
|
||||||
final RadioThemeData defaults = Theme.of(context).useMaterial3 ? _RadioDefaultsM3(context) : _RadioDefaultsM2(context);
|
final RadioThemeData defaults = Theme.of(context).useMaterial3 ? _RadioDefaultsM3(context) : _RadioDefaultsM2(context);
|
||||||
final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
|
final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
|
||||||
|
@ -18,6 +18,8 @@ import 'theme_data.dart';
|
|||||||
// enum SingingCharacter { lafayette }
|
// enum SingingCharacter { lafayette }
|
||||||
// late SingingCharacter? _character;
|
// late SingingCharacter? _character;
|
||||||
|
|
||||||
|
enum _RadioType { material, adaptive }
|
||||||
|
|
||||||
/// A [ListTile] with a [Radio]. In other words, a radio button with a label.
|
/// A [ListTile] with a [Radio]. In other words, a radio button with a label.
|
||||||
///
|
///
|
||||||
/// The entire list tile is interactive: tapping anywhere in the tile selects
|
/// The entire list tile is interactive: tapping anywhere in the tile selects
|
||||||
@ -186,7 +188,46 @@ class RadioListTile<T> extends StatelessWidget {
|
|||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.onFocusChange,
|
this.onFocusChange,
|
||||||
this.enableFeedback,
|
this.enableFeedback,
|
||||||
}) : assert(!isThreeLine || subtitle != null);
|
}) : _radioType = _RadioType.material,
|
||||||
|
assert(!isThreeLine || subtitle != null);
|
||||||
|
|
||||||
|
/// Creates a combination of a list tile and a platform adaptive radio.
|
||||||
|
///
|
||||||
|
/// The checkbox uses [Radio.adaptive] to show a [CupertinoRadio] for
|
||||||
|
/// iOS platforms, or [Radio] for all others.
|
||||||
|
///
|
||||||
|
/// All other properties are the same as [RadioListTile].
|
||||||
|
const RadioListTile.adaptive({
|
||||||
|
super.key,
|
||||||
|
required this.value,
|
||||||
|
required this.groupValue,
|
||||||
|
required this.onChanged,
|
||||||
|
this.mouseCursor,
|
||||||
|
this.toggleable = false,
|
||||||
|
this.activeColor,
|
||||||
|
this.fillColor,
|
||||||
|
this.hoverColor,
|
||||||
|
this.overlayColor,
|
||||||
|
this.splashRadius,
|
||||||
|
this.materialTapTargetSize,
|
||||||
|
this.title,
|
||||||
|
this.subtitle,
|
||||||
|
this.isThreeLine = false,
|
||||||
|
this.dense,
|
||||||
|
this.secondary,
|
||||||
|
this.selected = false,
|
||||||
|
this.controlAffinity = ListTileControlAffinity.platform,
|
||||||
|
this.autofocus = false,
|
||||||
|
this.contentPadding,
|
||||||
|
this.shape,
|
||||||
|
this.tileColor,
|
||||||
|
this.selectedTileColor,
|
||||||
|
this.visualDensity,
|
||||||
|
this.focusNode,
|
||||||
|
this.onFocusChange,
|
||||||
|
this.enableFeedback,
|
||||||
|
}) : _radioType = _RadioType.adaptive,
|
||||||
|
assert(!isThreeLine || subtitle != null);
|
||||||
|
|
||||||
/// The value represented by this radio button.
|
/// The value represented by this radio button.
|
||||||
final T value;
|
final T value;
|
||||||
@ -392,22 +433,44 @@ class RadioListTile<T> extends StatelessWidget {
|
|||||||
/// * [Feedback] for providing platform-specific feedback to certain actions.
|
/// * [Feedback] for providing platform-specific feedback to certain actions.
|
||||||
final bool? enableFeedback;
|
final bool? enableFeedback;
|
||||||
|
|
||||||
|
final _RadioType _radioType;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final Widget control = Radio<T>(
|
final Widget control;
|
||||||
value: value,
|
switch (_radioType) {
|
||||||
groupValue: groupValue,
|
case _RadioType.material:
|
||||||
onChanged: onChanged,
|
control = Radio<T>(
|
||||||
toggleable: toggleable,
|
value: value,
|
||||||
activeColor: activeColor,
|
groupValue: groupValue,
|
||||||
materialTapTargetSize: materialTapTargetSize ?? MaterialTapTargetSize.shrinkWrap,
|
onChanged: onChanged,
|
||||||
autofocus: autofocus,
|
toggleable: toggleable,
|
||||||
fillColor: fillColor,
|
activeColor: activeColor,
|
||||||
mouseCursor: mouseCursor,
|
materialTapTargetSize: materialTapTargetSize ?? MaterialTapTargetSize.shrinkWrap,
|
||||||
hoverColor: hoverColor,
|
autofocus: autofocus,
|
||||||
overlayColor: overlayColor,
|
fillColor: fillColor,
|
||||||
splashRadius: splashRadius,
|
mouseCursor: mouseCursor,
|
||||||
);
|
hoverColor: hoverColor,
|
||||||
|
overlayColor: overlayColor,
|
||||||
|
splashRadius: splashRadius,
|
||||||
|
);
|
||||||
|
case _RadioType.adaptive:
|
||||||
|
control = Radio<T>.adaptive(
|
||||||
|
value: value,
|
||||||
|
groupValue: groupValue,
|
||||||
|
onChanged: onChanged,
|
||||||
|
toggleable: toggleable,
|
||||||
|
activeColor: activeColor,
|
||||||
|
materialTapTargetSize: materialTapTargetSize ?? MaterialTapTargetSize.shrinkWrap,
|
||||||
|
autofocus: autofocus,
|
||||||
|
fillColor: fillColor,
|
||||||
|
mouseCursor: mouseCursor,
|
||||||
|
hoverColor: hoverColor,
|
||||||
|
overlayColor: overlayColor,
|
||||||
|
splashRadius: splashRadius,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget? leading, trailing;
|
Widget? leading, trailing;
|
||||||
switch (controlAffinity) {
|
switch (controlAffinity) {
|
||||||
case ListTileControlAffinity.leading:
|
case ListTileControlAffinity.leading:
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
@ -1241,6 +1242,37 @@ void main() {
|
|||||||
expect(tester.getSize(find.byType(Radio<bool>)), const Size(48.0, 48.0));
|
expect(tester.getSize(find.byType(Radio<bool>)), const Size(48.0, 48.0));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('RadioListTile.adaptive shows the correct radio platform widget', (WidgetTester tester) async {
|
||||||
|
Widget buildApp(TargetPlatform platform) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeData(platform: platform),
|
||||||
|
home: Material(
|
||||||
|
child: Center(
|
||||||
|
child: RadioListTile<int>.adaptive(
|
||||||
|
value: 1,
|
||||||
|
groupValue: 2,
|
||||||
|
onChanged: (_) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.iOS, TargetPlatform.macOS ]) {
|
||||||
|
await tester.pumpWidget(buildApp(platform));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byType(CupertinoRadio<int>), findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows ]) {
|
||||||
|
await tester.pumpWidget(buildApp(platform));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byType(CupertinoRadio<int>), findsNothing);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
group('feedback', () {
|
group('feedback', () {
|
||||||
late FeedbackTester feedback;
|
late FeedbackTester feedback;
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ library;
|
|||||||
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
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';
|
||||||
@ -1372,4 +1373,35 @@ void main() {
|
|||||||
: (paints..circle(color: theme.hoverColor)..circle(color: colors.secondary))
|
: (paints..circle(color: theme.hoverColor)..circle(color: colors.secondary))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Radio.adaptive shows the correct platform widget', (WidgetTester tester) async {
|
||||||
|
Widget buildApp(TargetPlatform platform) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeData(platform: platform),
|
||||||
|
home: Material(
|
||||||
|
child: Center(
|
||||||
|
child: Radio<int>.adaptive(
|
||||||
|
value: 1,
|
||||||
|
groupValue: 2,
|
||||||
|
onChanged: (_) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.iOS, TargetPlatform.macOS ]) {
|
||||||
|
await tester.pumpWidget(buildApp(platform));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byType(CupertinoRadio<int>), findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows ]) {
|
||||||
|
await tester.pumpWidget(buildApp(platform));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byType(CupertinoRadio<int>), findsNothing);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user