feat(RadioListTile):ensure that 'isThreeLine' can be configured through the (#166964)

This PR is a continuation of
[165481](https://github.com/flutter/flutter/pull/165481)

Related items also include:
[SwitchListTile](https://github.com/flutter/flutter/pull/166820),
[CheckboxListTile](https://github.com/flutter/flutter/pull/166826)

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

---------

Co-authored-by: Tong Mu <dkwingsmt@users.noreply.github.com>
This commit is contained in:
jcheng 2025-05-06 09:47:26 +08:00 committed by GitHub
parent 0d2ee2c49c
commit dfd0a3cb5f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 245 additions and 8 deletions

View File

@ -140,7 +140,8 @@ class ListTileThemeData with Diagnosticable {
/// or [ExpansionTile.controlAffinity] or [SwitchListTile.controlAffinity] or [RadioListTile.controlAffinity].
final ListTileControlAffinity? controlAffinity;
/// If specified, overrides the default value of [ListTile.isThreeLine] or [CheckboxListTile.isThreeLine].
/// If specified, overrides the default value of [ListTile.isThreeLine] or [CheckboxListTile.isThreeLine]
/// or [RadioListTile.isThreeLine].
final bool? isThreeLine;
/// Creates a copy of this object with the given fields replaced with the

View File

@ -186,7 +186,7 @@ class RadioListTile<T> extends StatelessWidget {
this.materialTapTargetSize,
this.title,
this.subtitle,
this.isThreeLine = false,
this.isThreeLine,
this.dense,
this.secondary,
this.selected = false,
@ -204,7 +204,7 @@ class RadioListTile<T> extends StatelessWidget {
this.internalAddSemanticForOnTap = false,
}) : _radioType = _RadioType.material,
useCupertinoCheckmarkStyle = false,
assert(!isThreeLine || subtitle != null);
assert(isThreeLine != true || subtitle != null);
/// Creates a combination of a list tile and a platform adaptive radio.
///
@ -227,7 +227,7 @@ class RadioListTile<T> extends StatelessWidget {
this.materialTapTargetSize,
this.title,
this.subtitle,
this.isThreeLine = false,
this.isThreeLine,
this.dense,
this.secondary,
this.selected = false,
@ -245,7 +245,7 @@ class RadioListTile<T> extends StatelessWidget {
this.useCupertinoCheckmarkStyle = false,
this.internalAddSemanticForOnTap = false,
}) : _radioType = _RadioType.adaptive,
assert(!isThreeLine || subtitle != null);
assert(isThreeLine != true || subtitle != null);
/// The value represented by this radio button.
final T value;
@ -385,9 +385,10 @@ class RadioListTile<T> extends StatelessWidget {
/// Whether this list tile is intended to display three lines of text.
///
/// If false, the list tile is treated as having one line if the subtitle is
/// null and treated as having two lines if the subtitle is non-null.
final bool isThreeLine;
/// If null, the value from [ListTileThemeData.isThreeLine] is used.
/// If that is also null, the value from [ThemeData.listTileTheme] is used.
/// If still null, the default value is `false`.
final bool? isThreeLine;
/// Whether this list tile is part of a vertically dense list.
///

View File

@ -1645,4 +1645,239 @@ void main() {
expect(widget.transform.getMaxScaleOnAxis(), scale);
});
testWidgets('RadioListTile isThreeLine', (WidgetTester tester) async {
const double height = 300;
const double size = 40.0;
Widget buildFrame({bool? themeDataIsThreeLine, bool? themeIsThreeLine, bool? isThreeLine}) {
return MaterialApp(
key: UniqueKey(),
theme:
themeDataIsThreeLine != null
? ThemeData(listTileTheme: ListTileThemeData(isThreeLine: themeDataIsThreeLine))
: null,
home: Material(
child: ListTileTheme(
data:
themeIsThreeLine != null ? ListTileThemeData(isThreeLine: themeIsThreeLine) : null,
child: ListView(
children: <Widget>[
RadioListTile<int>(
isThreeLine: isThreeLine,
title: const Text('A'),
subtitle: const Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
value: 0,
onChanged: null,
groupValue: 1,
),
RadioListTile<int>(
isThreeLine: isThreeLine,
title: const Text('A'),
subtitle: const Text('A'),
value: 0,
onChanged: null,
groupValue: 2,
),
],
),
),
),
);
}
void expectTwoLine() {
expect(
tester.getRect(find.byType(RadioListTile<int>).at(0)),
const Rect.fromLTWH(0.0, 0.0, 800.0, height),
);
expect(
tester.getRect(find.byType(Radio<int>).at(0)),
const Rect.fromLTWH(16.0, 130.0, size, size),
);
expect(
tester.getRect(find.byType(RadioListTile<int>).at(1)),
const Rect.fromLTWH(0.0, height, 800.0, 72.0),
);
expect(
tester.getRect(find.byType(Radio<int>).at(1)),
const Rect.fromLTWH(16.0, height + 16, size, size),
);
}
void expectThreeLine() {
expect(
tester.getRect(find.byType(RadioListTile<int>).at(0)),
const Rect.fromLTWH(0.0, 0.0, 800.0, height),
);
expect(
tester.getRect(find.byType(Radio<int>).at(0)),
const Rect.fromLTWH(16.0, 8.0, size, size),
);
expect(
tester.getRect(find.byType(RadioListTile<int>).at(1)),
const Rect.fromLTWH(0.0, height, 800.0, 88.0),
);
expect(
tester.getRect(find.byType(Radio<int>).at(1)),
const Rect.fromLTWH(16.0, height + 8.0, size, size),
);
}
await tester.pumpWidget(buildFrame());
expectTwoLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true));
expectThreeLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: false, themeIsThreeLine: true));
expectThreeLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true, themeIsThreeLine: false));
expectTwoLine();
await tester.pumpWidget(buildFrame(isThreeLine: true));
expectThreeLine();
await tester.pumpWidget(buildFrame(themeIsThreeLine: true, isThreeLine: false));
expectTwoLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true, isThreeLine: false));
expectTwoLine();
await tester.pumpWidget(
buildFrame(themeDataIsThreeLine: true, themeIsThreeLine: true, isThreeLine: false),
);
expectTwoLine();
await tester.pumpWidget(buildFrame(themeIsThreeLine: false, isThreeLine: true));
expectThreeLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: false, isThreeLine: true));
expectThreeLine();
await tester.pumpWidget(
buildFrame(themeDataIsThreeLine: false, themeIsThreeLine: false, isThreeLine: true),
);
expectThreeLine();
});
testWidgets('RadioListTile.adaptive isThreeLine', (WidgetTester tester) async {
const double height = 300;
const double size = 18.0;
Widget buildFrame({bool? themeDataIsThreeLine, bool? themeIsThreeLine, bool? isThreeLine}) {
return MaterialApp(
key: UniqueKey(),
theme: ThemeData(
platform: TargetPlatform.iOS,
listTileTheme:
themeDataIsThreeLine != null
? ListTileThemeData(isThreeLine: themeDataIsThreeLine)
: null,
),
home: Material(
child: ListTileTheme(
data:
themeIsThreeLine != null ? ListTileThemeData(isThreeLine: themeIsThreeLine) : null,
child: ListView(
children: <Widget>[
RadioListTile<int>.adaptive(
isThreeLine: isThreeLine,
title: const Text('A'),
subtitle: const Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
value: 0,
onChanged: null,
groupValue: 1,
),
RadioListTile<int>.adaptive(
isThreeLine: isThreeLine,
title: const Text('A'),
subtitle: const Text('A'),
value: 0,
onChanged: null,
groupValue: 2,
),
],
),
),
),
);
}
void expectTwoLine() {
expect(
tester.getRect(find.byType(RadioListTile<int>).at(0)),
const Rect.fromLTWH(0.0, 0.0, 800.0, height),
);
expect(
tester.getRect(find.byType(Radio<int>).at(0)),
const Rect.fromLTWH(16.0, 141.0, size, size),
);
expect(
tester.getRect(find.byType(RadioListTile<int>).at(1)),
const Rect.fromLTWH(0.0, height, 800.0, 72.0),
);
expect(
tester.getRect(find.byType(Radio<int>).at(1)),
const Rect.fromLTWH(16.0, height + 27.0, size, size),
);
}
void expectThreeLine() {
expect(
tester.getRect(find.byType(RadioListTile<int>).at(0)),
const Rect.fromLTWH(0.0, 0.0, 800.0, height),
);
expect(
tester.getRect(find.byType(Radio<int>).at(0)),
const Rect.fromLTWH(16.0, 8.0, size, size),
);
expect(
tester.getRect(find.byType(RadioListTile<int>).at(1)),
const Rect.fromLTWH(0.0, height, 800.0, 88.0),
);
expect(
tester.getRect(find.byType(Radio<int>).at(1)),
const Rect.fromLTWH(16.0, height + 8.0, size, size),
);
}
await tester.pumpWidget(buildFrame());
expectTwoLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true));
expectThreeLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: false, themeIsThreeLine: true));
expectThreeLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true, themeIsThreeLine: false));
expectTwoLine();
await tester.pumpWidget(buildFrame(isThreeLine: true));
expectThreeLine();
await tester.pumpWidget(buildFrame(themeIsThreeLine: true, isThreeLine: false));
expectTwoLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true, isThreeLine: false));
expectTwoLine();
await tester.pumpWidget(
buildFrame(themeDataIsThreeLine: true, themeIsThreeLine: true, isThreeLine: false),
);
expectTwoLine();
await tester.pumpWidget(buildFrame(themeIsThreeLine: false, isThreeLine: true));
expectThreeLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: false, isThreeLine: true));
expectThreeLine();
await tester.pumpWidget(
buildFrame(themeDataIsThreeLine: false, themeIsThreeLine: false, isThreeLine: true),
);
expectThreeLine();
});
}