การใช้งาน Dismissible Widget ร่วมกับ ListView ใน Flutter
เขียนเมื่อ 1 ปีก่อน โดย Ninenik Narkdeelistview dismissible alertdialog listtile flutter
คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ listview dismissible alertdialog listtile flutter
ไปที่
Copy




ดูตัวอย่างการทำงานอีกครั้ง

เนื้อหานี้เรายังอยู่กับการจัดการไฟล์ และโฟลเดอร์
แต่จะมาเสริมการประยุกต์การใช้งาน Dismissible Widget
เข้ามาเล็กน้อย เป็นแนวทางไว้นำไปใช้ ซึ่งจากตอนที่แล้ว
เราสร้างลิสรายการไฟล์ต่างๆ ในโฟลเดอร์ปัจจุบันที่กำลัง
ใช้งานอยู่ในรูปแบบของ ListView เราจะมาประยุกต์โดยการ
ใช้งาน Dismissible ให้สามารถเลือกทำการปัดรายการไฟล์หรือ
โฟลเดอร์ที่ต้องการ แล้วเลิกที่จะลบรายการนั้นๆ ได้ ทบทวนตอนที่
แล้วได้ที่บทความ
การใช้งาน Path Provider และการเชียนอ่าน File ใน Flutter http://niik.in/1066
https://www.ninenik.com/content.php?arti_id=1066 via @ninenik
*เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/960
ตัวอย่างผลลัพธ์และการทำงาน

