Add Material 3 RadioListTile example and update existing examples (#119716)

* Add Material 3 `RadioListTile` example and update existing examples

* Update examples with `useMaterial3: true` and update example descriptions.

* add a `ColorScheme` colour
This commit is contained in:
Taha Tesser 2023-02-03 18:27:43 +02:00 committed by GitHub
parent c5e8757fcb
commit 7177c413a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 448 additions and 117 deletions

View file

@ -0,0 +1,118 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flutter code sample for custom labeled radio.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() => runApp(const LabeledRadioApp());
class LabeledRadioApp extends StatelessWidget {
const LabeledRadioApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
home: Scaffold(
appBar: AppBar(title: const Text('Custom Labeled Radio Sample')),
body: const LabeledRadioExample(),
),
);
}
}
class LinkedLabelRadio extends StatelessWidget {
const LinkedLabelRadio({
super.key,
required this.label,
required this.padding,
required this.groupValue,
required this.value,
required this.onChanged,
});
final String label;
final EdgeInsets padding;
final bool groupValue;
final bool value;
final ValueChanged<bool> onChanged;
@override
Widget build(BuildContext context) {
return Padding(
padding: padding,
child: Row(
children: <Widget>[
Radio<bool>(
groupValue: groupValue,
value: value,
onChanged: (bool? newValue) {
onChanged(newValue!);
},
),
RichText(
text: TextSpan(
text: label,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()
..onTap = () {
debugPrint('Label has been tapped.');
},
),
),
],
),
);
}
}
class LabeledRadioExample extends StatefulWidget {
const LabeledRadioExample({super.key});
@override
State<LabeledRadioExample> createState() => _LabeledRadioExampleState();
}
class _LabeledRadioExampleState extends State<LabeledRadioExample> {
bool _isRadioSelected = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
LinkedLabelRadio(
label: 'First tappable label text',
padding: const EdgeInsets.symmetric(horizontal: 5.0),
value: true,
groupValue: _isRadioSelected,
onChanged: (bool newValue) {
setState(() {
_isRadioSelected = newValue;
});
},
),
LinkedLabelRadio(
label: 'Second tappable label text',
padding: const EdgeInsets.symmetric(horizontal: 5.0),
value: false,
groupValue: _isRadioSelected,
onChanged: (bool newValue) {
setState(() {
_isRadioSelected = newValue;
});
},
),
],
),
);
}
}

View file

