Adds a Listview tile select example (#99165)
diff --git a/examples/api/lib/widgets/scroll_view/listview_select.1.dart b/examples/api/lib/widgets/scroll_view/listview_select.1.dart
new file mode 100644
index 0000000..342ee36
--- /dev/null
+++ b/examples/api/lib/widgets/scroll_view/listview_select.1.dart
@@ -0,0 +1,239 @@
+// 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 selection in a ListView or GridView
+// Long press any ListTile to enable selection mode.
+
+import 'package:flutter/material.dart';
+
+void main() => runApp(const MyApp());
+
+class MyApp extends StatelessWidget {
+ const MyApp({Key? key}) : super(key: key);
+
+ static const String _title = 'Flutter Code Sample';
+
+ @override
+ Widget build(BuildContext context) {
+ return const MaterialApp(
+ title: _title,
+ home: ListTileSelectExample(),
+ );
+ }
+}
+
+class ListTileSelectExample extends StatefulWidget {
+ const ListTileSelectExample({Key? key}) : super(key: key);
+
+ @override
+ ListTileSelectExampleState createState() => ListTileSelectExampleState();
+}
+
+class ListTileSelectExampleState extends State<ListTileSelectExample> {
+ bool isSelectionMode = false;
+ final int listLength = 30;
+ late List<bool> _selected;
+ bool _selectAll = false;
+ bool _isGridMode = false;
+
+ @override
+ void initState() {
+ super.initState();
+ initializeSelection();
+ }
+
+ void initializeSelection() {
+ _selected = List<bool>.generate(listLength, (_) => false);
+ }
+
+ @override
+ void dispose() {
+ _selected.clear();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text(
+ 'ListTile selection',
+ ),
+ leading: isSelectionMode
+ ? IconButton(
+ icon: const Icon(Icons.close),
+ onPressed: () {
+ setState(() {
+ isSelectionMode = false;
+ });
+ initializeSelection();
+ },
+ )
+ : const SizedBox(),
+ actions: <Widget>[
+ if (_isGridMode)
+ IconButton(
+ icon: const Icon(Icons.grid_on),
+ onPressed: () {
+ setState(() {
+ _isGridMode = false;
+ });
+ },
+ )
+ else
+ IconButton(
+ icon: const Icon(Icons.list),
+ onPressed: () {
+ setState(() {
+ _isGridMode = true;
+ });
+ },
+ ),
+ if (isSelectionMode)
+ TextButton(
+ child: !_selectAll
+ ? const Text(
+ 'select all',
+ style: TextStyle(color: Colors.white),
+ )
+ : const Text(
+ 'unselect all',
+ style: TextStyle(color: Colors.white),
+ ),
+ onPressed: () {
+ _selectAll = !_selectAll;
+ setState(() {
+ _selected =
+ List<bool>.generate(listLength, (_) => _selectAll);
+ });
+ }),
+ ],
+ ),
+ body: _isGridMode
+ ? GridBuilder(
+ isSelectionMode: isSelectionMode,
+ selectedList: _selected,
+ onSelectionChange: (bool x) {
+ setState(() {
+ isSelectionMode = x;
+ });
+ },
+ )
+ : ListBuilder(
+ isSelectionMode: isSelectionMode,
+ selectedList: _selected,
+ onSelectionChange: (bool x) {
+ setState(() {
+ isSelectionMode = x;
+ });
+ },
+ ));
+ }
+}
+
+class GridBuilder extends StatefulWidget {
+ const GridBuilder({
+ Key? key,
+ required this.selectedList,
+ required this.isSelectionMode,
+ required this.onSelectionChange,
+ }) : super(key: key);
+
+ final bool isSelectionMode;
+ final Function(bool)? onSelectionChange;
+ final List<bool> selectedList;
+
+ @override
+ GridBuilderState createState() => GridBuilderState();
+}
+
+class GridBuilderState extends State<GridBuilder> {
+ void _toggle(int index) {
+ if (widget.isSelectionMode) {
+ setState(() {
+ widget.selectedList[index] = !widget.selectedList[index];
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GridView.builder(
+ itemCount: widget.selectedList.length,
+ gridDelegate:
+ const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
+ itemBuilder: (_, int index) {
+ return InkWell(
+ onTap: () => _toggle(index),
+ onLongPress: () {
+ if (!widget.isSelectionMode) {
+ setState(() {
+ widget.selectedList[index] = true;
+ });
+ widget.onSelectionChange!(true);
+ }
+ },
+ child: GridTile(
+ child: Container(
+ child: widget.isSelectionMode
+ ? Checkbox(
+ onChanged: (bool? x) => _toggle(index),
+ value: widget.selectedList[index])
+ : const Icon(Icons.image),
+ )),
+ );
+ });
+ }
+}
+
+class ListBuilder extends StatefulWidget {
+ const ListBuilder({
+ Key? key,
+ required this.selectedList,
+ required this.isSelectionMode,
+ required this.onSelectionChange,
+ }) : super(key: key);
+
+ final bool isSelectionMode;
+ final List<bool> selectedList;
+ final Function(bool)? onSelectionChange;
+
+ @override
+ State<ListBuilder> createState() => _ListBuilderState();
+}
+
+class _ListBuilderState extends State<ListBuilder> {
+ void _toggle(int index) {
+ if (widget.isSelectionMode) {
+ setState(() {
+ widget.selectedList[index] = !widget.selectedList[index];
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return ListView.builder(
+ itemCount: widget.selectedList.length,
+ itemBuilder: (_, int index) {
+ return ListTile(
+ onTap: () => _toggle(index),
+ onLongPress: () {
+ if (!widget.isSelectionMode) {
+ setState(() {
+ widget.selectedList[index] = true;
+ });
+ widget.onSelectionChange!(true);
+ }
+ },
+ trailing: widget.isSelectionMode
+ ? Checkbox(
+ value: widget.selectedList[index],
+ onChanged: (bool? x) => _toggle(index),
+ )
+ : const SizedBox.shrink(),
+ title: Text('item $index'));
+ });
+ }
+}
diff --git a/examples/api/test/widgets/scrollview/listview_select_test.1.dart b/examples/api/test/widgets/scrollview/listview_select_test.1.dart
new file mode 100644
index 0000000..11087ce
--- /dev/null
+++ b/examples/api/test/widgets/scrollview/listview_select_test.1.dart
@@ -0,0 +1,74 @@
+// 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/widgets/scroll_view/listview_select.1.dart'
+ as example;
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ testWidgets('long press ListTile should enable edit mode',
+ (WidgetTester tester) async {
+ await tester.pumpWidget(
+ const example.MyApp(),
+ );
+
+ final Finder listView = find.byType(ListView);
+ final Finder selectAllFinder = find.text('select all');
+ final Finder checkBoxFinder = find.byType(Checkbox);
+ expect(listView, findsWidgets);
+ expect(selectAllFinder, findsNothing);
+ expect(checkBoxFinder, findsNothing);
+ await tester.longPress(listView.first);
+ await tester.pump();
+ expect(selectAllFinder, findsOneWidget);
+ expect(checkBoxFinder, findsWidgets);
+ });
+
+ testWidgets('Pressing cross button should disable edit mode',
+ (WidgetTester tester) async {
+ await tester.pumpWidget(
+ const example.MyApp(),
+ );
+
+ final Finder listView = find.byType(ListView);
+ final Finder crossIconFinder = find.byIcon(Icons.close);
+ expect(listView, findsWidgets);
+ expect(crossIconFinder, findsNothing);
+
+ /// enable edit mode
+ await tester.longPress(listView.first);
+ await tester.pump();
+
+ expect(crossIconFinder, findsOneWidget);
+ await tester.tap(crossIconFinder);
+ await tester.pump();
+ final Finder selectAllFinder = find.text('select all');
+ expect(selectAllFinder, findsNothing);
+ expect(crossIconFinder, findsNothing);
+ });
+
+ testWidgets('tapping ListTile or checkBox should toggle ListTile state',
+ (WidgetTester tester) async {
+ await tester.pumpWidget(
+ const example.MyApp(),
+ );
+
+ final Finder listView = find.byType(ListView);
+ final Finder selectAllFinder = find.text('select all');
+ expect(listView, findsWidgets);
+
+ /// enable edit mode
+ await tester.longPress(listView.first);
+ await tester.pump();
+
+ final Finder checkBoxFinder = find.byType(Checkbox).first;
+ expect(selectAllFinder, findsOneWidget);
+ expect(checkBoxFinder, findsOneWidget);
+ expect(tester.widget<Checkbox>(checkBoxFinder).value, false);
+ await tester.tap(checkBoxFinder);
+ await tester.pump();
+ expect(tester.widget<Checkbox>(checkBoxFinder).value, true);
+ });
+}
diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart
index ecc3029..a7f69bd 100644
--- a/packages/flutter/lib/src/widgets/scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/scroll_view.dart
@@ -1010,6 +1010,13 @@
/// example of how a caller might wire up basic item selection, see
/// [ListTile.selected].
///
+/// {@tool dartpad}
+/// This example shows a custom implementation of [ListTile] selection in a [ListView] or [GridView].
+/// Long press any ListTile to enable selection mode.
+///
+/// ** See code in examples/api/lib/widgets/scroll_view/listview_select.1.dart **
+/// {@end-tool}
+///
/// See also:
///
/// * [SingleChildScrollView], which is a scrollable widget that has a single
@@ -1700,6 +1707,13 @@
/// ```
/// {@end-tool}
///
+/// {@tool dartpad}
+/// This example shows a custom implementation of [ListTile] selection in a [GridView] or [ListView].
+/// Long press any ListTile to enable selection mode.
+///
+/// ** See code in examples/api/lib/widgets/scroll_view/listview_select.1.dart **
+/// {@end-tool}
+///
/// See also:
///
/// * [SingleChildScrollView], which is a scrollable widget that has a single