เราทำการปัดรายการไฟล์หรือโฟลเดอร์ไปทางซ้าย หรือขวา ก็จะมีคำสั่งที่รองรับการทำงาน
ในตัวอย่างเราปัดไปซ้ายเพื่อลบ ก็จะขึ้นแจ้งให้ยืนยันการลบ กรณีที่เราต้องการป้องกันการปัดรายการ
โดยไม่ตั้งใจ เมื่อเรายืนยันการลบแล้ว รายการก็จะถูกลบออกจากลิส และทำคำสั่งลบข้อมูลต่อไป
การใช้งาน Dismissible Widget
Dismissible Widget เป็น widget ที่สามารถนำออกได้โดยการปัดไปตามทิศทางที่กำหนด โดย child
ที่กำหนดใน widget นี้จะเลื่อนออกไปจากหน้าจอ แล้วเกิด event ที่ชื่อ onDismissed ขึ้น นั่นคือเมื่อ
รายการถูกลบออกไป เราก็สามารถกำหนดคำสั่งการทำงานในให้กับ event นี้ได้
รูปแบบการใช้งาน
return Dismissible( key: UniqueKey(), confirmDismiss: (direction) async { }, onDismissed: (direction) { // ปัดไปทางขวา - บนลงล่าง if(direction == DismissDirection.startToEnd){ } // ปัดไปซ้าย - ล่างขึ้นบน if(direction == DismissDirection.endToStart){ } }, background: Container( // พื้นหลังปัดไปทางขวา color: Colors.green, ), secondaryBackground: Container( // พื้นหลังปัดไปทางซ้าย color: Colors.red, ), child: Text('Dismiss widget') );
รูปแบบการใช้งานข้างต้น รองรับการกำหนด รองรับการยืนยันการทำรายการ หรือก่อนเกิด onDismissed
event ขึ้น ถ้ายกเลิกก็จะไม่เกิด event child ก็จะไม่ถูกลบออกจากรายการ ถ้ากำหนดสีพื้นหลังแค่ค่า
background ค่าพื้นหลังทั้งปัดซ้ายหรือขวาก็จะเป็นค่าสีเดียวกัน เราสามารถกำหนดให้ทำงานเมื่อปัดขวา
อย่าเดียว หรือปัดซ้ายอย่างเดียว หรือทั้งปัดขวาและปัดซ้าย ได้โดยใช้ค่า direction โดยกำหนดเงื่อนไข
ให้ทำงานเฉพาะที่เป็นค่าตามต้องการ
onDismissed: (direction) { // ปัดไปทางขวา - บนลงล่าง if(direction == DismissDirection.startToEnd){ } // ปัดไปซ้าย - ล่างขึ้นบน if(direction == DismissDirection.endToStart){ } },
กรณีรองรับการปัดทุกทิศทาง ก็ไม่ต้องกำหนดเงื่อนไขเข้าไป
onDismissed: (direction) { // ทำงานทุกเงื่อนไขทิศทางที่ปัด },
กรณีใช้กับรายการที่มีหลายๆ รายการเช่น ListView ควรกำหนด่า key เข้าไปเพื่อเป็นการบอกว่าเป็น key
หรือ ไอดีอะไร จะได้เป็นแยกแต่ละรายการชัดเจนไม่ซ้ำกัน และสามารถอ้างอิงได้ ในตัวอย่างเราสามารถใช้
ค่า Key เป็น key: UniqueKey() ได้
เราสามารถกำหนดให้รองรับการยืนยันก่อน ทำงานได้ โดยกำหนดในส่วนของ confirmDismiss
ในรูปแบบดังนี้
confirmDismiss: (direction) async { return await showDialog( context: context, builder: (context) { return AlertDialog( title: const Text("Confirm"), content: const Text("Are you sure you wish to delete this item?"), actions: <Widget>[ ElevatedButton( onPressed: () => Navigator.of(context).pop(true), child: const Text("DELETE") ), ElevatedButton( onPressed: () => Navigator.of(context).pop(false), child: const Text("CANCEL"), ), ], ); }, ); },
เราใช้คำสั่ง showDialog() แล้วเรียกใช้งาน AlertDialog widget โดยจะคืนค่า true กลับมาก็ต่อเมื่อ
กดที่ปุ่ม DELETE หรือยืนยันการลบเท่านั้น ส่วนเงื่อนไขอื่นๆ เป็นการยกเลิก
ตัวอย่าง

ในส่วนของการกำหนดสีพื้นหลัง เราสามารถกำหนดรูปแบบการแสดงด้านใน เพื่อให้ผู้ใช้เข้าใจถึงการ
ทำงานได้ว่า หากปัดไปด้านนี้จะทำงานยังไง เช่น เราต้องการแสดงรูปถังขยะ กรณีปัดไปทางซ้าย ก็สามารถ
ทำได้ดังนี้
secondaryBackground: Container( color: Colors.red, child: Align( alignment: Alignment.centerRight, child: Padding( padding: EdgeInsets.symmetric(horizontal: 10.0), child: FaIcon(FontAwesomeIcons.trashAlt), ) ) ),
ตัวอย่าง

โดยค่าเริ่มต้น ระยะทางการปัดเลื่อนรายการที่จะมีผลให้เกิด onDismissed ขึ้นจะอยู่ที่ 0.4 หรือประมาณ
40 เปอร์เซ็นต์ของความกว้างรายการ นั่นหมายความว่า ถ้าเราปัดไปได้ยังไม่ถึง 40% รายการก็จะปัดกลับ และ
ยกเลิกการ dismiss ไป แต่ถ้าเราปัดไปได้ที่ 40% ขึ้นไป รายการก็จะทำการ dismiss ค่าส่วนนี้ เราสามารถ
เปลี่ยนแปลงได้ ผ่านค่า dismissThresholds สมมติเช่น เรามองว่า 0.4 น้ยยไป กลัวว่าคนอาจจะเผลอไปโดน
โดยไม่รู้ตัว และเราไม่ได้ตั้งให้ confirm เพราะต้องการให้ทำงานรวดเร็วลดขั้นตอน เราก็อาจจะเพิ่มระยะเป็นสัก
60% ก็สามารถกำหนดได้ดังนี้
// ถ้ากำหนดค่าเป็น 1.0 จะหมายถึงต้องปัดให้สุดขอบของรายการ ถึงจะทำงาน dismissThresholds: const { DismissDirection.endToStart:0.6, DismissDirection.startToEnd:0.6},
หากต้องการเปลี่ยนทิศทางการปัดจากแนวนอน เป็นนแวตั้ง ก็สามารถใช้ค่า direction กำหนดทิศทางได้
direction: DismissDirection.vertical, // แนวตั้ง // direction: DismissDirection.horizontal, // ค่าเริ่มต้น แนวนอน
ตัวอย่าง

ไฟล์ home.dart
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:path_provider/path_provider.dart'; import '../components/sidemenu.dart'; class Home extends StatefulWidget { static const routeName = '/'; const Home({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _HomeState(); } } class _HomeState extends State<Home> { List<FileSystemEntity?>? _folders; String _currentPath = ''; // เก็บ path ปัจจุบัน Directory? _currentFolder; // เก็บ โฟลเดอร์ที่กำลังใช้งาน @override void initState() { // TODO: implement initState super.initState(); _loadFolder(); } // เปิดโฟลเดอร์ และแสดงรายการในโฟลเดอร์ void _setPath(dir) async { _currentFolder = dir; _currentPath = dir.path; final myDir = Directory(_currentPath); try{ setState(() { _folders = myDir.listSync(recursive: false, followLinks: false); }); }catch(e){ print(e); } } // คำสังลบไฟล์ void _deleteFile(path) async { final deletefile = File(path); // กำหนด file object final isExits = await deletefile.exists(); // เช็คว่ามีไฟล์หรือไม่ if(isExits){ // ถ้ามีไฟล์ try{ await deletefile.delete(); }catch(e){ print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } // คำสั่งลบโฟลเดอร์ void _deleteFolder(path) async { final deleteFolder = Directory(path); // สร้าง directory object var isExits = await deleteFolder.exists(); // เช็คว่ามีแล้วหรือไม่ if(isExits){ // ถ้ามีโฟลเดอร์ try{ await deleteFolder.delete(recursive: true); }catch(e){ print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } // จำลองสร้างไฟล์ใหม่ void _newFile() async { String newFile = "${_currentFolder!.path}/myfile.txt"; final myfile = File(newFile); // กำหนด file object final isExits = await myfile.exists(); // เช็คว่ามีไฟล์หรือไม่ if(!isExits){ // ถ้ายังไม่มีไฟล์ try{ // สร้างไฟล์ text var file = await myfile.writeAsString( 'Hello World' ); print(file); }catch(e){ print(e); } }else{ // ถ้ามีไฟล์อยู่แล้ว จำลองการลบข้อมูล try{ await myfile.delete(); }catch(e){ print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } // คำสั่งจำลองการสร้างโฟลเดอร์ void _newFolder() async { String newFolder = "${_currentFolder!.path}/mydir"; final myDir = Directory(newFolder); // สร้าง directory object var isExits = await myDir.exists(); // เช็คว่ามีแล้วหรือไม่ if(!isExits){ // ถ้ายังไม่มีสร้างโฟลเดอร์ขึ้นมาใหม่ try{ var directory = await Directory(newFolder).create(recursive: true); print(directory); }catch(e){ print(e); } }else{ // ถ้ามีแล้วจำลองการทำคำสั่งลบ try{ await myDir.delete(recursive: true); }catch(e){ print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } void _loadFolder() async { // ข้อมูลเกี่ยวกับโฟลเดอร์ Directory ต่างๆ final tempDirectory = await getTemporaryDirectory(); final appSupportDirectory = await getApplicationSupportDirectory(); final appDocumentsDirectory = await getApplicationDocumentsDirectory(); final externalDocumentsDirectory = await getExternalStorageDirectory(); final externalStorageDirectories = await getExternalStorageDirectories(type: StorageDirectory.music); final externalCacheDirectories = await getExternalCacheDirectories(); /* print(tempDirectory); print(appSupportDirectory); print(appDocumentsDirectory); print(externalDocumentsDirectory); print(externalCacheDirectories); print(externalStorageDirectories); */ // เมื่อโหลดขึ้นมา เาจะเปิดโฟลเดอร์ของ package เป้นโฟลเดอร์หลัก _currentFolder = appDocumentsDirectory.parent; _currentPath = appDocumentsDirectory.parent.path; final myDir = Directory(_currentPath); setState(() { _folders = myDir.listSync(recursive: false, followLinks: false); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home'), actions: <Widget>[ // IconButton( onPressed: _newFolder, // สร้างโฟลเดอร์ใหม่ icon: FaIcon(FontAwesomeIcons.folderPlus), ), IconButton( onPressed: _newFile, // สร้างไฟล์ใหม่ icon: FaIcon(FontAwesomeIcons.fileAlt), ), ], ), body: Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ ListTile( leading: FaIcon(FontAwesomeIcons.angleLeft), title: Text('${_currentPath.replaceAll('/data/user/0/com.example.flutter_app3', '/')}'), onTap: (){ _setPath(_currentFolder!.parent); } ), Expanded( child: _folders!=null // เมื่อไม่ใช่ค่า null ? ListView.separated( // กรณีมีรายการ แสดงปกติ itemCount: _folders==null ? 0 : _folders!.length, itemBuilder: (context, index) { var isFolder = _folders![index] is Directory ? true : false; // เช็คว่าเป็นโฟลเดอร์ var isFile = _folders![index] is File ? true : false; // เช็คว่าเป็นไฟล์ if(_folders![index] != null){ // เอาเฉพาะชื่อหลัง / ตัวสุดท้าย String fileName = _folders![index]!.path.split('/').last; return Dismissible( key: UniqueKey(), confirmDismiss: (direction) async { return await showDialog( context: context, builder: (context) { return AlertDialog( title: const Text("Confirm"), content: const Text("Are you sure you wish to delete this item?"), actions: <Widget>[ ElevatedButton( onPressed: () => Navigator.of(context).pop(true), child: const Text("DELETE") ), ElevatedButton( onPressed: () => Navigator.of(context).pop(false), child: const Text("CANCEL"), ), ], ); }, ); }, onDismissed: (direction) { // ปัดไปทางขวา - บนลงล่าง if(direction == DismissDirection.startToEnd){ } // ปัดไปซ้าย - ล่างขึ้นบน if(direction == DismissDirection.endToStart){ try{ setState(() { if(isFile){ // ถ้าเป็นไฟล์ ส่ง path ไฟล์ไปลบ _deleteFile(_folders![index]!.path); } if(isFolder){ // ถ้าเป็นโฟลเดอร์ส่ง path โฟลเดอร์ไปลบ _deleteFolder(_folders![index]!.path); } // ต้องลบข้อมูลก่อน แล้วค่อยลบรายการในลิส _folders!.removeAt(index); }); }catch(e){ print(e); } } ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('$index dismissed'))); }, background: Container( color: Colors.green, ), secondaryBackground: Container( color: Colors.red, child: Align( alignment: Alignment.centerRight, child: Padding( padding: EdgeInsets.symmetric(horizontal: 10.0), child: FaIcon(FontAwesomeIcons.trashAlt), ) ) ), child: ListTile( leading: isFolder ? FaIcon(FontAwesomeIcons.solidFolder) : FaIcon(FontAwesomeIcons.file), title: Text('${fileName}'), onTap: (isFolder==true) ? (){ // กรณีเป้นโฟลเดอร์ _setPath(_folders![index]!); // ถ้ากด ให้ทำคำสั่งเปิดโฟลเดอร์ } : (){}, // กรณีเป็นไฟล์ ) ); }else{ return Container(); } }, separatorBuilder: (BuildContext context, int index) => const Divider(height: 1,), ) : const Center(child: Text('No items')), // กรณีไม่มีรายการ ), ], ), drawer: SideMenu(), ); } }
เราเพิ่มส่วนของคำสั่งลบไฟล์ และลบโฟลเดอร์เพิ่มเข้ามา
// คำสังลบไฟล์ void _deleteFile(path) async { final deletefile = File(path); // กำหนด file object final isExits = await deletefile.exists(); // เช็คว่ามีไฟล์หรือไม่ if(isExits){ // ถ้ามีไฟล์ try{ await deletefile.delete(); }catch(e){ print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } // คำสั่งลบโฟลเดอร์ void _deleteFolder(path) async { final deleteFolder = Directory(path); // สร้าง directory object var isExits = await deleteFolder.exists(); // เช็คว่ามีแล้วหรือไม่ if(isExits){ // ถ้ามีโฟลเดอร์ try{ await deleteFolder.delete(recursive: true); }catch(e){ print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); }
จากนั้นก็ใช้งาน Dismissible โดยให้ ListTile เป็น child ในตัวอย่างเรากำหนดคำสั่งการทำงานเฉพาะ
ส่วนของปัดไปทางซ้าย หรือการลบข้อมูล
// ปัดไปซ้าย - ล่างขึ้นบน if(direction == DismissDirection.endToStart){ try{ setState(() { if(isFile){ // ถ้าเป็นไฟล์ ส่ง path ไฟล์ไปลบ _deleteFile(_folders![index]!.path); } if(isFolder){ // ถ้าเป็นโฟลเดอร์ส่ง path โฟลเดอร์ไปลบ _deleteFolder(_folders![index]!.path); } // ต้องลบข้อมูลก่อน แล้วค่อยลบรายการในลิส _folders!.removeAt(index); }); }catch(e){ print(e); } }
สังเกตว่า เราต้องทำการลบข้อมูลก่อน ที่จะลบรายการออกจากลิส เพราะถ้าเราไปเผลอลบลิสก่อน
ข้อมูลในลิสที่จะส่งไปลบ จะถูกดึงออกจาก view ทำให้ไม่มีค่าถูกส่งไปใช้งาน ดังนั้น ก็ให้ไปทำคำสั่ง
ลบก่อน แล้วค่อยดึงออกจากลิสรายการ ตามลำดับ
ดูตัวอย่างการทำงานอีกครั้ง

หวังว่าจะเป็นแนวทางนำไปปรับใช้งานต่อไป เนื้อหาเกี่ยวกับไฟล์ และการใช้งานหรือการประยุกต์
โดยอาศัยเนื้อหานี้ ยังมีอยู่รอติดตามว่าจะเป็นอะไรในบทความต่อๆ ไป
กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ

อ่านต่อที่บทความ
-
05 Dec2021การทำ Selected Item ใน ListView เพื่อจัดการ ใน Flutter อ่าน 3,711
เนื้อหาตอนต่อไปนี้ก็เป็นการประยุกต์รายการใน ListView เป็นวิธีการที่ทำให้เ
เนื้อหาที่เกี่ยวข้อง
-
05 Nov2021การใช้งาน Provider จัดการข้อมูล App State ใน Flutter อ่าน 10,406
เนื้อหาตอนต่อไปนี้เราจะมาดูเกี่ยวกับการใช้งาน provider จ้ดการข้อมูล app s
-
06 Nov2021จัดการข้อมูลด้วย SQL Database โดยใช้ Sqflite ใน Flutter อ่าน 9,456
เนื้อหาตอนต่อไปนี้จะมาแนะนำ การบันทึกข้อมูลไว้ใน app ในรูปแบบ SQLite data
-
08 Nov2021การใช้งาน Form และ Form Validation ใน Flutter อ่าน 11,168
เนื้อหาตอนต่อไปนี้ เราจะมาดูเกี่ยวกับการใช้งาน form ใน flutter เริ่มตั้งแ
-
10 Nov2021การจัดการข้อมูลของ Form Element อื่นๆ ใน Flutter อ่าน 3,412
เนื้อหาต่อไปนี้ จะมาดูต่อเกี่ยวกับการใช้งานฟอร์ม ต่อจาก เนื้อหาตอนที่แล้ว
-
11 Nov2021ประยุกต์ใช้งาน Form บันทึกลงฐานข้อมูล ใน Flutter อ่าน 3,274
เนื้อหานี้จะเป็นเนื้อหาประยุกต์เพิ่มเติม เล็กน้อยเกี่ยวกับการ ปรับใช้งานฟ
-
22 Nov2021ประยุกต์เก็บข้อมูลด้วย shared preferences ใน Flutter อ่าน 4,827
เนื้อหาตอนต่อไปนี้ เราจะมาประยุกต์การใช้งาน plugin ที่ใช้ สำหรับเก็บข้อมู
-
24 Nov2021ประยุกต์ระบบ Register Login ผ่าน API บน server ใน Flutter อ่าน 8,144
เนื้อหาต่อไปนี้เป็นการประยุกต์ใช้งาน ต่อเนื้อง จากเนื้อหาตอนที่แล้ว เกี่ย
-
25 Nov2021ใช้งาน Firebase Authentication จัดการระบบสมาชิกใน Flutter อ่าน 2,515
สำหรับเนื้อหานี้จะเป็นแนวทางการประยุกต์อีกรูปแบบ ของระบบสมาชิก ที่เราไม่จ
-
26 Nov2021ประยุกต์ระบบ Register Login ด้วย Firebase Auth ใน Flutter อ่าน 5,456
ต่อเนื่องจากบทความตอนที่แล้ว ที่เราได้เตรียมพร้อมและ ตั้งค่าส่วนต่างๆ สำห
-
27 Nov2021การเปลี่ยนชื่อ App ชื่อ Package และไอคอน ใน Flutter อ่าน 5,310
เนื้อหาตอนต่อไปนี้จะมาแนะนำวิธีการเปลี่ยนชื่อ app และ ไอคอนของ app
-
28 Nov2021การกำหนด Splash screen ให้กับ app ใน Flutter อ่าน 4,486
Splash screen หรือที่รู้จักใช้ชื่อ Launch screens เป็น ส่วนที่ทำงานเริ่มต
-
29 Nov2021การสร้าง Keystore และการ Build app ใน Flutter อ่าน 7,463
เนื้อหาตอนต่อไปนี้จะมาดูวิธีการสร้าง keystore สำหรับใช้ ในการ build app เ
-
03 Dec2021การใช้งาน Path Provider และการเชียนอ่าน File ใน Flutter อ่าน 4,991
เราได้รู้จักการเก็บข้อมูลแบบถาวรไว้ใน app มาแล้ว ไม่ว่าจะเป็นการใช้งาน ฐา
-
กำลังอ่านเนื้อหานี้อยู่04 Dec2021การใช้งาน Dismissible Widget ร่วมกับ ListView ใน Flutter อ่าน 3,436
เนื้อหานี้เรายังอยู่กับการจัดการไฟล์ และโฟลเดอร์ แต่จะมาเสริมการประยุกต์ก
-
05 Dec2021การทำ Selected Item ใน ListView เพื่อจัดการ ใน Flutter อ่าน 3,711
เนื้อหาตอนต่อไปนี้ก็เป็นการประยุกต์รายการใน ListView เป็นวิธีการที่ทำให้เ
URL สำหรับอ้างอิง
Top
Copy
ขอบคุณทุกการสนับสนุน
![]()