@ -2,24 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flutter code sample for [RadioListTile].
// Flutter code sample for custom labeled radio.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
void main() => runApp(const LabeledRadioApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
class LabeledRadioApp extends StatelessWidget {
const LabeledRadioApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
theme: ThemeData(useMaterial3: true),
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
appBar: AppBar(title: const Text('Custom Labeled Radio Sample')),
body: const LabeledRadioExample(),
),
);
}
@ -68,14 +66,14 @@ class LabeledRadio extends StatelessWidget {
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
class LabeledRadioExample extends StatefulWidget {
const LabeledRadioExample({super.key});
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
State<LabeledRadioExample> createState() => _LabeledRadioExampleState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
class _LabeledRadioExampleState extends State<LabeledRadioExample> {
bool _isRadioSelected = false;
@override

View file

@ -6,20 +6,18 @@
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
void main() => runApp(const RadioListTileApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
class RadioListTileApp extends StatelessWidget {
const RadioListTileApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
theme: ThemeData(useMaterial3: true),
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
appBar: AppBar(title: const Text('RadioListTile Sample')),
body: const RadioListTileExample(),
),
);
}
@ -27,14 +25,14 @@ class MyApp extends StatelessWidget {
enum SingingCharacter { lafayette, jefferson }
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
class RadioListTileExample extends StatefulWidget {
const RadioListTileExample({super.key});
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
State<RadioListTileExample> createState() => _RadioListTileExampleState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
class _RadioListTileExampleState extends State<RadioListTileExample> {
SingingCharacter? _character = SingingCharacter.lafayette;
@override

View file

@ -4,113 +4,73 @@
// Flutter code sample for [RadioListTile].
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
void main() => runApp(const RadioListTileApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
class RadioListTileApp extends StatelessWidget {
const RadioListTileApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
theme: ThemeData(useMaterial3: true),
home: const RadioListTileExample(),
);
}
}
class LinkedLabelRadio extends StatelessWidget {
const LinkedLabelRadio({
super.key,
required this.label,
required this.padding,
required this.groupValue,
required this.value,
required this.onChanged,
});
enum Groceries { pickles, tomato, lettuce }
final String label;
final EdgeInsets padding;
final bool groupValue;
final bool value;
final ValueChanged<bool> onChanged;
class RadioListTileExample extends StatefulWidget {
const RadioListTileExample({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: padding,
child: Row(
children: <Widget>[
Radio<bool>(
groupValue: groupValue,
value: value,
onChanged: (bool? newValue) {
onChanged(newValue!);
}),
RichText(
text: TextSpan(
text: label,
style: const TextStyle(
color: Colors.blueAccent,
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()
..onTap = () {
debugPrint('Label has been tapped.');
},
),
),
],
),
);
}
State<RadioListTileExample> createState() => _RadioListTileExampleState();
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool _isRadioSelected = false;
class _RadioListTileExampleState extends State<RadioListTileExample> {
Groceries? _groceryItem = Groceries.pickles;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('RadioListTile Sample')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
LinkedLabelRadio(
label: 'First tappable label text',
padding: const EdgeInsets.symmetric(horizontal: 5.0),
value: true,
groupValue: _isRadioSelected,
onChanged: (bool newValue) {
RadioListTile<Groceries>(
value: Groceries.pickles,
groupValue: _groceryItem,
onChanged: (Groceries? value) {
setState(() {
_isRadioSelected = newValue;
_groceryItem = value;
});
},
title: const Text('Pickles'),
subtitle: const Text('Supporting text'),
),
LinkedLabelRadio(
label: 'Second tappable label text',
padding: const EdgeInsets.symmetric(horizontal: 5.0),
value: false,
groupValue: _isRadioSelected,
onChanged: (bool newValue) {
RadioListTile<Groceries>(
value: Groceries.tomato,
groupValue: _groceryItem,
onChanged: (Groceries? value) {
setState(() {
_isRadioSelected = newValue;
_groceryItem = value;
});
},
title: const Text('Tomato'),
subtitle: const Text('Longer supporting text to demonstrate how the text wraps and the radio is centered vertically with the text.'),
),
RadioListTile<Groceries>(
value: Groceries.lettuce,
groupValue: _groceryItem,
onChanged: (Groceries? value) {
setState(() {
_groceryItem = value;
});
},
title: const Text('Lettuce'),
subtitle: const Text("Longer supporting text to demonstrate how the text wraps and how setting 'RadioListTile.isThreeLine = true' aligns the radio to the top vertically with the text."),
isThreeLine: true,
),
],
),

View file

@ -6,33 +6,31 @@
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
void main() => runApp(const RadioListTileApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
class RadioListTileApp extends StatelessWidget {
const RadioListTileApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
theme: ThemeData(useMaterial3: true),
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
appBar: AppBar(title: const Text('RadioListTile.toggleable Sample')),
body: const RadioListTileExample(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
class RadioListTileExample extends StatefulWidget {
const RadioListTileExample({super.key});
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
State<RadioListTileExample> createState() => _RadioListTileExampleState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
class _RadioListTileExampleState extends State<RadioListTileExample> {
int? groupValue;
static const List<String> selections = <String>[
'Hercules Mulligan',

View file

@ -0,0 +1,43 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/radio_list_tile/custom_labeled_radio.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('LinkedLabelRadio contains RichText and Radio', (WidgetTester tester) async {
await tester.pumpWidget(
const example.LabeledRadioApp(),
);
// Label text is in a RichText widget with the correct text.
final RichText richText = tester.widget(find.byType(RichText).first);
expect(richText.text.toPlainText(), 'First tappable label text');
// First Radio is initially unchecked.
Radio<bool> radio = tester.widget(find.byType(Radio<bool>).first);
expect(radio.value, true);
expect(radio.groupValue, false);
// Last Radio is initially checked.
radio = tester.widget(find.byType(Radio<bool>).last);
expect(radio.value, false);
expect(radio.groupValue, false);
// Tap the first radio.
await tester.tap(find.byType(Radio<bool>).first);
await tester.pump();
// First Radio is now checked.
radio = tester.widget(find.byType(Radio<bool>).first);
expect(radio.value, true);
expect(radio.groupValue, true);
// Last Radio is now unchecked.
radio = tester.widget(find.byType(Radio<bool>).last);
expect(radio.value, false);
expect(radio.groupValue, true);
});
}

View file

@ -0,0 +1,39 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/radio_list_tile/custom_labeled_radio.1.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Tapping LabeledRadio toggles the radio', (WidgetTester tester) async {
await tester.pumpWidget(
const example.LabeledRadioApp(),
);
// First Radio is initially unchecked.
Radio<bool> radio = tester.widget(find.byType(Radio<bool>).first);
expect(radio.value, true);
expect(radio.groupValue, false);
// Last Radio is initially checked.
radio = tester.widget(find.byType(Radio<bool>).last);
expect(radio.value, false);
expect(radio.groupValue, false);
// Tap the first labeled radio to toggle the Radio widget.
await tester.tap(find.byType(example.LabeledRadio).first);
await tester.pumpAndSettle();
// First Radio is now checked.
radio = tester.widget(find.byType(Radio<bool>).first);
expect(radio.value, true);
expect(radio.groupValue, true);
// Last Radio is now unchecked.
radio = tester.widget(find.byType(Radio<bool>).last);
expect(radio.value, false);
expect(radio.groupValue, true);
});
}

View file

@ -0,0 +1,38 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/radio_list_tile/radio_list_tile.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Can update RadioListTile group value', (WidgetTester tester) async {
await tester.pumpWidget(
const example.RadioListTileApp(),
);
// Find the number of RadioListTiles.
expect(find.byType(RadioListTile<example.SingingCharacter>), findsNWidgets(2));
// The initial group value is lafayette for the first RadioListTile.
RadioListTile<example.SingingCharacter> radioListTile = tester.widget(find.byType(RadioListTile<example.SingingCharacter>).first);
expect(radioListTile.groupValue, example.SingingCharacter.lafayette);
// The initial group value is lafayette for the last RadioListTile.
radioListTile = tester.widget(find.byType(RadioListTile<example.SingingCharacter>).last);
expect(radioListTile.groupValue, example.SingingCharacter.lafayette);
// Tap the last RadioListTile to change the group value to jefferson.
await tester.tap(find.byType(RadioListTile<example.SingingCharacter>).last);
await tester.pump();
// The group value is now jefferson for the first RadioListTile.
radioListTile = tester.widget(find.byType(RadioListTile<example.SingingCharacter>).first);
expect(radioListTile.groupValue, example.SingingCharacter.jefferson);
// The group value is now jefferson for the last RadioListTile.
radioListTile = tester.widget(find.byType(RadioListTile<example.SingingCharacter>).last);
expect(radioListTile.groupValue, example.SingingCharacter.jefferson);
});
}

View file

@ -0,0 +1,94 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/radio_list_tile/radio_list_tile.1.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Radio aligns appropriately', (WidgetTester tester) async {
await tester.pumpWidget(
const example.RadioListTileApp(),
);
expect(find.byType(RadioListTile<example.Groceries>), findsNWidgets(3));
Offset tileTopLeft = tester.getTopLeft(find.byType(RadioListTile<example.Groceries>).at(0));
Offset radioTopLeft = tester.getTopLeft(find.byType(Radio<example.Groceries>).at(0));
// The radio is centered vertically with the text.
expect(radioTopLeft - tileTopLeft, const Offset(16.0, 16.0));
tileTopLeft = tester.getTopLeft(find.byType(RadioListTile<example.Groceries>).at(1));
radioTopLeft = tester.getTopLeft(find.byType(Radio<example.Groceries>).at(1));
// The radio is centered vertically with the text.
expect(radioTopLeft - tileTopLeft, const Offset(16.0, 30.0));
tileTopLeft = tester.getTopLeft(find.byType(RadioListTile<example.Groceries>).at(2));
radioTopLeft = tester.getTopLeft(find.byType(Radio<example.Groceries>).at(2));
// The radio is aligned to the top vertically with the text.
expect(radioTopLeft - tileTopLeft, const Offset(16.0, 8.0));
});
testWidgets('Radios can be checked', (WidgetTester tester) async {
await tester.pumpWidget(
const example.RadioListTileApp(),
);
expect(find.byType(RadioListTile<example.Groceries>), findsNWidgets(3));
final Finder radioListTile = find.byType(RadioListTile<example.Groceries>);
// Initially the first radio is checked.
expect(
tester.widget<RadioListTile<example.Groceries>>(radioListTile.at(0)).groupValue,
example.Groceries.pickles,
);
expect(
tester.widget<RadioListTile<example.Groceries>>(radioListTile.at(1)).groupValue,
example.Groceries.pickles,
);
expect(
tester.widget<RadioListTile<example.Groceries>>(radioListTile.at(2)).groupValue,
example.Groceries.pickles,
);
// Tap the second radio.
await tester.tap(find.byType(Radio<example.Groceries>).at(1));
await tester.pumpAndSettle();
// The second radio is checked.
expect(
tester.widget<RadioListTile<example.Groceries>>(radioListTile.at(0)).groupValue,
example.Groceries.tomato,
);
expect(
tester.widget<RadioListTile<example.Groceries>>(radioListTile.at(1)).groupValue,
example.Groceries.tomato,
);
expect(
tester.widget<RadioListTile<example.Groceries>>(radioListTile.at(2)).groupValue,
example.Groceries.tomato,
);
// Tap the third radio.
await tester.tap(find.byType(Radio<example.Groceries>).at(2));
await tester.pumpAndSettle();
// The third radio is checked.
expect(
tester.widget<RadioListTile<example.Groceries>>(radioListTile.at(0)).groupValue,
example.Groceries.lettuce,
);
expect(
tester.widget<RadioListTile<example.Groceries>>(radioListTile.at(1)).groupValue,
example.Groceries.lettuce,
);
expect(
tester.widget<RadioListTile<example.Groceries>>(radioListTile.at(2)).groupValue,
example.Groceries.lettuce,
);
});
}

View file

@ -0,0 +1,38 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/radio_list_tile/radio_list_tile.toggleable.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('RadioListTile is toggleable', (WidgetTester tester) async {
await tester.pumpWidget(
const example.RadioListTileApp(),
);
// Initially the third radio button is not selected.
Radio<int> radio = tester.widget(find.byType(Radio<int>).at(2));
expect(radio.value, 2);
expect(radio.groupValue, null);
// Tap the third radio button.
await tester.tap(find.text('Philip Schuyler'));
await tester.pumpAndSettle();
// The third radio button is now selected.
radio = tester.widget(find.byType(Radio<int>).at(2));
expect(radio.value, 2);
expect(radio.groupValue, 2);
// Tap the third radio button again.
await tester.tap(find.text('Philip Schuyler'));
await tester.pumpAndSettle();
// The third radio button is now unselected.
radio = tester.widget(find.byType(Radio<int>).at(2));
expect(radio.value, 2);
expect(radio.groupValue, null);
});
}

View file

@ -86,6 +86,13 @@ import 'theme_data.dart';
/// ** See code in examples/api/lib/material/radio_list_tile/radio_list_tile.0.dart **
/// {@end-tool}
///
/// {@tool dartpad}
/// This sample demonstrates how [RadioListTile] positions the radio widget
/// relative to the text in different configurations.
///
/// ** See code in examples/api/lib/material/radio_list_tile/radio_list_tile.1.dart **
/// {@end-tool}
///
/// ## Semantics in RadioListTile
///
/// Since the entirety of the RadioListTile is interactive, it should represent
@ -110,7 +117,7 @@ import 'theme_data.dart';
/// LinkedLabelRadio, that includes an interactive [RichText] widget that
/// handles tap gestures.
///
/// ** See code in examples/api/lib/material/radio_list_tile/radio_list_tile.1.dart **
/// ** See code in examples/api/lib/material/radio_list_tile/custom_labeled_radio.0.dart **
/// {@end-tool}
///
/// ## RadioListTile isn't exactly what I want
@ -126,7 +133,7 @@ import 'theme_data.dart';
/// Here is an example of a custom LabeledRadio widget, but you can easily
/// make your own configurable widget.
///
/// ** See code in examples/api/lib/material/radio_list_tile/radio_list_tile.2.dart **
/// ** See code in examples/api/lib/material/radio_list_tile/custom_labeled_radio.1.dart **
/// {@end-tool}
///
/// See also: