Skip to content

Commit c251856

Browse files
authored
Update ListTile docs for color animation issues and add example (flutter#106955)
1 parent c7ce24d commit c251856

File tree

5 files changed

+302
-52
lines changed

5 files changed

+302
-52
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flutter code sample for ListTile
6+
7+
import 'package:flutter/material.dart';
8+
9+
void main() => runApp(const ListTileApp());
10+
11+
class ListTileApp extends StatelessWidget {
12+
const ListTileApp({super.key});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
return MaterialApp(
17+
theme: ThemeData(
18+
listTileTheme: const ListTileThemeData(
19+
textColor: Colors.white,
20+
)
21+
),
22+
home: Scaffold(
23+
appBar: AppBar(title: const Text('ListTile Samples')),
24+
body: const LisTileExample(),
25+
),
26+
);
27+
}
28+
}
29+
30+
class LisTileExample extends StatefulWidget {
31+
const LisTileExample({super.key});
32+
33+
@override
34+
State<LisTileExample> createState() => _LisTileExampleState();
35+
}
36+
37+
class _LisTileExampleState extends State<LisTileExample> with TickerProviderStateMixin {
38+
late final AnimationController _fadeController;
39+
late final AnimationController _sizeController;
40+
late final Animation<double> _fadeAnimation;
41+
late final Animation<double> _sizeAnimation;
42+
43+
@override
44+
void initState() {
45+
super.initState();
46+
_fadeController = AnimationController(
47+
duration: const Duration(seconds: 1),
48+
vsync: this,
49+
)..repeat(reverse: true);
50+
51+
_sizeController = AnimationController(
52+
duration: const Duration(milliseconds: 850),
53+
vsync: this,
54+
)..repeat(reverse: true);
55+
56+
_fadeAnimation = CurvedAnimation(
57+
parent: _fadeController,
58+
curve: Curves.easeInOut,
59+
);
60+
61+
_sizeAnimation = CurvedAnimation(
62+
parent: _sizeController,
63+
curve: Curves.easeOut,
64+
);
65+
}
66+
67+
@override
68+
void dispose() {
69+
_fadeController.dispose();
70+
_sizeController.dispose();
71+
super.dispose();
72+
}
73+
74+
@override
75+
Widget build(BuildContext context) {
76+
return Column(
77+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
78+
children: <Widget>[
79+
Hero(
80+
tag: 'ListTile-Hero',
81+
// Wrap the ListTile in a Material widget so the ListTile has someplace
82+
// to draw the animated colors during the hero transition.
83+
child: Material(
84+
child: ListTile(
85+
title: const Text('ListTile with Hero'),
86+
subtitle: const Text('Tap here for Hero transition'),
87+
tileColor: Colors.cyan,
88+
onTap: () {
89+
Navigator.push(context, MaterialPageRoute<Widget>(
90+
builder: (BuildContext context) {
91+
return Scaffold(
92+
appBar: AppBar(title: const Text('ListTile Hero')),
93+
body: Center(
94+
child: Hero(
95+
tag: 'ListTile-Hero',
96+
child: Material(
97+
child: ListTile(
98+
title: const Text('ListTile with Hero'),
99+
subtitle: const Text('Tap here to go back'),
100+
tileColor: Colors.blue[700],
101+
onTap: () {
102+
Navigator.pop(context);
103+
},
104+
),
105+
),
106+
),
107+
),
108+
);
109+
}),
110+
);
111+
},
112+
),
113+
),
114+
),
115+
FadeTransition(
116+
opacity: _fadeAnimation,
117+
// Wrap the ListTile in a Material widget so the ListTile has someplace
118+
// to draw the animated colors during the fade transition.
119+
child: const Material(
120+
child: ListTile(
121+
title: Text('ListTile with FadeTransition'),
122+
selectedTileColor: Colors.green,
123+
selectedColor: Colors.white,
124+
selected: true,
125+
),
126+
),
127+
),
128+
SizedBox(
129+
height: 100,
130+
child: Center(
131+
child: SizeTransition(
132+
sizeFactor: _sizeAnimation,
133+
axisAlignment: -1.0,
134+
// Wrap the ListTile in a Material widget so the ListTile has someplace
135+
// to draw the animated colors during the size transition.
136+
child: const Material(
137+
child: ListTile(
138+
title: Text('ListTile with SizeTransition'),
139+
tileColor: Colors.red,
140+
minVerticalPadding: 25.0,
141+
),
142+
),
143+
),
144+
),
145+
),
146+
],
147+
);
148+
}
149+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flutter code sample for ListTile
6+
7+
import 'package:flutter/material.dart';
8+
9+
void main() => runApp(const ListTileApp());
10+
11+
class ListTileApp extends StatelessWidget {
12+
const ListTileApp({super.key});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
return MaterialApp(
17+
home: Scaffold(
18+
appBar: AppBar(title: const Text('ListTile Sample')),
19+
body: const LisTileExample(),
20+
),
21+
);
22+
}
23+
}
24+
25+
class LisTileExample extends StatelessWidget {
26+
const LisTileExample({super.key});
27+
28+
@override
29+
Widget build(BuildContext context) {
30+
return ListView(
31+
children: const <Widget>[
32+
Card(child: ListTile(title: Text('One-line ListTile'))),
33+
Card(
34+
child: ListTile(
35+
leading: FlutterLogo(),
36+
title: Text('One-line with leading widget'),
37+
),
38+
),
39+
Card(
40+
child: ListTile(
41+
title: Text('One-line with trailing widget'),
42+
trailing: Icon(Icons.more_vert),
43+
),
44+
),
45+
Card(
46+
child: ListTile(
47+
leading: FlutterLogo(),
48+
title: Text('One-line with both widgets'),
49+
trailing: Icon(Icons.more_vert),
50+
),
51+
),
52+
Card(
53+
child: ListTile(
54+
title: Text('One-line dense ListTile'),
55+
dense: true,
56+
),
57+
),
58+
Card(
59+
child: ListTile(
60+
leading: FlutterLogo(size: 56.0),
61+
title: Text('Two-line ListTile'),
62+
subtitle: Text('Here is a second line'),
63+
trailing: Icon(Icons.more_vert),
64+
),
65+
),
66+
Card(
67+
child: ListTile(
68+
leading: FlutterLogo(size: 72.0),
69+
title: Text('Three-line ListTile'),
70+
subtitle: Text(
71+
'A sufficiently long subtitle warrants three lines.'
72+
),
73+
trailing: Icon(Icons.more_vert),
74+
isThreeLine: true,
75+
),
76+
),
77+
],
78+
);
79+
}
80+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_api_samples/material/list_tile/list_tile.0.dart' as example;
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
void main() {
10+
testWidgets('ListTile with Hero does not throw', (WidgetTester tester) async {
11+
const int totalTiles = 3;
12+
13+
await tester.pumpWidget(
14+
const example.ListTileApp(),
15+
);
16+
17+
expect(find.byType(ListTile), findsNWidgets(totalTiles));
18+
19+
const String heroTransitionText = 'Tap here for Hero transition';
20+
const String goBackText = 'Tap here to go back';
21+
22+
expect(find.text(heroTransitionText), findsOneWidget);
23+
expect(find.text(goBackText), findsNothing);
24+
await tester.tap(find.text(heroTransitionText));
25+
await tester.pumpAndSettle();
26+
expect(find.text(heroTransitionText), findsNothing);
27+
expect(find.text(goBackText), findsOneWidget);
28+
29+
expect(tester.takeException(), null);
30+
});
31+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_api_samples/material/list_tile/list_tile.1.dart' as example;
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
void main() {
10+
testWidgets('ListTiles wrapped in Card widgets', (WidgetTester tester) async {
11+
const int totalTiles = 7;
12+
13+
await tester.pumpWidget(
14+
const example.ListTileApp(),
15+
);
16+
17+
expect(find.byType(ListTile), findsNWidgets(totalTiles));
18+
19+
for (int i = 0; i < totalTiles; i++) {
20+
expect(
21+
find.ancestor(
22+
of: find.byType(ListTile).at(i),
23+
matching: find.byType(Card).at(i),
24+
),
25+
findsOneWidget,
26+
);
27+
}
28+
});
29+
}

packages/flutter/lib/src/material/list_tile.dart

Lines changed: 13 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -114,65 +114,26 @@ enum ListTileControlAffinity {
114114
/// is expensive. Consider only wrapping the [ListTile]s that require it
115115
/// or include a common [Material] ancestor where possible.
116116
///
117-
/// {@tool snippet}
117+
/// [ListTile] must be wrapped in a [Material] widget to animate [tileColor],
118+
/// [selectedTileColor], [focusColor], and [hoverColor] as these colors
119+
/// are not drawn by the list tile itself but by the material widget ancestor.
120+
///
121+
/// {@tool dartpad}
122+
/// This example showcases how [ListTile] needs to be wrapped in a [Material]
123+
/// widget to animate colors.
124+
///
125+
/// ** See code in examples/api/lib/material/list_tile/list_tile.0.dart **
126+
/// {@end-tool}
118127
///
128+
/// {@tool dartpad}
119129
/// This example uses a [ListView] to demonstrate different configurations of
120130
/// [ListTile]s in [Card]s.
121131
///
122132
/// ![Different variations of ListTile](https://flutter.github.io/assets-for-api-docs/assets/material/list_tile.png)
123133
///
124-
/// ```dart
125-
/// ListView(
126-
/// children: const <Widget>[
127-
/// Card(child: ListTile(title: Text('One-line ListTile'))),
128-
/// Card(
129-
/// child: ListTile(
130-
/// leading: FlutterLogo(),
131-
/// title: Text('One-line with leading widget'),
132-
/// ),
133-
/// ),
134-
/// Card(
135-
/// child: ListTile(
136-
/// title: Text('One-line with trailing widget'),
137-
/// trailing: Icon(Icons.more_vert),
138-
/// ),
139-
/// ),
140-
/// Card(
141-
/// child: ListTile(
142-
/// leading: FlutterLogo(),
143-
/// title: Text('One-line with both widgets'),
144-
/// trailing: Icon(Icons.more_vert),
145-
/// ),
146-
/// ),
147-
/// Card(
148-
/// child: ListTile(
149-
/// title: Text('One-line dense ListTile'),
150-
/// dense: true,
151-
/// ),
152-
/// ),
153-
/// Card(
154-
/// child: ListTile(
155-
/// leading: FlutterLogo(size: 56.0),
156-
/// title: Text('Two-line ListTile'),
157-
/// subtitle: Text('Here is a second line'),
158-
/// trailing: Icon(Icons.more_vert),
159-
/// ),
160-
/// ),
161-
/// Card(
162-
/// child: ListTile(
163-
/// leading: FlutterLogo(size: 72.0),
164-
/// title: Text('Three-line ListTile'),
165-
/// subtitle: Text(
166-
/// 'A sufficiently long subtitle warrants three lines.'
167-
/// ),
168-
/// trailing: Icon(Icons.more_vert),
169-
/// isThreeLine: true,
170-
/// ),
171-
/// ),
172-
/// ],
173-
/// )
174-
/// ```
134+
/// ** See code in examples/api/lib/material/list_tile/list_tile.1.dart **
175135
/// {@end-tool}
136+
///
176137
/// {@tool snippet}
177138
///
178139
/// To use a [ListTile] within a [Row], it needs to be wrapped in an

0 commit comments

Comments
 (0)