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













ต่อจากตอนที่แล้ว ที่เราได้ใช้งาน Package จากภายนอก
สำหรับสร้าง random word มาใช้งานในโปรเจ็ค App และ
จำนำมาต่อยอดในเนื้อหาของตอนนี้ ซึ่งเป็นการใช้งาน ListView
ดังนั้น ก่อนที่จะเข้าสู่เนื้อหาการประยุกต์ใช้งาน เราจะทำความ
รู้จักกับ ListView Widget เบื้องต้นกันก่อน ว่ามีรูปแบบการกำหนด
และการใช้งานอย่างไร
ทบทวนเนื้อหาตอนที่แล้วได้ที่บทความด้านล่าง
การติดตั้งและใช้งาน Package ภายนอก ใน Flutter เบื้องต้น http://niik.in/956
https://www.ninenik.com/content.php?arti_id=956 via @ninenik
การใช้งาน ListView Widget
เป็น widget ที่ใช้ในการสร้างลิสรายการที่สามารถเลือนได้ โดยเรียงต่อกันเป็นแนว สามารถกำหนดลิสรายการเป็น widget ต่างๆ
ส่วนใหญ่แล้วเราจะพบเห็นใช้บ่อยในการสร้างเป็นลิสรายการข้อความ widget ยอ่ยหรือลิสรายการแต่ละรายการจะเรียงต่อหลังกันไป
เรื่อยๆ ตามทิศทางการเลื่อน scroll ซึ่งเป็นได้ทั้งในแนวตั้งและแนวนอน ขึ้นอยู่กับการกำหนด
สามารถสร้าง ListView ได้จากวิธีต่างๆ 4 วิธี ดังนี้
1. การใช้งาน ListView()
ใช้ default constructor เป็น ListView() แล้วกำหนด children เป็น List<Widget> หรืออาเรย์ widget ต่างๆ ที่ต้องการ ซึ่ง
โดยทั่วไปจะใช้เป็น ListTile ที่จะมีรูปแบบที่สามารถกำหนด ส่วนของ leading, title, subtitle และ trailing เหล่านี้เป็นต้น
ในตัวอย่างด้านล่าง กำหนดโดยใช้ ListTile สามรายการแรก และรายการที่ 6 ส่วนรายการที่ 4 และ 5 กำหนดโดยใช้ Text และ Icon
widget ตามลำดับ

ผลลัพธ์ที่ได้

การใช้งาน ListView รูปแบบนี้ เหมาะสำหรับรายการที่มีจำนวนไม่มาก เช่น 4 - 10 รายการหรือไม่ควรเกินขอบเขต ที่สามารถมอง
เห็นได้ เช่นมีจำนวนเกินความสูงของหน้าจอทำให้มีบางส่วนถูกซ่อนไป และอีกสาเหตุที่ไม่ควรมีจำนวนมาก เพราะว่า เราจะต้องจัดการ
แต่ละ ลิสรายการ เช่นกำหนดการเรียกใช้คำสั่ง เมื่อเกิด onTap หรือแตะที่รายการนั้น โดยจะทำได้กับรายการที่สามารถมองเห็นได้เท่านั้น
2. การใช้งาน ListView.builder()
ใช้ named constructor เป็น ListView.builder() แล้ว child ลิสรายการ ให้กับ itemBuilder ด้วยการเรียกใช้งานฟังก์ชั่น IndexedWidgetBuilder()
เมื่อเลื่อน scroll เพื่อแสดงรายการโดยพิจารณาจากค่า index ซึ่งลิสรายการจะไม่ถูกเรียกมาแสดงทั้งหมด
เมื่อเลื่อน scroll เพื่อแสดงรายการโดยพิจารณาจากค่า index ซึ่งลิสรายการจะไม่ถูกเรียกมาแสดงทั้งหมด
ในครั้งเดียว เหมือนกับการใช้งานในวิธีแรก แต่จะแสดงเฉพาะบางส่วนให้เต็มพื้นที่ที่มองเห็น เช่น หน้าจอแสดงได้ 10 รายการ ก็อาจจะแสดง
มาสัก 15-20 กว่ารายการ เป็นต้น และเมื่อทำการเลื่อน scroll ลงไปเพื่อแสดงรายการที่เหลือ ฟังก์ชั่น IndexedWidgetBuilder() ก็จะทำการเพิ่มรายการ
เข้ามาเรื่อยๆ ให้เต็มพื้นที่ที่สามารถมองเห็นได้ จนกว่ารายการจะแสดงครบ
เข้ามาเรื่อยๆ ให้เต็มพื้นที่ที่สามารถมองเห็นได้ จนกว่ารายการจะแสดงครบ
หรือบางทีก็เป็นรายการแบบ infinite ที่เพิ่มรายการได้ไม่สิ้นสุด ซึ่งวิธีที่สองนี้เหมาะกับการใช้งานการแสดงลิสรายการจำนวนมากๆ
สมมติเราจำลองสร้าง รายการทั้งหมด 1000 รายการโดย generate ค่าไว้ในตัวแปร items ด้วยคำสั่ง
final items = List<String>.generate(10000, (i) => "Item $i");
จากนั้นเรียกใช้งาน ListView.builder() จะได้เป็นดังนี้

