Unverified Commit 42b20cf9 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Added ListTile.titleAlignment, ListTileThemeData.titleAlignment (#119872)

* added ListTile.textAlignment

* changed titlesHeight to titleHeight

* fixed a typo

* Add tests and example

* Update tests

* update example test

---------
Co-authored-by: 's avatartahatesser <tessertaha@gmail.com>
parent 0521c60c
// 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 [ListTile].
import 'package:flutter/material.dart';
void main() => runApp(const ListTileApp());
class ListTileApp extends StatelessWidget {
const ListTileApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const ListTileExample(),
);
}
}
class ListTileExample extends StatefulWidget {
const ListTileExample({super.key});
@override
State<ListTileExample> createState() => _ListTileExampleState();
}
class _ListTileExampleState extends State<ListTileExample> {
ListTileTitleAlignment? titleAlignment;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('ListTile.titleAlignment Sample')),
body: Column(
children: <Widget>[
const Divider(),
ListTile(
titleAlignment: titleAlignment,
leading: Checkbox(
value: true,
onChanged:(bool? value) { },
),
title: const Text('Headline Text'),
subtitle: const Text('Tapping on the trailing widget will show a menu that allows you to change the title alignment. The title alignment is set to threeLine by default if `ThemeData.useMaterial3` is true. Otherwise, defaults to titleHeight.'),
trailing: PopupMenuButton<ListTileTitleAlignment>(
onSelected: (ListTileTitleAlignment? value) {
setState(() {
titleAlignment = value;
});
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<ListTileTitleAlignment>>[
const PopupMenuItem<ListTileTitleAlignment>(
value: ListTileTitleAlignment.threeLine,
child: Text('threeLine'),
),
const PopupMenuItem<ListTileTitleAlignment>(
value: ListTileTitleAlignment.titleHeight,
child: Text('titleHeight'),
),
const PopupMenuItem<ListTileTitleAlignment>(
value: ListTileTitleAlignment.top,
child: Text('top'),
),
const PopupMenuItem<ListTileTitleAlignment>(
value: ListTileTitleAlignment.center,
child: Text('center'),
),
const PopupMenuItem<ListTileTitleAlignment>(
value: ListTileTitleAlignment.bottom,
child: Text('bottom'),
),
],
),
),
const Divider(),
],
),
);
}
}
// 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/list_tile/list_tile.4.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Can choose different title aligments from popup menu', (WidgetTester tester) async {
await tester.pumpWidget(
const example.ListTileApp(),
);
Offset titleOffset = tester.getTopLeft(find.text('Headline Text'));
Offset leadingOffset = tester.getTopLeft(find.byType(Checkbox));
Offset trailingOffset = tester.getTopRight(find.byIcon(Icons.adaptive.more));
// The default title alignment is threeLine.
expect(leadingOffset.dy - titleOffset.dy, 48.0);
expect(trailingOffset.dy - titleOffset.dy, 60.0);
await tester.tap(find.byIcon(Icons.adaptive.more));
await tester.pumpAndSettle();
// Change the title alignment to titleHeight.
await tester.tap(find.text('titleHeight'));
await tester.pumpAndSettle();
titleOffset = tester.getTopLeft(find.text('Headline Text'));
leadingOffset = tester.getTopLeft(find.byType(Checkbox));
trailingOffset = tester.getTopRight(find.byIcon(Icons.adaptive.more));
expect(leadingOffset.dy - titleOffset.dy, 8.0);
expect(trailingOffset.dy - titleOffset.dy, 20.0);
await tester.tap(find.byIcon(Icons.adaptive.more));
await tester.pumpAndSettle();
// Change the title alignment to bottom.
await tester.tap(find.text('bottom'));
await tester.pumpAndSettle();
titleOffset = tester.getTopLeft(find.text('Headline Text'));
leadingOffset = tester.getTopLeft(find.byType(Checkbox));
trailingOffset = tester.getTopRight(find.byIcon(Icons.adaptive.more));
expect(leadingOffset.dy - titleOffset.dy, 96.0);
expect(trailingOffset.dy - titleOffset.dy, 108.0);
});
}
...@@ -63,6 +63,7 @@ class ListTileThemeData with Diagnosticable { ...@@ -63,6 +63,7 @@ class ListTileThemeData with Diagnosticable {
this.enableFeedback, this.enableFeedback,
this.mouseCursor, this.mouseCursor,
this.visualDensity, this.visualDensity,
this.titleAlignment,
}); });
/// Overrides the default value of [ListTile.dense]. /// Overrides the default value of [ListTile.dense].
...@@ -119,6 +120,9 @@ class ListTileThemeData with Diagnosticable { ...@@ -119,6 +120,9 @@ class ListTileThemeData with Diagnosticable {
/// If specified, overrides the default value of [ListTile.visualDensity]. /// If specified, overrides the default value of [ListTile.visualDensity].
final VisualDensity? visualDensity; final VisualDensity? visualDensity;
/// If specified, overrides the default value of [ListTile.titleAlignment].
final ListTileTitleAlignment? titleAlignment;
/// Creates a copy of this object with the given fields replaced with the /// Creates a copy of this object with the given fields replaced with the
/// new values. /// new values.
ListTileThemeData copyWith({ ListTileThemeData copyWith({
...@@ -141,6 +145,7 @@ class ListTileThemeData with Diagnosticable { ...@@ -141,6 +145,7 @@ class ListTileThemeData with Diagnosticable {
MaterialStateProperty<MouseCursor?>? mouseCursor, MaterialStateProperty<MouseCursor?>? mouseCursor,
bool? isThreeLine, bool? isThreeLine,
VisualDensity? visualDensity, VisualDensity? visualDensity,
ListTileTitleAlignment? titleAlignment,
}) { }) {
return ListTileThemeData( return ListTileThemeData(
dense: dense ?? this.dense, dense: dense ?? this.dense,
...@@ -161,6 +166,7 @@ class ListTileThemeData with Diagnosticable { ...@@ -161,6 +166,7 @@ class ListTileThemeData with Diagnosticable {
enableFeedback: enableFeedback ?? this.enableFeedback, enableFeedback: enableFeedback ?? this.enableFeedback,
mouseCursor: mouseCursor ?? this.mouseCursor, mouseCursor: mouseCursor ?? this.mouseCursor,
visualDensity: visualDensity ?? this.visualDensity, visualDensity: visualDensity ?? this.visualDensity,
titleAlignment: titleAlignment ?? this.titleAlignment,
); );
} }
...@@ -188,6 +194,7 @@ class ListTileThemeData with Diagnosticable { ...@@ -188,6 +194,7 @@ class ListTileThemeData with Diagnosticable {
enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback, enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor, mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity, visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
titleAlignment: t < 0.5 ? a?.titleAlignment : b?.titleAlignment,
); );
} }
...@@ -211,6 +218,7 @@ class ListTileThemeData with Diagnosticable { ...@@ -211,6 +218,7 @@ class ListTileThemeData with Diagnosticable {
enableFeedback, enableFeedback,
mouseCursor, mouseCursor,
visualDensity, visualDensity,
titleAlignment,
); );
@override @override
...@@ -239,7 +247,8 @@ class ListTileThemeData with Diagnosticable { ...@@ -239,7 +247,8 @@ class ListTileThemeData with Diagnosticable {
&& other.minLeadingWidth == minLeadingWidth && other.minLeadingWidth == minLeadingWidth
&& other.enableFeedback == enableFeedback && other.enableFeedback == enableFeedback
&& other.mouseCursor == mouseCursor && other.mouseCursor == mouseCursor
&& other.visualDensity == visualDensity; && other.visualDensity == visualDensity
&& other.titleAlignment == titleAlignment;
} }
@override @override
...@@ -263,6 +272,7 @@ class ListTileThemeData with Diagnosticable { ...@@ -263,6 +272,7 @@ class ListTileThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null)); properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null)); properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null)); properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
properties.add(DiagnosticsProperty<ListTileTitleAlignment>('titleAlignment', titleAlignment, defaultValue: null));
} }
} }
...@@ -477,6 +487,7 @@ class ListTileTheme extends InheritedTheme { ...@@ -477,6 +487,7 @@ class ListTileTheme extends InheritedTheme {
double? horizontalTitleGap, double? horizontalTitleGap,
double? minVerticalPadding, double? minVerticalPadding,
double? minLeadingWidth, double? minLeadingWidth,
ListTileTitleAlignment? titleAlignment,
required Widget child, required Widget child,
}) { }) {
return Builder( return Builder(
...@@ -498,6 +509,7 @@ class ListTileTheme extends InheritedTheme { ...@@ -498,6 +509,7 @@ class ListTileTheme extends InheritedTheme {
horizontalTitleGap: horizontalTitleGap ?? parent.horizontalTitleGap, horizontalTitleGap: horizontalTitleGap ?? parent.horizontalTitleGap,
minVerticalPadding: minVerticalPadding ?? parent.minVerticalPadding, minVerticalPadding: minVerticalPadding ?? parent.minVerticalPadding,
minLeadingWidth: minLeadingWidth ?? parent.minLeadingWidth, minLeadingWidth: minLeadingWidth ?? parent.minLeadingWidth,
titleAlignment: titleAlignment ?? parent.titleAlignment,
), ),
child: child, child: child,
); );
......
...@@ -71,6 +71,7 @@ void main() { ...@@ -71,6 +71,7 @@ void main() {
expect(themeData.enableFeedback, null); expect(themeData.enableFeedback, null);
expect(themeData.mouseCursor, null); expect(themeData.mouseCursor, null);
expect(themeData.visualDensity, null); expect(themeData.visualDensity, null);
expect(themeData.titleAlignment, null);
}); });
testWidgets('Default ListTileThemeData debugFillProperties', (WidgetTester tester) async { testWidgets('Default ListTileThemeData debugFillProperties', (WidgetTester tester) async {
...@@ -106,6 +107,7 @@ void main() { ...@@ -106,6 +107,7 @@ void main() {
enableFeedback: true, enableFeedback: true,
mouseCursor: MaterialStateMouseCursor.clickable, mouseCursor: MaterialStateMouseCursor.clickable,
visualDensity: VisualDensity.comfortable, visualDensity: VisualDensity.comfortable,
titleAlignment: ListTileTitleAlignment.top,
).debugFillProperties(builder); ).debugFillProperties(builder);
final List<String> description = builder.properties final List<String> description = builder.properties
...@@ -134,6 +136,7 @@ void main() { ...@@ -134,6 +136,7 @@ void main() {
'enableFeedback: true', 'enableFeedback: true',
'mouseCursor: MaterialStateMouseCursor(clickable)', 'mouseCursor: MaterialStateMouseCursor(clickable)',
'visualDensity: VisualDensity#00000(h: -1.0, v: -1.0)(horizontal: -1.0, vertical: -1.0)', 'visualDensity: VisualDensity#00000(h: -1.0, v: -1.0)(horizontal: -1.0, vertical: -1.0)',
'titleAlignment: ListTileTitleAlignment.top',
]), ]),
); );
}); });
...@@ -215,6 +218,7 @@ void main() { ...@@ -215,6 +218,7 @@ void main() {
selectedColor: selectedColor, selectedColor: selectedColor,
iconColor: iconColor, iconColor: iconColor,
textColor: textColor, textColor: textColor,
minVerticalPadding: 25.0,
mouseCursor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { mouseCursor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) { if (states.contains(MaterialState.disabled)) {
return SystemMouseCursors.forbidden; return SystemMouseCursors.forbidden;
...@@ -223,6 +227,7 @@ void main() { ...@@ -223,6 +227,7 @@ void main() {
return SystemMouseCursors.click; return SystemMouseCursors.click;
}), }),
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
titleAlignment: ListTileTitleAlignment.bottom,
), ),
child: Builder( child: Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
...@@ -311,7 +316,14 @@ void main() { ...@@ -311,7 +316,14 @@ void main() {
// VisualDensity is respected // VisualDensity is respected
final RenderBox box = tester.renderObject(find.byKey(listTileKey)); final RenderBox box = tester.renderObject(find.byKey(listTileKey));
expect(box.size, equals(const Size(800, 64.0))); expect(box.size, equals(const Size(800, 80.0)));
// titleAlignment is respected.
final Offset titleOffset = tester.getTopLeft(find.text('title'));
final Offset leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
final Offset trailingOffset = tester.getTopRight(find.byKey(trailingKey));
expect(leadingOffset.dy - titleOffset.dy, 6);
expect(trailingOffset.dy - titleOffset.dy, 6);
}); });
testWidgets('ListTileTheme colors are applied to leading and trailing text widgets', (WidgetTester tester) async { testWidgets('ListTileTheme colors are applied to leading and trailing text widgets', (WidgetTester tester) async {
...@@ -721,6 +733,7 @@ void main() { ...@@ -721,6 +733,7 @@ void main() {
minVerticalPadding: 300, minVerticalPadding: 300,
minLeadingWidth: 400, minLeadingWidth: 400,
enableFeedback: true, enableFeedback: true,
titleAlignment: ListTileTitleAlignment.bottom,
); );
final ListTileThemeData copy = original.copyWith( final ListTileThemeData copy = original.copyWith(
...@@ -740,6 +753,7 @@ void main() { ...@@ -740,6 +753,7 @@ void main() {
minVerticalPadding: 700, minVerticalPadding: 700,
minLeadingWidth: 800, minLeadingWidth: 800,
enableFeedback: false, enableFeedback: false,
titleAlignment: ListTileTitleAlignment.top,
); );
expect(copy.dense, false); expect(copy.dense, false);
...@@ -758,6 +772,43 @@ void main() { ...@@ -758,6 +772,43 @@ void main() {
expect(copy.minVerticalPadding, 700); expect(copy.minVerticalPadding, 700);
expect(copy.minLeadingWidth, 800); expect(copy.minLeadingWidth, 800);
expect(copy.enableFeedback, false); expect(copy.enableFeedback, false);
expect(copy.titleAlignment, ListTileTitleAlignment.top);
});
testWidgets('ListTileTheme.titleAlignment is overridden by ListTile.titleAlignment', (WidgetTester tester) async {
final Key leadingKey = GlobalKey();
final Key trailingKey = GlobalKey();
const String titleText = '\nHeadline Text\n';
const String subtitleText = '\nSupporting Text\n';
Widget buildFrame({ ListTileTitleAlignment? alignment }) {
return MaterialApp(
theme: ThemeData(
useMaterial3: true,
listTileTheme: const ListTileThemeData(
titleAlignment: ListTileTitleAlignment.center,
),
),
home: Material(
child: Center(
child: ListTile(
titleAlignment: ListTileTitleAlignment.top,
leading: SizedBox(key: leadingKey, width: 24.0, height: 24.0),
title: const Text(titleText),
subtitle: const Text(subtitleText),
trailing: SizedBox(key: trailingKey, width: 24.0, height: 24.0),
),
),
),
);
}
await tester.pumpWidget(buildFrame());
final Offset tileOffset = tester.getTopLeft(find.byType(ListTile));
final Offset leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
final Offset trailingOffset = tester.getTopRight(find.byKey(trailingKey));
expect(leadingOffset.dy - tileOffset.dy, 8.0);
expect(trailingOffset.dy - tileOffset.dy, 8.0);
}); });
} }
......
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