diff --git a/examples/api/lib/material/list_tile/list_tile.4.dart b/examples/api/lib/material/list_tile/custom_list_item.0.dart similarity index 68% rename from examples/api/lib/material/list_tile/list_tile.4.dart rename to examples/api/lib/material/list_tile/custom_list_item.0.dart index 4d748fa10813..9c5a1172ceb8 100644 --- a/examples/api/lib/material/list_tile/list_tile.4.dart +++ b/examples/api/lib/material/list_tile/custom_list_item.0.dart @@ -2,25 +2,19 @@ // 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]. +// Flutter code sample for custom list items. import 'package:flutter/material.dart'; -void main() => runApp(const MyApp()); +void main() => runApp(const CustomListItemApp()); -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - static const String _title = 'Flutter Code Sample'; +class CustomListItemApp extends StatelessWidget { + const CustomListItemApp({super.key}); @override Widget build(BuildContext context) { - return MaterialApp( - title: _title, - home: Scaffold( - appBar: AppBar(title: const Text(_title)), - body: const MyStatelessWidget(), - ), + return const MaterialApp( + home: CustomListItemExample(), ); } } @@ -109,32 +103,35 @@ class _VideoDescription extends StatelessWidget { } } -class MyStatelessWidget extends StatelessWidget { - const MyStatelessWidget({super.key}); +class CustomListItemExample extends StatelessWidget { + const CustomListItemExample({super.key}); @override Widget build(BuildContext context) { - return ListView( - padding: const EdgeInsets.all(8.0), - itemExtent: 106.0, - children: [ - CustomListItem( - user: 'Flutter', - viewCount: 999000, - thumbnail: Container( - decoration: const BoxDecoration(color: Colors.blue), + return Scaffold( + appBar: AppBar(title: const Text('Custom List Item Sample')), + body: ListView( + padding: const EdgeInsets.all(8.0), + itemExtent: 106.0, + children: [ + CustomListItem( + user: 'Flutter', + viewCount: 999000, + thumbnail: Container( + decoration: const BoxDecoration(color: Colors.blue), + ), + title: 'The Flutter YouTube Channel', ), - title: 'The Flutter YouTube Channel', - ), - CustomListItem( - user: 'Dash', - viewCount: 884000, - thumbnail: Container( - decoration: const BoxDecoration(color: Colors.yellow), + CustomListItem( + user: 'Dash', + viewCount: 884000, + thumbnail: Container( + decoration: const BoxDecoration(color: Colors.yellow), + ), + title: 'Announcing Flutter 1.0', ), - title: 'Announcing Flutter 1.0', - ), - ], + ], + ), ); } } diff --git a/examples/api/lib/material/list_tile/list_tile.5.dart b/examples/api/lib/material/list_tile/custom_list_item.1.dart similarity index 72% rename from examples/api/lib/material/list_tile/list_tile.5.dart rename to examples/api/lib/material/list_tile/custom_list_item.1.dart index 3a6ab7615bc6..19477cd64ec5 100644 --- a/examples/api/lib/material/list_tile/list_tile.5.dart +++ b/examples/api/lib/material/list_tile/custom_list_item.1.dart @@ -2,25 +2,19 @@ // 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]. +// Flutter code sample for custom list items. import 'package:flutter/material.dart'; -void main() => runApp(const MyApp()); +void main() => runApp(const CustomListItemApp()); -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - static const String _title = 'Flutter Code Sample'; +class CustomListItemApp extends StatelessWidget { + const CustomListItemApp({super.key}); @override Widget build(BuildContext context) { - return MaterialApp( - title: _title, - home: Scaffold( - appBar: AppBar(title: const Text(_title)), - body: const MyStatelessWidget(), - ), + return const MaterialApp( + home: CustomListItemExample(), ); } } @@ -147,36 +141,39 @@ class CustomListItemTwo extends StatelessWidget { } } -class MyStatelessWidget extends StatelessWidget { - const MyStatelessWidget({super.key}); +class CustomListItemExample extends StatelessWidget { + const CustomListItemExample({super.key}); @override Widget build(BuildContext context) { - return ListView( - padding: const EdgeInsets.all(10.0), - children: [ - CustomListItemTwo( - thumbnail: Container( - decoration: const BoxDecoration(color: Colors.pink), + return Scaffold( + appBar: AppBar(title: const Text('Custom List Item Sample')), + body: ListView( + padding: const EdgeInsets.all(10.0), + children: [ + CustomListItemTwo( + thumbnail: Container( + decoration: const BoxDecoration(color: Colors.pink), + ), + title: 'Flutter 1.0 Launch', + subtitle: 'Flutter continues to improve and expand its horizons. ' + 'This text should max out at two lines and clip', + author: 'Dash', + publishDate: 'Dec 28', + readDuration: '5 mins', ), - title: 'Flutter 1.0 Launch', - subtitle: 'Flutter continues to improve and expand its horizons. ' - 'This text should max out at two lines and clip', - author: 'Dash', - publishDate: 'Dec 28', - readDuration: '5 mins', - ), - CustomListItemTwo( - thumbnail: Container( - decoration: const BoxDecoration(color: Colors.blue), + CustomListItemTwo( + thumbnail: Container( + decoration: const BoxDecoration(color: Colors.blue), + ), + title: 'Flutter 1.2 Release - Continual updates to the framework', + subtitle: 'Flutter once again improves and makes updates.', + author: 'Flutter', + publishDate: 'Feb 26', + readDuration: '12 mins', ), - title: 'Flutter 1.2 Release - Continual updates to the framework', - subtitle: 'Flutter once again improves and makes updates.', - author: 'Flutter', - publishDate: 'Feb 26', - readDuration: '12 mins', - ), - ], + ], + ), ); } } diff --git a/examples/api/lib/material/list_tile/list_tile.0.dart b/examples/api/lib/material/list_tile/list_tile.0.dart index 3a384a3b7623..4f4ebeded534 100644 --- a/examples/api/lib/material/list_tile/list_tile.0.dart +++ b/examples/api/lib/material/list_tile/list_tile.0.dart @@ -19,10 +19,7 @@ class ListTileApp extends StatelessWidget { textColor: Colors.white, ) ), - home: Scaffold( - appBar: AppBar(title: const Text('ListTile Samples')), - body: const LisTileExample(), - ), + home: const LisTileExample(), ); } } @@ -73,77 +70,80 @@ class _LisTileExampleState extends State with TickerProviderStat @override Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Hero( - tag: 'ListTile-Hero', - // Wrap the ListTile in a Material widget so the ListTile has someplace - // to draw the animated colors during the hero transition. - child: Material( - child: ListTile( - title: const Text('ListTile with Hero'), - subtitle: const Text('Tap here for Hero transition'), - tileColor: Colors.cyan, - onTap: () { - Navigator.push(context, MaterialPageRoute( - builder: (BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('ListTile Hero')), - body: Center( - child: Hero( - tag: 'ListTile-Hero', - child: Material( - child: ListTile( - title: const Text('ListTile with Hero'), - subtitle: const Text('Tap here to go back'), - tileColor: Colors.blue[700], - onTap: () { - Navigator.pop(context); - }, + return Scaffold( + appBar: AppBar(title: const Text('ListTile Samples')), + body: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Hero( + tag: 'ListTile-Hero', + // Wrap the ListTile in a Material widget so the ListTile has someplace + // to draw the animated colors during the hero transition. + child: Material( + child: ListTile( + title: const Text('ListTile with Hero'), + subtitle: const Text('Tap here for Hero transition'), + tileColor: Colors.cyan, + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('ListTile Hero')), + body: Center( + child: Hero( + tag: 'ListTile-Hero', + child: Material( + child: ListTile( + title: const Text('ListTile with Hero'), + subtitle: const Text('Tap here to go back'), + tileColor: Colors.blue[700], + onTap: () { + Navigator.pop(context); + }, + ), ), ), ), - ), - ); - }), - ); - }, + ); + }), + ); + }, + ), ), ), - ), - FadeTransition( - opacity: _fadeAnimation, - // Wrap the ListTile in a Material widget so the ListTile has someplace - // to draw the animated colors during the fade transition. - child: const Material( - child: ListTile( - title: Text('ListTile with FadeTransition'), - selectedTileColor: Colors.green, - selectedColor: Colors.white, - selected: true, + FadeTransition( + opacity: _fadeAnimation, + // Wrap the ListTile in a Material widget so the ListTile has someplace + // to draw the animated colors during the fade transition. + child: const Material( + child: ListTile( + title: Text('ListTile with FadeTransition'), + selectedTileColor: Colors.green, + selectedColor: Colors.white, + selected: true, + ), ), ), - ), - SizedBox( - height: 100, - child: Center( - child: SizeTransition( - sizeFactor: _sizeAnimation, - axisAlignment: -1.0, - // Wrap the ListTile in a Material widget so the ListTile has someplace - // to draw the animated colors during the size transition. - child: const Material( - child: ListTile( - title: Text('ListTile with SizeTransition'), - tileColor: Colors.red, - minVerticalPadding: 25.0, + SizedBox( + height: 100, + child: Center( + child: SizeTransition( + sizeFactor: _sizeAnimation, + axisAlignment: -1.0, + // Wrap the ListTile in a Material widget so the ListTile has someplace + // to draw the animated colors during the size transition. + child: const Material( + child: ListTile( + title: Text('ListTile with SizeTransition'), + tileColor: Colors.red, + minVerticalPadding: 25.0, + ), ), ), ), ), - ), - ], + ], + ), ); } } diff --git a/examples/api/lib/material/list_tile/list_tile.1.dart b/examples/api/lib/material/list_tile/list_tile.1.dart index 26cc83c7b1c7..9df3aeb8fab4 100644 --- a/examples/api/lib/material/list_tile/list_tile.1.dart +++ b/examples/api/lib/material/list_tile/list_tile.1.dart @@ -13,11 +13,8 @@ class ListTileApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar(title: const Text('ListTile Sample')), - body: const LisTileExample(), - ), + return const MaterialApp( + home: LisTileExample(), ); } } @@ -27,54 +24,57 @@ class LisTileExample extends StatelessWidget { @override Widget build(BuildContext context) { - return ListView( - children: const [ - Card(child: ListTile(title: Text('One-line ListTile'))), - Card( - child: ListTile( - leading: FlutterLogo(), - title: Text('One-line with leading widget'), + return Scaffold( + appBar: AppBar(title: const Text('ListTile Sample')), + body: ListView( + children: const [ + Card(child: ListTile(title: Text('One-line ListTile'))), + Card( + child: ListTile( + leading: FlutterLogo(), + title: Text('One-line with leading widget'), + ), ), - ), - Card( - child: ListTile( - title: Text('One-line with trailing widget'), - trailing: Icon(Icons.more_vert), + Card( + child: ListTile( + title: Text('One-line with trailing widget'), + trailing: Icon(Icons.more_vert), + ), ), - ), - Card( - child: ListTile( - leading: FlutterLogo(), - title: Text('One-line with both widgets'), - trailing: Icon(Icons.more_vert), + Card( + child: ListTile( + leading: FlutterLogo(), + title: Text('One-line with both widgets'), + trailing: Icon(Icons.more_vert), + ), ), - ), - Card( - child: ListTile( - title: Text('One-line dense ListTile'), - dense: true, + Card( + child: ListTile( + title: Text('One-line dense ListTile'), + dense: true, + ), ), - ), - Card( - child: ListTile( - leading: FlutterLogo(size: 56.0), - title: Text('Two-line ListTile'), - subtitle: Text('Here is a second line'), - trailing: Icon(Icons.more_vert), + Card( + child: ListTile( + leading: FlutterLogo(size: 56.0), + title: Text('Two-line ListTile'), + subtitle: Text('Here is a second line'), + trailing: Icon(Icons.more_vert), + ), ), - ), - Card( - child: ListTile( - leading: FlutterLogo(size: 72.0), - title: Text('Three-line ListTile'), - subtitle: Text( - 'A sufficiently long subtitle warrants three lines.' + Card( + child: ListTile( + leading: FlutterLogo(size: 72.0), + title: Text('Three-line ListTile'), + subtitle: Text( + 'A sufficiently long subtitle warrants three lines.' + ), + trailing: Icon(Icons.more_vert), + isThreeLine: true, ), - trailing: Icon(Icons.more_vert), - isThreeLine: true, ), - ), - ], + ], + ), ); } } diff --git a/examples/api/lib/material/list_tile/list_tile.2.dart b/examples/api/lib/material/list_tile/list_tile.2.dart new file mode 100644 index 000000000000..9b8d36e3bfe8 --- /dev/null +++ b/examples/api/lib/material/list_tile/list_tile.2.dart @@ -0,0 +1,64 @@ +// 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(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true), + home: const ListTileExample(), + ); + } +} + +class ListTileExample extends StatelessWidget { + const ListTileExample({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('ListTile Sample')), + body: Column( + children: const [ + ListTile( + leading: CircleAvatar( + child: Text('A') + ), + title: Text('Headline'), + subtitle: Text('Supporting text'), + trailing: Icon(Icons.favorite_rounded), + ), + Divider(), + ListTile( + leading: CircleAvatar( + child: Text('B') + ), + title: Text('Headline'), + subtitle: Text('Longer supporting text to demonstrate how the text wraps and how the leading and trailing widgets are centered vertically with the text.'), + trailing: Icon(Icons.favorite_rounded), + ), + Divider(), + ListTile( + leading: CircleAvatar( + child: Text('C') + ), + title: Text('Headline'), + subtitle: Text("Longer supporting text to demonstrate how the text wraps and how setting 'ListTile.isThreeLine = true' aligns leading and trailing widgets to the top vertically with the text."), + trailing: Icon(Icons.favorite_rounded), + isThreeLine: true, + ), + Divider(), + ], + ), + ); + } +} diff --git a/examples/api/lib/material/list_tile/list_tile.3.dart b/examples/api/lib/material/list_tile/list_tile.3.dart new file mode 100644 index 000000000000..91a437870de2 --- /dev/null +++ b/examples/api/lib/material/list_tile/list_tile.3.dart @@ -0,0 +1,89 @@ +// 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(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true), + home: const ListTileExample(), + ); + } +} + +class ListTileExample extends StatefulWidget { + const ListTileExample({super.key}); + + @override + State createState() => _ListTileExampleState(); +} + +class _ListTileExampleState extends State { + bool _selected = false; + bool _enabled = true; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('ListTile Sample')), + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ListTile( + enabled: _enabled, + selected: _selected, + onTap: () { + setState(() { + // This is called when the user toggles the switch. + _selected = !_selected; + }); + }, + // This sets text color and icon color to red when list tile is disabled and + // green when list tile is selected, otherwise sets it to black. + iconColor: MaterialStateColor.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return Colors.red; + } + if (states.contains(MaterialState.selected)) { + return Colors.green; + } + return Colors.black; + }), + // This sets text color and icon color to red when list tile is disabled and + // green when list tile is selected, otherwise sets it to black. + textColor: MaterialStateColor.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return Colors.red; + } + if (states.contains(MaterialState.selected)) { + return Colors.green; + } + return Colors.black; + }), + leading: const Icon(Icons.person), + title: const Text('Headline'), + subtitle: Text('Enabled: $_enabled, Selected: $_selected'), + trailing: Switch( + onChanged: (bool? value) { + // This is called when the user toggles the switch. + setState(() { + _enabled = value!; + }); + }, + value: _enabled, + ), + ), + ], + ), + ); + } +} diff --git a/examples/api/lib/material/list_tile/list_tile.selected.0.dart b/examples/api/lib/material/list_tile/list_tile.selected.0.dart index 59295707e3ee..c746378218a2 100644 --- a/examples/api/lib/material/list_tile/list_tile.selected.0.dart +++ b/examples/api/lib/material/list_tile/list_tile.selected.0.dart @@ -6,50 +6,46 @@ import 'package:flutter/material.dart'; -void main() => runApp(const MyApp()); +void main() => runApp(const ListTileApp()); -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - static const String _title = 'Flutter Code Sample'; +class ListTileApp extends StatelessWidget { + const ListTileApp({super.key}); @override Widget build(BuildContext context) { - return MaterialApp( - title: _title, - home: Scaffold( - appBar: AppBar(title: const Text(_title)), - body: const MyStatefulWidget(), - ), + return const MaterialApp( + home: LisTileExample(), ); } } - -class MyStatefulWidget extends StatefulWidget { - const MyStatefulWidget({super.key}); +class LisTileExample extends StatefulWidget { + const LisTileExample({super.key}); @override - State createState() => _MyStatefulWidgetState(); + State createState() => _LisTileExampleState(); } -class _MyStatefulWidgetState extends State { +class _LisTileExampleState extends State { int _selectedIndex = 0; @override Widget build(BuildContext context) { - return ListView.builder( - itemCount: 10, - itemBuilder: (BuildContext context, int index) { - return ListTile( - title: Text('Item $index'), - selected: index == _selectedIndex, - onTap: () { - setState(() { - _selectedIndex = index; - }); - }, - ); - }, + return Scaffold( + appBar: AppBar(title: const Text('Custom List Item Sample')), + body: ListView.builder( + itemCount: 10, + itemBuilder: (BuildContext context, int index) { + return ListTile( + title: Text('Item $index'), + selected: index == _selectedIndex, + onTap: () { + setState(() { + _selectedIndex = index; + }); + }, + ); + }, + ), ); } } diff --git a/examples/api/test/material/list_tile/custom_list_item.0_test.dart b/examples/api/test/material/list_tile/custom_list_item.0_test.dart new file mode 100644 index 000000000000..3aec3a3a2174 --- /dev/null +++ b/examples/api/test/material/list_tile/custom_list_item.0_test.dart @@ -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/list_tile/custom_list_item.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Custom list item uses Expanded widgets for the layout', (WidgetTester tester) async { + await tester.pumpWidget( + const example.CustomListItemApp(), + ); + + // The Expanded widget is used to control the size of the thumbnail. + Expanded thumbnailExpanded = tester.widget(find.ancestor( + of: find.byType(Container).first, + matching: find.byType(Expanded), + )); + expect(thumbnailExpanded.flex, 2); + + // The Expanded widget is used to control the size of the text. + Expanded textExpanded = tester.widget(find.ancestor( + of: find.text('The Flutter YouTube Channel'), + matching: find.byType(Expanded), + )); + expect(textExpanded.flex, 3); + + // The Expanded widget is used to control the size of the thumbnail. + thumbnailExpanded = tester.widget(find.ancestor( + of: find.byType(Container).last, + matching: find.byType(Expanded), + )); + expect(thumbnailExpanded.flex, 2); + + // The Expanded widget is used to control the size of the text. + textExpanded = tester.widget(find.ancestor( + of: find.text('Announcing Flutter 1.0'), + matching: find.byType(Expanded), + )); + expect(textExpanded.flex, 3); + }); +} diff --git a/examples/api/test/material/list_tile/custom_list_item.1_test.dart b/examples/api/test/material/list_tile/custom_list_item.1_test.dart new file mode 100644 index 000000000000..0002d0125dad --- /dev/null +++ b/examples/api/test/material/list_tile/custom_list_item.1_test.dart @@ -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/list_tile/custom_list_item.1.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Custom list item uses AspectRatio and Expanded widgets for the layout', (WidgetTester tester) async { + await tester.pumpWidget( + const example.CustomListItemApp(), + ); + + // The AspectRatio widget is used to constrain the size of the thumbnail. + AspectRatio thumbnailAspectRatio = tester.widget(find.ancestor( + of: find.byType(Container).first, + matching: find.byType(AspectRatio), + )); + expect(thumbnailAspectRatio.aspectRatio, 1.0); + + // The Expanded widget is used to control the size of the text. + Expanded textExpanded = tester.widget(find.ancestor( + of: find.text('Flutter 1.0 Launch'), + matching: find.byType(Expanded).at(0), + )); + expect(textExpanded.flex, 1); + + // The AspectRatio widget is used to constrain the size of the thumbnail. + thumbnailAspectRatio = tester.widget(find.ancestor( + of: find.byType(Container).last, + matching: find.byType(AspectRatio), + )); + expect(thumbnailAspectRatio.aspectRatio, 1.0); + + // The Expanded widget is used to control the size of the text. + textExpanded = tester.widget(find.ancestor( + of: find.text('Flutter 1.2 Release - Continual updates to the framework'), + matching: find.byType(Expanded).at(3), + )); + expect(textExpanded.flex, 1); + }); +} diff --git a/examples/api/test/material/list_tile/list_tile.0_test.dart b/examples/api/test/material/list_tile/list_tile.0_test.dart index 5cea641cabc1..7b11b573792f 100644 --- a/examples/api/test/material/list_tile/list_tile.0_test.dart +++ b/examples/api/test/material/list_tile/list_tile.0_test.dart @@ -21,8 +21,12 @@ void main() { expect(find.text(heroTransitionText), findsOneWidget); expect(find.text(goBackText), findsNothing); + + // Tap on the ListTile widget to trigger the Hero transition. await tester.tap(find.text(heroTransitionText)); await tester.pumpAndSettle(); + + // The Hero transition is triggered and tap to go back text is displayed. expect(find.text(heroTransitionText), findsNothing); expect(find.text(goBackText), findsOneWidget); diff --git a/examples/api/test/material/list_tile/list_tile.1_test.dart b/examples/api/test/material/list_tile/list_tile.1_test.dart index 5beb9f3c770b..c651aceed565 100644 --- a/examples/api/test/material/list_tile/list_tile.1_test.dart +++ b/examples/api/test/material/list_tile/list_tile.1_test.dart @@ -16,6 +16,7 @@ void main() { expect(find.byType(ListTile), findsNWidgets(totalTiles)); + // The ListTile widget is wrapped in a Card widget. for (int i = 0; i < totalTiles; i++) { expect( find.ancestor( diff --git a/examples/api/test/material/list_tile/list_tile.2_test.dart b/examples/api/test/material/list_tile/list_tile.2_test.dart new file mode 100644 index 000000000000..ba26d0a34d84 --- /dev/null +++ b/examples/api/test/material/list_tile/list_tile.2_test.dart @@ -0,0 +1,41 @@ +// 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.2.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('ListTile leading and trailing widgets are aligned appropriately', (WidgetTester tester) async { + await tester.pumpWidget( + const example.ListTileApp(), + ); + + expect(find.byType(ListTile), findsNWidgets(3)); + + Offset listTileTopLeft = tester.getTopLeft(find.byType(ListTile).at(0)); + Offset leadingTopLeft = tester.getTopLeft(find.byType(CircleAvatar).at(0)); + Offset trailingTopLeft = tester.getTopLeft(find.byType(Icon).at(0)); + + // The leading and trailing widgets are centered vertically with the text. + expect(leadingTopLeft - listTileTopLeft, const Offset(16.0, 16.0)); + expect(trailingTopLeft - listTileTopLeft, const Offset(752.0, 24.0)); + + listTileTopLeft = tester.getTopLeft(find.byType(ListTile).at(1)); + leadingTopLeft = tester.getTopLeft(find.byType(CircleAvatar).at(1)); + trailingTopLeft = tester.getTopLeft(find.byType(Icon).at(1)); + + // The leading and trailing widgets are centered vertically with the text. + expect(leadingTopLeft - listTileTopLeft, const Offset(16.0, 30.0)); + expect(trailingTopLeft - listTileTopLeft, const Offset(752.0, 38.0)); + + listTileTopLeft = tester.getTopLeft(find.byType(ListTile).at(2)); + leadingTopLeft = tester.getTopLeft(find.byType(CircleAvatar).at(2)); + trailingTopLeft = tester.getTopLeft(find.byType(Icon).at(2)); + + // The leading and trailing widgets are aligned to the top vertically with the text. + expect(leadingTopLeft - listTileTopLeft, const Offset(16.0, 8.0)); + expect(trailingTopLeft - listTileTopLeft, const Offset(752.0, 8.0)); + }); +} diff --git a/examples/api/test/material/list_tile/list_tile.3_test.dart b/examples/api/test/material/list_tile/list_tile.3_test.dart new file mode 100644 index 000000000000..50f685262e29 --- /dev/null +++ b/examples/api/test/material/list_tile/list_tile.3_test.dart @@ -0,0 +1,58 @@ +// 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/rendering.dart'; +import 'package:flutter_api_samples/material/list_tile/list_tile.3.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('ListTile color properties respect Material state color', (WidgetTester tester) async { + await tester.pumpWidget( + const example.ListTileApp(), + ); + ListTile listTile = tester.widget(find.byType(ListTile)); + + // Enabled list tile uses black color for icon and headline. + expect(listTile.enabled, true); + expect(listTile.selected, false); + RenderParagraph headline = _getTextRenderObject(tester, 'Headline'); + expect(headline.text.style!.color, Colors.black); + RichText icon = tester.widget(find.byType(RichText).at(0)); + expect(icon.text.style!.color, Colors.black); + + // Tap list tile to select it. + await tester.tap(find.byType(ListTile)); + await tester.pumpAndSettle(); + + // Selected list tile uses green color for icon and headline. + listTile = tester.widget(find.byType(ListTile)); + expect(listTile.enabled, true); + expect(listTile.selected, true); + headline = _getTextRenderObject(tester, 'Headline'); + expect(headline.text.style!.color, Colors.green); + icon = tester.widget(find.byType(RichText).at(0)); + expect(icon.text.style!.color, Colors.green); + + // Tap switch to disable list tile. + await tester.tap(find.byType(Switch)); + await tester.pumpAndSettle(); + + // Disabled list tile uses red color for icon and headline. + listTile = tester.widget(find.byType(ListTile)); + expect(listTile.enabled, false); + expect(listTile.selected, true); + headline = _getTextRenderObject(tester, 'Headline'); + expect(headline.text.style!.color, Colors.red); + icon = tester.widget(find.byType(RichText).at(0)); + expect(icon.text.style!.color, Colors.red); + }); +} + +RenderParagraph _getTextRenderObject(WidgetTester tester, String text) { + return tester.renderObject(find.descendant( + of: find.byType(ListTile), + matching: find.text(text), + )); +} diff --git a/examples/api/test/material/list_tile/list_tile.selected.0_test.dart b/examples/api/test/material/list_tile/list_tile.selected.0_test.dart new file mode 100644 index 000000000000..155c88c36ecf --- /dev/null +++ b/examples/api/test/material/list_tile/list_tile.selected.0_test.dart @@ -0,0 +1,28 @@ +// 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.selected.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('ListTile item can be selected', (WidgetTester tester) async { + await tester.pumpWidget( + const example.ListTileApp(), + ); + + expect(find.byType(ListTile), findsNWidgets(10)); + + // The first item is selected by default. + expect(tester.widget(find.byType(ListTile).at(0)).selected, true); + + // Tap a list item to select it. + await tester.tap(find.byType(ListTile).at(5)); + await tester.pump(); + + // The first item is no longer selected. + expect(tester.widget(find.byType(ListTile).at(0)).selected, false); + expect(tester.widget(find.byType(ListTile).at(5)).selected, true); + }); +} diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart index 7b0bb2e176cc..bd6817e67538 100644 --- a/packages/flutter/lib/src/material/list_tile.dart +++ b/packages/flutter/lib/src/material/list_tile.dart @@ -139,12 +139,18 @@ enum ListTileControlAffinity { /// ** See code in examples/api/lib/material/list_tile/list_tile.1.dart ** /// {@end-tool} /// -/// {@tool snippet} +/// {@tool dartpad} +/// This sample shows the creation of a [ListTile] using [ThemeData.useMaterial3] flag, +/// as described in: https://m3.material.io/components/lists/overview. +/// +/// ** See code in examples/api/lib/material/list_tile/list_tile.2.dart ** +/// {@end-tool} /// /// To use a [ListTile] within a [Row], it needs to be wrapped in an /// [Expanded] widget. [ListTile] requires fixed width constraints, /// whereas a [Row] does not constrain its children. /// +/// {@tool snippet} /// ```dart /// Row( /// children: const [ @@ -231,7 +237,7 @@ enum ListTileControlAffinity { /// /// ![Custom list item a](https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_list_item_a.png) /// -/// ** See code in examples/api/lib/material/list_tile/list_tile.4.dart ** +/// ** See code in examples/api/lib/material/list_tile/custom_list_item.0.dart ** /// {@end-tool} /// /// {@tool dartpad} @@ -241,7 +247,7 @@ enum ListTileControlAffinity { /// /// ![Custom list item b](https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_list_item_b.png) /// -/// ** See code in examples/api/lib/material/list_tile/list_tile.5.dart ** +/// ** See code in examples/api/lib/material/list_tile/custom_list_item.1.dart ** /// {@end-tool} /// /// See also: @@ -409,7 +415,16 @@ class ListTile extends StatelessWidget { /// Defines the default color for [leading] and [trailing] icons. /// - /// If this property is null then [ListTileThemeData.iconColor] is used. + /// If this property is null and [selected] is false then [ListTileThemeData.iconColor] + /// is used. If that is also null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurface] + /// is used, otherwise if [ThemeData.brightness] is [Brightness.light], [Colors.black54] is used, + /// and if [ThemeData.brightness] is [Brightness.dark], the value is null. + /// + /// If this property is null and [selected] is true then [ListTileThemeData.selectedColor] + /// is used. If that is also null then [ColorScheme.primary] is used. + /// + /// If this color is a [MaterialStateColor] it will be resolved against + /// [MaterialState.selected] and [MaterialState.disabled] states. /// /// See also: /// @@ -417,10 +432,18 @@ class ListTile extends StatelessWidget { /// [ListTileThemeData]. final Color? iconColor; - /// Defines the default color for the [title] and [subtitle]. + /// Defines the text color for the [title], [subtitle], [leading], and [trailing]. + /// + /// If this property is null and [selected] is false then [ListTileThemeData.textColor] + /// is used. If that is also null then default text color is used for the [title], [subtitle] + /// [leading], and [trailing]. Except for [subtitle], if [ThemeData.useMaterial3] is false, + /// [TextTheme.bodySmall] is used. + /// + /// If this property is null and [selected] is true then [ListTileThemeData.selectedColor] + /// is used. If that is also null then [ColorScheme.primary] is used. /// - /// If this property is null then [ListTileThemeData.textColor] is used. If that - /// is also null then [ColorScheme.primary] is used. + /// If this color is a [MaterialStateColor] it will be resolved against + /// [MaterialState.selected] and [MaterialState.disabled] states. /// /// See also: /// @@ -541,8 +564,11 @@ class ListTile extends StatelessWidget { /// {@template flutter.material.ListTile.tileColor} /// Defines the background color of `ListTile` when [selected] is false. /// - /// When the value is null, the [tileColor] is set to [ListTileTheme.tileColor] - /// if it's not null and to [Colors.transparent] if it's null. + /// If this property is null and [selected] is false then [ListTileThemeData.tileColor] + /// is used. If that is also null and [selected] is true, [selectedTileColor] is used. + /// When that is also null, the [ListTileTheme.selectedTileColor] is used, otherwise + /// [Colors.transparent] is used. + /// /// {@endtemplate} final Color? tileColor;