ในบรรทัดที่ 31 itemBuilder จะสร้างรายการโดยเรียกใช้งานฟังก์ชั่น IndexedWidgetBuilder() สังเกตจะเห็นว่าฟังก์ชั่นนี้ ไม่มีการ
กำหนดชื่อฟังก์ชั่น เราเรียกว่า Typedef ในภาษา Dart เหมือนเป็นฟังก์ชั่นต้นแบบหรือ prototype โดยตัวฟังก์ชั่น IndexedWidgetBuilder
นี้จะวนลูป List หรืออาเรย์ของ context (ในที่นี้คือตัวแปร items ที่เราทำการสร้างรายการสมมติมา 1000 รายการ) แสดงรายการให้เต็ม
พื้นที่ที่สามารถมองเห็น และเมื่อเลื่อน scroll ลงไปเพื่อแสดงรายกาเพิ่มเติม ก็จะทำการวนลูปแสดงรายการจาก index ที่เหลือต่อไปเรื่อยๆ
บรรทัดที่ 30 อย่าลืมกำหนด itemCount ไม่อย่างงั้นจะเกิด Range error นั่นคือไปวนลูปเพิ่มรายการเกินขอบเขตหรือเกินจำนวน
ที่มีจริง ในที่นี้คือระบุ items.length
ผลลัพธ์ที่ได้
จะเห็นว่าเมื่อแสดงครั้งแรก จะแสดงลิสรายการที่ index 0 -10 และพอเราเลื่อนลงไป ก็จะแสดงรายการเพิ่มเข้ามาเรือยๆ ตามค่า index
ที่เปลี่ยนแปลง
3. การใช้งาน ListView.separated()
ใช้ named constructor เป็น ListView.separated() รูปแบบวิธีการนี้ จะเหมือนกับวิธีที่สอง แต่ที่เพิ่มเข้ามา คือมีส่วนของตัวที่กำหนด
ตัวแบ่งคั่นเพิ่มเข้ามา นั่นคือในวิธีที่ 2 มีเฉพาะ itemBuilder ส่วนวิธีที่ 3 จะมี separatorBuilder ที่ทำงานคล้ายกันเพิ่มเข้ามา เข้าใจอย่าง
ง่าย คือเมื่อวนลูปสร้างรายการใหม่แต่ละครั้ง ก็จะวนลูปสร้างตัวแบ่งเพิ่มเข้ามาด้วย ซึ่งตัวแบ่ง เราอาจจะใช้เป็น Divider widget ที่เป็นเส้น
คั่น 1px หรือจะใช้เป็น ลิสแบ่งหัวข้อรายการลิสอีกทีก็ได้ ขึ้นอยู่กับการประยุกต์ใช้งาน
วิธีการนี้ จริงๆ แล้วเราสามารถใช้วิธีที่ 2 แล้วสร้างเงือนไข ในการสร้าง wdiget ได้ โดยพิจารณาใช้จากค่า index ที่เริ่มต้นจาก 0 เสมอ
แล้วเพิ่มค่าไปเรื่อยๆ เช่น index (0) แรกเป็น ลิสรายการ ข้อความ index (1) ถัดไปเป็นลิสรายการ ตัวแบ่ง และเมื่อลำดับ index เพิ่มข้ึน
ในรูปแบบ 0, 1, 2, 3, 4, ..... นั่นคือที่ตำแหน่ง 1, 3 ซึ่งเป็นเลขคี่ เราก็ใช้เป็นเงื่อนไขตัวแบ่งได้ดังนี้

