From 67a0df41cd7fadbe77c6b1d60ad9a84bc8675d0c Mon Sep 17 00:00:00 2001 From: Mahesh Jamdade <31410839+maheshmnj@users.noreply.github.com> Date: Tue, 8 Mar 2022 08:26:17 +0530 Subject: [PATCH] Adds a Listview tile select example (#99165) --- .../scroll_view/listview_select.1.dart | 239 ++++++++++++++++++ .../scrollview/listview_select_test.1.dart | 74 ++++++ .../flutter/lib/src/widgets/scroll_view.dart | 14 + 3 files changed, 327 insertions(+) create mode 100644 examples/api/lib/widgets/scroll_view/listview_select.1.dart create mode 100644 examples/api/test/widgets/scrollview/listview_select_test.1.dart 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 0000000000..342ee365e4 --- /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 { + bool isSelectionMode = false; + final int listLength = 30; + late List _selected; + bool _selectAll = false; + bool _isGridMode = false; + + @override + void initState() { + super.initState(); + initializeSelection(); + } + + void initializeSelection() { + _selected = List.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: [ + 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.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 selectedList; + + @override + GridBuilderState createState() => GridBuilderState(); +} + +class GridBuilderState extends State { + 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 selectedList; + final Function(bool)? onSelectionChange; + + @override + State createState() => _ListBuilderState(); +} + +class _ListBuilderState extends State { + 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 0000000000..11087ceb97 --- /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(checkBoxFinder).value, false); + await tester.tap(checkBoxFinder); + await tester.pump(); + expect(tester.widget(checkBoxFinder).value, true); + }); +} diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart index ecc3029ef0..a7f69bdb82 100644 --- a/packages/flutter/lib/src/widgets/scroll_view.dart +++ b/packages/flutter/lib/src/widgets/scroll_view.dart @@ -1010,6 +1010,13 @@ abstract class BoxScrollView extends ScrollView { /// 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 @@ class ListView extends BoxScrollView { /// ``` /// {@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