mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add titleAlignment to CheckboxListTile and RadioListTile (#168666)
Fixes https://github.com/flutter/flutter/issues/168596#issue-3052291792 ## 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. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
e425d4348a
commit
4d037cfdf5
@ -206,6 +206,7 @@ class CheckboxListTile extends StatelessWidget {
|
||||
this.enableFeedback,
|
||||
this.checkboxSemanticLabel,
|
||||
this.checkboxScaleFactor = 1.0,
|
||||
this.titleAlignment,
|
||||
this.internalAddSemanticForOnTap = false,
|
||||
}) : _checkboxType = _CheckboxType.material,
|
||||
assert(tristate || value != null),
|
||||
@ -252,6 +253,7 @@ class CheckboxListTile extends StatelessWidget {
|
||||
this.enableFeedback,
|
||||
this.checkboxSemanticLabel,
|
||||
this.checkboxScaleFactor = 1.0,
|
||||
this.titleAlignment,
|
||||
this.internalAddSemanticForOnTap = false,
|
||||
}) : _checkboxType = _CheckboxType.adaptive,
|
||||
assert(tristate || value != null),
|
||||
@ -468,6 +470,20 @@ class CheckboxListTile extends StatelessWidget {
|
||||
/// inoperative.
|
||||
final bool? enabled;
|
||||
|
||||
/// Defines how [ListTile.leading] and [ListTile.trailing] are
|
||||
/// vertically aligned relative to the [ListTile]'s titles
|
||||
/// ([ListTile.title] and [ListTile.subtitle]).
|
||||
///
|
||||
/// If this property is null then [ListTileThemeData.titleAlignment]
|
||||
/// is used. If that is also null then [ListTileTitleAlignment.threeLine]
|
||||
/// is used.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s
|
||||
/// [ListTileThemeData].
|
||||
final ListTileTitleAlignment? titleAlignment;
|
||||
|
||||
/// Whether to add button:true to the semantics if onTap is provided.
|
||||
/// This is a temporary flag to help changing the behavior of ListTile onTap semantics.
|
||||
///
|
||||
@ -583,6 +599,7 @@ class CheckboxListTile extends StatelessWidget {
|
||||
focusNode: focusNode,
|
||||
onFocusChange: onFocusChange,
|
||||
enableFeedback: enableFeedback,
|
||||
titleAlignment: titleAlignment,
|
||||
internalAddSemanticForOnTap: internalAddSemanticForOnTap,
|
||||
),
|
||||
);
|
||||
|
||||
@ -201,6 +201,7 @@ class RadioListTile<T> extends StatelessWidget {
|
||||
this.onFocusChange,
|
||||
this.enableFeedback,
|
||||
this.radioScaleFactor = 1.0,
|
||||
this.titleAlignment,
|
||||
this.internalAddSemanticForOnTap = false,
|
||||
}) : _radioType = _RadioType.material,
|
||||
useCupertinoCheckmarkStyle = false,
|
||||
@ -243,6 +244,7 @@ class RadioListTile<T> extends StatelessWidget {
|
||||
this.enableFeedback,
|
||||
this.radioScaleFactor = 1.0,
|
||||
this.useCupertinoCheckmarkStyle = false,
|
||||
this.titleAlignment,
|
||||
this.internalAddSemanticForOnTap = false,
|
||||
}) : _radioType = _RadioType.adaptive,
|
||||
assert(isThreeLine != true || subtitle != null);
|
||||
@ -454,6 +456,20 @@ class RadioListTile<T> extends StatelessWidget {
|
||||
|
||||
final _RadioType _radioType;
|
||||
|
||||
/// Defines how [ListTile.leading] and [ListTile.trailing] are
|
||||
/// vertically aligned relative to the [ListTile]'s titles
|
||||
/// ([ListTile.title] and [ListTile.subtitle]).
|
||||
///
|
||||
/// If this property is null then [ListTileThemeData.titleAlignment]
|
||||
/// is used. If that is also null then [ListTileTitleAlignment.threeLine]
|
||||
/// is used.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s
|
||||
/// [ListTileThemeData].
|
||||
final ListTileTitleAlignment? titleAlignment;
|
||||
|
||||
/// Whether to add button:true to the semantics if onTap is provided.
|
||||
/// This is a temporary flag to help changing the behavior of ListTile onTap semantics.
|
||||
///
|
||||
@ -567,6 +583,7 @@ class RadioListTile<T> extends StatelessWidget {
|
||||
focusNode: focusNode,
|
||||
onFocusChange: onFocusChange,
|
||||
enableFeedback: enableFeedback,
|
||||
titleAlignment: titleAlignment,
|
||||
internalAddSemanticForOnTap: internalAddSemanticForOnTap,
|
||||
),
|
||||
);
|
||||
|
||||
@ -1569,6 +1569,278 @@ void main() {
|
||||
);
|
||||
expectThreeLine();
|
||||
});
|
||||
|
||||
testWidgets('titleAlignment position with title widget', (WidgetTester tester) async {
|
||||
const Key secondaryKey = Key('secondary');
|
||||
const double titleHeight = 50.0;
|
||||
const double secondaryHeight = 24.0;
|
||||
// The default vertical padding for material 3 is 8.0.
|
||||
const double minVerticalPadding = 8.0;
|
||||
|
||||
Widget buildFrame({ListTileTitleAlignment? titleAlignment}) {
|
||||
return MaterialApp(
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: CheckboxListTile(
|
||||
titleAlignment: titleAlignment,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: true,
|
||||
onChanged: (bool? newValue) {},
|
||||
title: const SizedBox(width: 20.0, height: titleHeight),
|
||||
secondary: const SizedBox(key: secondaryKey, width: 24.0, height: secondaryHeight),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// If [ThemeData.useMaterial3] is true, the default title alignment is
|
||||
// [ListTileTitleAlignment.threeLine], which positions the leading and
|
||||
// trailing widgets center vertically in the tile if the [ListTile.isThreeLine]
|
||||
// property is false.
|
||||
await tester.pumpWidget(buildFrame());
|
||||
final double checkboxHeight = tester.getSize(find.byType(Checkbox)).height;
|
||||
final double tileHeight = tester.getSize(find.byType(ListTile)).height;
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == null;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
Offset tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
Offset checkboxOffset = tester.getTopLeft(find.byType(Checkbox));
|
||||
Offset secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are centered vertically in the tile.
|
||||
final double centerPositionCheckbox = (tileHeight / 2) - (checkboxHeight / 2);
|
||||
final double centerPositionSecondary = (tileHeight / 2) - (secondaryHeight / 2);
|
||||
expect(checkboxOffset.dy - tileOffset.dy, centerPositionCheckbox);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.threeLine] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.threeLine));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.threeLine;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
checkboxOffset = tester.getTopLeft(find.byType(Checkbox));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are centered vertically in the tile,
|
||||
// If the [ListTile.isThreeLine] property is false.
|
||||
expect(checkboxOffset.dy - tileOffset.dy, centerPositionCheckbox);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.titleHeight] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.titleHeight));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.titleHeight;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
checkboxOffset = tester.getTopLeft(find.byType(Checkbox));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
expect(checkboxOffset.dy - tileOffset.dy, (tileHeight - checkboxHeight) / 2);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.top] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.top));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.top;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
checkboxOffset = tester.getTopLeft(find.byType(Checkbox));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are placed minVerticalPadding below
|
||||
// the top of the title widget. The default for material 3 is 8.0.
|
||||
const double topPosition = minVerticalPadding;
|
||||
expect(checkboxOffset.dy - tileOffset.dy, topPosition);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, topPosition);
|
||||
|
||||
// Test [ListTileTitleAlignment.center] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.center));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.center;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
checkboxOffset = tester.getTopLeft(find.byType(Checkbox));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are centered vertically in the tile.
|
||||
expect(checkboxOffset.dy - tileOffset.dy, centerPositionCheckbox);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.bottom] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.bottom));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.bottom;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
checkboxOffset = tester.getTopLeft(find.byType(Checkbox));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are placed minVerticalPadding above
|
||||
// the bottom of the subtitle widget.
|
||||
final double bottomPositionCheckbox = tileHeight - minVerticalPadding - checkboxHeight;
|
||||
final double bottomPositionSecondary = tileHeight - minVerticalPadding - secondaryHeight;
|
||||
expect(checkboxOffset.dy - tileOffset.dy, bottomPositionCheckbox);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, bottomPositionSecondary);
|
||||
});
|
||||
|
||||
testWidgets('titleAlignment position with title and subtitle widgets', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
const Key secondaryKey = Key('secondary');
|
||||
const double titleHeight = 50.0;
|
||||
const double subtitleHeight = 50.0;
|
||||
const double secondaryHeight = 24.0;
|
||||
const double verticalPadding = 8.0;
|
||||
|
||||
Widget buildFrame({ListTileTitleAlignment? titleAlignment}) {
|
||||
return MaterialApp(
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: CheckboxListTile(
|
||||
titleAlignment: titleAlignment,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
title: const SizedBox(width: 20.0, height: titleHeight),
|
||||
subtitle: const SizedBox(width: 20.0, height: subtitleHeight),
|
||||
secondary: const SizedBox(key: secondaryKey, width: 24.0, height: secondaryHeight),
|
||||
value: true,
|
||||
onChanged: (bool? newValue) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// If [ThemeData.useMaterial3] is true, the default title alignment is
|
||||
// [ListTileTitleAlignment.threeLine], which positions the leading and
|
||||
// trailing widgets center vertically in the tile if the [ListTile.isThreeLine]
|
||||
// property is false.
|
||||
await tester.pumpWidget(buildFrame());
|
||||
final double tileHeight = tester.getSize(find.byType(ListTile)).height;
|
||||
final double checkboxHeight = tester.getSize(find.byType(Checkbox)).height;
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == null;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
Offset tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
Offset checkboxOffset = tester.getTopLeft(find.byType(Checkbox));
|
||||
Offset secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are centered vertically in the tile.
|
||||
final double centerPositionOffsetCheckbox = (tileHeight / 2) - (checkboxHeight / 2);
|
||||
final double centerPositionOffsetSecondary = (tileHeight / 2) - (secondaryHeight / 2);
|
||||
expect(checkboxOffset.dy - tileOffset.dy, centerPositionOffsetCheckbox);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionOffsetSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.threeLine] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.threeLine));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.threeLine;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
checkboxOffset = tester.getTopLeft(find.byType(Checkbox));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are centered vertically in the tile,
|
||||
// If the [ListTile.isThreeLine] property is false.
|
||||
expect(checkboxOffset.dy - tileOffset.dy, centerPositionOffsetCheckbox);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionOffsetSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.titleHeight] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.titleHeight));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.titleHeight;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
checkboxOffset = tester.getTopLeft(find.byType(Checkbox));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are positioned 16.0 pixels below the
|
||||
// top of the title widget.
|
||||
const double titlePosition = 16.0;
|
||||
expect(checkboxOffset.dy - tileOffset.dy, titlePosition);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, titlePosition);
|
||||
|
||||
// Test [ListTileTitleAlignment.top] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.top));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.top;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
checkboxOffset = tester.getTopLeft(find.byType(Checkbox));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are placed minVerticalPadding below
|
||||
// the top of the title widget.
|
||||
const double topPosition = verticalPadding;
|
||||
expect(checkboxOffset.dy - tileOffset.dy, topPosition);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, topPosition);
|
||||
|
||||
// Test [ListTileTitleAlignment.center] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.center));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.center;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
checkboxOffset = tester.getTopLeft(find.byType(Checkbox));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are centered vertically in the tile.
|
||||
expect(checkboxOffset.dy - tileOffset.dy, centerPositionOffsetCheckbox);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionOffsetSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.bottom] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.bottom));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.bottom;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
checkboxOffset = tester.getTopLeft(find.byType(Checkbox));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are placed minVerticalPadding above
|
||||
// the bottom of the subtitle widget.
|
||||
final double bottomPositionCheckbox = tileHeight - verticalPadding - checkboxHeight;
|
||||
final double bottomPositionSecondary = tileHeight - verticalPadding - secondaryHeight;
|
||||
expect(checkboxOffset.dy - tileOffset.dy, bottomPositionCheckbox);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, bottomPositionSecondary);
|
||||
});
|
||||
}
|
||||
|
||||
class _SelectedGrabMouseCursor extends MaterialStateMouseCursor {
|
||||
|
||||
@ -1880,4 +1880,278 @@ void main() {
|
||||
);
|
||||
expectThreeLine();
|
||||
});
|
||||
|
||||
testWidgets('titleAlignment position with title widget', (WidgetTester tester) async {
|
||||
const Key secondaryKey = Key('secondary');
|
||||
const double titleHeight = 50.0;
|
||||
const double secondaryHeight = 24.0;
|
||||
// The default vertical padding for material 3 is 8.0.
|
||||
const double minVerticalPadding = 8.0;
|
||||
|
||||
Widget buildFrame({ListTileTitleAlignment? titleAlignment}) {
|
||||
return MaterialApp(
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: RadioListTile<bool>(
|
||||
titleAlignment: titleAlignment,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: true,
|
||||
groupValue: true,
|
||||
onChanged: (bool? newValue) {},
|
||||
title: const SizedBox(width: 20.0, height: titleHeight),
|
||||
secondary: const SizedBox(key: secondaryKey, width: 24.0, height: secondaryHeight),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// If [ThemeData.useMaterial3] is true, the default title alignment is
|
||||
// [ListTileTitleAlignment.threeLine], which positions the leading and
|
||||
// trailing widgets center vertically in the tile if the [ListTile.isThreeLine]
|
||||
// property is false.
|
||||
await tester.pumpWidget(buildFrame());
|
||||
final double radioHeight = tester.getSize(find.byType(Radio<bool>)).height;
|
||||
final double tileHeight = tester.getSize(find.byType(ListTile)).height;
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == null;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
Offset tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
Offset radioOffset = tester.getTopLeft(find.byType(Radio<bool>));
|
||||
Offset secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are centered vertically in the tile.
|
||||
final double centerPositionRadio = (tileHeight / 2) - (radioHeight / 2);
|
||||
final double centerPositionSecondary = (tileHeight / 2) - (secondaryHeight / 2);
|
||||
expect(radioOffset.dy - tileOffset.dy, centerPositionRadio);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.threeLine] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.threeLine));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.threeLine;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
radioOffset = tester.getTopLeft(find.byType(Radio<bool>));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are centered vertically in the tile,
|
||||
// If the [ListTile.isThreeLine] property is false.
|
||||
expect(radioOffset.dy - tileOffset.dy, centerPositionRadio);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.titleHeight] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.titleHeight));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.titleHeight;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
radioOffset = tester.getTopLeft(find.byType(Radio<bool>));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
expect(radioOffset.dy - tileOffset.dy, (tileHeight - radioHeight) / 2);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.top] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.top));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.top;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
radioOffset = tester.getTopLeft(find.byType(Radio<bool>));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are placed minVerticalPadding below
|
||||
// the top of the title widget. The default for material 3 is 8.0.
|
||||
const double topPosition = minVerticalPadding;
|
||||
expect(radioOffset.dy - tileOffset.dy, topPosition);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, topPosition);
|
||||
|
||||
// Test [ListTileTitleAlignment.center] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.center));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.center;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
radioOffset = tester.getTopLeft(find.byType(Radio<bool>));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are centered vertically in the tile.
|
||||
expect(radioOffset.dy - tileOffset.dy, centerPositionRadio);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.bottom] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.bottom));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.bottom;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
radioOffset = tester.getTopLeft(find.byType(Radio<bool>));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are placed minVerticalPadding above
|
||||
// the bottom of the subtitle widget.
|
||||
final double bottomPositionRadio = tileHeight - minVerticalPadding - radioHeight;
|
||||
final double bottomPositionSecondary = tileHeight - minVerticalPadding - secondaryHeight;
|
||||
expect(radioOffset.dy - tileOffset.dy, bottomPositionRadio);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, bottomPositionSecondary);
|
||||
});
|
||||
|
||||
testWidgets('titleAlignment position with title and subtitle widgets', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
const Key secondaryKey = Key('secondary');
|
||||
const double titleHeight = 50.0;
|
||||
const double subtitleHeight = 50.0;
|
||||
const double secondaryHeight = 24.0;
|
||||
const double verticalPadding = 8.0;
|
||||
|
||||
Widget buildFrame({ListTileTitleAlignment? titleAlignment}) {
|
||||
return MaterialApp(
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: RadioListTile<bool>(
|
||||
titleAlignment: titleAlignment,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
title: const SizedBox(width: 20.0, height: titleHeight),
|
||||
subtitle: const SizedBox(width: 20.0, height: subtitleHeight),
|
||||
secondary: const SizedBox(key: secondaryKey, width: 24.0, height: secondaryHeight),
|
||||
value: true,
|
||||
groupValue: true,
|
||||
onChanged: (bool? newValue) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// If [ThemeData.useMaterial3] is true, the default title alignment is
|
||||
// [ListTileTitleAlignment.threeLine], which positions the leading and
|
||||
// trailing widgets center vertically in the tile if the [ListTile.isThreeLine]
|
||||
// property is false.
|
||||
await tester.pumpWidget(buildFrame());
|
||||
final double tileHeight = tester.getSize(find.byType(ListTile)).height;
|
||||
final double radioHeight = tester.getSize(find.byType(Radio<bool>)).height;
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == null;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
Offset tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
Offset radioOffset = tester.getTopLeft(find.byType(Radio<bool>));
|
||||
Offset secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are centered vertically in the tile.
|
||||
final double centerPositionOffsetRadio = (tileHeight / 2) - (radioHeight / 2);
|
||||
final double centerPositionOffsetSecondary = (tileHeight / 2) - (secondaryHeight / 2);
|
||||
expect(radioOffset.dy - tileOffset.dy, centerPositionOffsetRadio);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionOffsetSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.threeLine] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.threeLine));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.threeLine;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
radioOffset = tester.getTopLeft(find.byType(Radio<bool>));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are centered vertically in the tile,
|
||||
// If the [ListTile.isThreeLine] property is false.
|
||||
expect(radioOffset.dy - tileOffset.dy, centerPositionOffsetRadio);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionOffsetSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.titleHeight] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.titleHeight));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.titleHeight;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
radioOffset = tester.getTopLeft(find.byType(Radio<bool>));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are positioned 16.0 pixels below the
|
||||
// top of the title widget.
|
||||
const double titlePosition = 16.0;
|
||||
expect(radioOffset.dy - tileOffset.dy, titlePosition);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, titlePosition);
|
||||
|
||||
// Test [ListTileTitleAlignment.top] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.top));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.top;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
radioOffset = tester.getTopLeft(find.byType(Radio<bool>));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are placed minVerticalPadding below
|
||||
// the top of the title widget.
|
||||
const double topPosition = verticalPadding;
|
||||
expect(radioOffset.dy - tileOffset.dy, topPosition);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, topPosition);
|
||||
|
||||
// Test [ListTileTitleAlignment.center] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.center));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.center;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
radioOffset = tester.getTopLeft(find.byType(Radio<bool>));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are centered vertically in the tile.
|
||||
expect(radioOffset.dy - tileOffset.dy, centerPositionOffsetRadio);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, centerPositionOffsetSecondary);
|
||||
|
||||
// Test [ListTileTitleAlignment.bottom] alignment.
|
||||
await tester.pumpWidget(buildFrame(titleAlignment: ListTileTitleAlignment.bottom));
|
||||
expect(
|
||||
find.byWidgetPredicate((Widget widget) {
|
||||
return widget is ListTile && widget.titleAlignment == ListTileTitleAlignment.bottom;
|
||||
}),
|
||||
findsOne,
|
||||
);
|
||||
tileOffset = tester.getTopLeft(find.byType(ListTile));
|
||||
radioOffset = tester.getTopLeft(find.byType(Radio<bool>));
|
||||
secondaryOffset = tester.getTopRight(find.byKey(secondaryKey));
|
||||
|
||||
// Leading and trailing widgets are placed minVerticalPadding above
|
||||
// the bottom of the subtitle widget.
|
||||
final double bottomPositionRadio = tileHeight - verticalPadding - radioHeight;
|
||||
final double bottomPositionSecondary = tileHeight - verticalPadding - secondaryHeight;
|
||||
expect(radioOffset.dy - tileOffset.dy, bottomPositionRadio);
|
||||
expect(secondaryOffset.dy - tileOffset.dy, bottomPositionSecondary);
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user