ในบรรทัดที่ 32 เราเพิ่ม if (index.isOdd) return Divider(); เข้าไป เพื่อให้สร้าง Divider wdiget เข้าไปแทนใน index ที่เป็นเลขคี่
แต่ผลที่ตามมาคือ เราใช้การนับจำนวน itemCount จากจำนวนของข้อมูล ดังนั้นจำนวนข้อมูลที่แสดงจริงจะไม่ครบ ดูตัวอย่าง เรากำหนด
ลิสรายการ 5 รายการ ซึ่งจะมี index เป็น 0,1,2,3,4 และตำแหน่งที่ 1,3 เป็นของ Divider ทำให้ข้อมูลแสดงแค่ตำแหน่งที่ 0,2 และ 4
ซึ่งข้อมูลเราควรมี 5 รายการ ดูผลลัพธ์ ดังรูป ที่แสดงรายการไม่ครบ เพราะมีตัวแบ่งเพิ่มเข้ามา

วิธีแก้ปัญหาที่เหมาะสม คือ สร้างฟังก์ชั่นที่ทำการ return ค่า Widget ที่มีการจัดรูปแบบแล้วกลับออกมาเเป็น context แทนการกำหนด
การใช้งาน widget เดียว นั้นคือ เราสร้างฟังก์ชั่น ที่รวมเอา ListTile และ Divider รวมกัน แล้ว return ออกมาเป็น Widget เดียว ดังนี้

บรรทัดที่ 32 แทนที่เราจะ return ListTile หรือ Divider ตามเงื่อนไข index อย่างใดอย่างหนึ่ง เราก็สามารถเลือกที่จะ return ทั้งสอง
ออกมาพร้อมกันโดย จากการใช้ฟังก์ชั่น _buildRow() โดยส่ง context และ index เป็น paramter เข้าไปใช้งานอีกที บรรทัดที่ 43 - 55
เป็นส่วนที่เราสร้าง Widget ใหม่ โดยวิธีที้ เราสามารถกำหนดรูปแบบหรือจัดเรียง widget ได้ตามต้องการ แต่ถ้าเราต้องการใช้งาน ListTile
ซึ่งทั่วไปแล้วมักกำหนดใช้งานกับ ListView และจะสามารถกำหนดได้ในบาง widget เช่น Column , Drawer และ Card เป็นต้น
ผลลัพธ์ที่ได้

และอีกวิธี ก็คือใช้วิธีที 3 แทนดังนี้

วิธีการนี้ เราใช้ตัวแบ่ง โดยเรียกฟังก์ชั่น IndexedWidgetBuilder แยก จึงไม่มีผลกับการแสดงข้อมูล เพราะใช้ index จาก Context
คนละตัวกัน เราสามารถใช้รูปแบบ Fat Arrow ให้กับฟังก์ชั่น ที่มีการทำงานแค่บรรทัดเดียว สามารถใช้เป็น
separatorBuilder: (context, index) => const Divider(),
ผลลัพธ์ที่ได้

4. การใช้งาน ListView.custom()
ใช้ named constructor เป็น ListView.custom() จะมีการใช้งาน SliverChildDelegate class เป็นตัวแทนสร้าง ลิสรายการให้กับ
childrenDelegate โดยสามารถกำหนดการจัดการหรือรูปแบบเพิ่มเติมให้กับ child ได้ เช่น SliverChildDelegate สามารถประมาณการ
ขนาดของ child ที่ยังไม่ได้แสดงได้ ส่วนนี้จำเป็นต้องเข้าใจส่วนอื่นเพิ่มเติม ดังนั้นขอ ข้ามรายละเอียดไปก่อน อาจจะนำมาอธิบายเพิ่มเติม
ภายหลังหากมีเนื้อหาที่สัมพันธ์กับการใช้งาน
นอกจากแนวทางการสร้าง ListView แล้ว เรายังจัดรูปแบบการใช้งานเพิ่มเติมได้ เช่น การแสดงในแนวนอนดังนี้

ผลลัพธ์ที่ได้
การปรับแต่ง และกำหนด property ต่างเพิ่มเติม สามารถดูได้ที่ ListView Widget API
เมื่อเราพอเข้าใจแนวทางการใช้งานเบื้องต้นเกี่ยวกับ ListView แล้ว เราจะกลับมาที่เนื้อหาจากตอนที่แล้วของเรา และจะมาประยุกต์
การใช้งาน ดังนี้คือ ส่วนของ _FirstScreen state class จากตอนที่แล้ว ในไฟล์ first_screen.dart เป็นดังนี้
class _FirstScreen extends State<FirstScreen>{ String _randomWord = WordPair.random().asPascalCase; final _biggerFont = const TextStyle(color: Colors.black, fontSize: 20.0); void _generateWord(){ setState(() { _randomWord = WordPair.random().asPascalCase; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Welcome to Flutter'), backgroundColor: Colors.green ), body: ListTile( title: Text( _randomWord, style: _biggerFont, ), ), floatingActionButton: FloatingActionButton( backgroundColor: Colors.green, onPressed: _generateWorld, child: Icon(Icons.add), ), ); } }
จากโค้ดข้างต้น เดิมเราใช้งานตัวแปร _randomWord เป็น final String ข้อความที่ได้จากการ Random โดยการใช้งาน "english_word"
package และใช้ปุ่ม "Renew" สำหรับแสดงข้อความใหม่ที่ได้จากการ Random
ประยุกต์เพิ่มรายการใน ListView
ในเนื้อหาตอนนี้ เราจะให้ตัวแปร _randomWord เป็น List รายการ ของ WordPair หรือก็คือ List<WordPair> เพื่อเก็บรายการ Random
ทั้งหมด โดยกำหนดโดยใช้ var แทน final เนื่องจากว่า ค่าจะต้องมีการเปลี่ยนแปลงได้ แล้วเปลี่ยนปุ่ม เป็นปุ่ม "Add New" โดยทำการเพิ่มรายการจากที่ได้ทำการ Random มาทีละ 3 รายการ เข้าไปใน List จากนั้นนำไป แสดงใน ListView โดยเรียกใช้งานฟังก์ชั่น _buildRow
เพื่อสร้างลิสรายการสำหรับ ListView อีกที นอกจากทำการเพิ่มรายการได้แล้ว เรายังเพิ่ม action เป็น IconButton เขาไปใน AppBar
เป็นปุ่มสำหรับล้างค่ารายการ _randomWord ทั้งหมด เพื่อเริ่มการเพิ่มรายการใหม่
สรูปก็คือ เราจะได้ฟังก์ชั่น ทั้งหมด 3 ฟังก์ชั่น สำหรับเรียรกใช้งาน คือ ฟังก์ชั่นเพิ่มรายการทีละ 3 รายการชื่อว่า _addRandomWord
ต่อด้วยฟังก์ชั่น ล้างค่ารายการทั้งหมด ชื่อว่า _clearRandomWord และสุดท้ายฟังก์ชั่นสำหรับสร้าง widget จากตัวแปร _randomWord
เพื่อเพิ่มเข้าไปใน ListView ชื่อฟังก์ชั่นว่า _buildRow
จะได้ไฟล์ first_screen.dart เป็นดังนี้
import 'package:flutter/material.dart'; import 'package:english_words/english_words.dart'; // ส่วนของ Stateful widget class FirstScreen extends StatefulWidget{ @override State<StatefulWidget> createState() { return _FirstScreen(); } } class _FirstScreen extends State<FirstScreen>{ var _randomWord = <WordPair>[]; final _biggerFont = const TextStyle(color: Colors.black, fontSize: 20.0); void _addRandomWord(){ setState(() { _randomWord.addAll(generateWordPairs().take(3)); }); } void _clearRandomWord(){ setState(() { _randomWord.clear(); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Welcome to Flutter'), backgroundColor: Colors.green, actions: <Widget>[ IconButton( icon: const Icon(Icons.clear_all), tooltip: 'Clear List', onPressed: _clearRandomWord, ), ], ), body: Container( child: ListView.builder( itemCount: _randomWord.length, itemBuilder: (context, index) { return _buildRow(_randomWord, index); }, ), ), floatingActionButton: FloatingActionButton( backgroundColor: Colors.green, onPressed: _addRandomWord, child: Icon(Icons.add), ), ); } Widget _buildRow(randomWord, index) { return Container( child: Column( children: <Widget>[ ListTile( title: Text('${randomWord[index].asPascalCase}'), onTap: (){}, ), const Divider(), ], ), ); } }
ดูตัวอย่างผลลัพธ์ที่ได้ ตามลำดับดังนี้
1. เมื่อเปิด App เริ่มต้นขึ้นมา จะไม่มีรายการใดๆ แสดง เพราะตัวแปร _randomWord ยังไม่มีข้อมูลใดๆ เป็นค่าว่าง
2. เมื่อเราคลิกที่ปุ่ม "Add New" ก็จะเพิ่มรายการเข้าไปใน _randomWord ทั้งหมด 3 รายการ เกิดการ setState และทำการ
สร้างลิสรายการ 3 รายการขึ้นมา
3. กดเพิ่มอีกครั้งเป็น 6 รายการ (สามารถกดเพิ่มไปได้เรื่อยๆ ในที่นี้จะสมมติกดไป แค่ 2 ครั้ง)
4. ทำการล้างค่า 6 รายการที่ได้เพิ่มไป โดยกดที่ปุ่ม "Clear All" ที่ปุ่ม action ตรง AppBar
เนื้อหาในตอนนี้เราได้แนวทางการใช้งาน ListView ไปพอสมควร ตอนหน้า เรายังจะประยุกต์ต่อการใช้งาน "english_word" package
ร่วมกับการใช้งาน ListView โดยจะเพิ่มเติมเนื้อหาการจัดการรายการในหน้า Screen ใหม่ รอติดตาม
เพิ่มเติมเนื้อหา ครั้งที่ 1 วันที่ 16-10-2021
การจัดการกรณีเงื่อนไขไม่มีรายการ
เราสามารถกำหนดรูปแบบการจัดการเงื่อนไขกรณีไม่มีรายการโดยใช้การตรวจสอบจำนวน
ของรายการว่ามากกว่า 0 หรือไม่ ถ้ามากกว่า 0 ก็เข้าเงื่อนไขการทำงานปกติ แต่ถ้าเท่ากับ 0
ก็ให้ทำอีกเงื่อนไข ตัวอย่างด้านล่าง กรณีเท่ากับ 0 หรือเป็นรายการว่าง ก็จะให้แสดงข้อความ
ตรงกลางคำว่า "No items" เป็นรุปแบบอย่างง่าย
body: Container( child: items.length > 0 // กำหนดเงื่อนไขตรงนี้ ? ListView.separated( // กรณีมีรายการ แสดงปกติ itemCount: items.length, itemBuilder: (context, index) { return ListTile( title: Text('${items[index]}'), ); }, separatorBuilder: (BuildContext context, int index) => const Divider(), ) : const Center(child: Text('No items')), // กรณีไม่มีรายการ
ถ้ามองจากรูปแบบการใช้งาน ก็จะเป็นในรูปแบบ (condition) ? true : false
กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ

อ่านต่อที่บทความ
-
12 Dec2019การใช้งาน Navigator และ Routing ใน Flutter เบื้องต้น อ่าน 17,690
ก่อนที่เราจะเข้าสู่เนื้อหาการประยุกต์ต่อจากตอนที่แล้ว เนื่้องจาก สิ่งที่เ
เนื้อหาที่เกี่ยวข้อง
-
02 Sep2019เริ่มต้นใช้งาน Flutter พัฒนา Nativiely Compiled Apps เบื้องต้น อ่าน 10,023
เราสามารถทำความรู้จัก หรือค้นหาว่า Flutter ที่่เรากำลังจะศึกษา ว่าคืออะไร
-
01 Dec2019ทดสอบรันโปรเจ็ค Demo ของ Flutter ใน Android Studio อ่าน 5,891
หลังจากเราได้เตรียมความพร้อมสำหรับพัฒนา แอปด้วย Flutter ทั้งการเตรียมเครื
-
02 Dec2019เริ่มต้นสร้าง App แรกด้วย Flutter กับความเข้าใจเบื้องต้น อ่าน 8,995
ตอนที่แล้วเราได้รู้จักโครงสร้าง ของโปรเจ็ค flutter ที่เป็น Demo ไปแ
-
04 Dec2019การใช้งาน Stateless และ Stateful Widget ใน Flutter เบื้องต้น อ่าน 26,243
เนื้อหาตอนที่แล้ว เราได้ทำการสร้าง App เริ่มต้น โดยใช้งาน widget มาประกอบ
-
06 Dec2019แนะนำ Basic Widget ที่ควรรู้สำหรับใช้งาน Flutter เบื้องต้น อ่าน 20,989
เนื้อหาตอนที่แล้ว เราได้ทำความเข้าใจเบื้องต้นเกี่ยวกับ การใช้งาน Stateles
-
07 Dec2019การติดตั้งและใช้งาน Package ภายนอก ใน Flutter เบื้องต้น อ่าน 9,964
เนื้อหาตอนต่อไปนี้ เราจะมาดูเกี่ยวกับการติดตั้ง Package จาก ภายนอก เพื่อม
-
กำลังอ่านเนื้อหานี้อยู่09 Dec2019การกำหนด และใช้งาน ListView Widget ใน Flutter เบื้องต้น อ่าน 15,585
ต่อจากตอนที่แล้ว ที่เราได้ใช้งาน Package จากภายนอก สำหรับสร้าง random wor
-
12 Dec2019การใช้งาน Navigator และ Routing ใน Flutter เบื้องต้น อ่าน 17,690
ก่อนที่เราจะเข้าสู่เนื้อหาการประยุกต์ต่อจากตอนที่แล้ว เนื่้องจาก สิ่งที่เ
URL สำหรับอ้างอิง
Top
Copy
ขอบคุณทุกการสนับสนุน
![]()