Flutter : รู้จักกับ Sliver Delegate ตอนที่ 1
สวัสดีผู้อ่านทุกท่านครับ บล็อกนี้ผมจะพามาลองเล่นเกี่ยวกับเรื่อง Sliver Delegate ใน Flutter ครับ เป็น class ที่อยู่เบื้องหลังของ ListView/GridView ซึ่งหากเราไม่เข้าใจว่า Sliver delegate คืออะไร ทำงานอย่างไร ก็อาจทำให้เราไม่สามารถใช้งาน Widget ที่ใช้งาน Sliver ได้ ดังนั้นบล็อกนี้มาเล่นกับ Sliver delegate กันเถอะ
Sliver คืออะไร
Sliver คือส่วนหนึ่งของ ScrollViewใน Flutter เป็นตัวอธิบายการทำงานของ ScrollView ซึ่งมันคือ class ที่อยู่เบื้องหลังการทำงานของ ListView กับ GridView ที่เราใช้กันใน Flutter นั่นเอง โดยเบื้องหลังการทำงานของ ListView/GridView ก็จะไปเรียกใช้ Sliver ต่างๆ ดังนั้นหากเราต้องการปรับแต่ง ListView/GridView ให้มีความซับซ้อนเราจำเป็นต้องเข้าใจหลักการทำงานของ Sliver
การใช้ Sliver
อย่างที่บอกในตอนต้นว่า sliver คือส่วนหนึ่งของ ScrollView ดังนั้น เราจะสามารถใช้มันร่วมกับ CustomScrollView ซึ่ง CustomScrollView สามารถเพิ่ม Sliver ได้หลายตัว สังเกตว่ามันรับค่าเป็น list
CustomScrollView( slivers: [ ... ])
Sliver จะแบ่งเป็น 2 ส่วนใหญ่ๆ คือส่วนที่เป็น widget กับส่วนที่เป็น delegate
- Sliver delegate คือ ตัวอธิบายว่า จะใช้วิธีไหนในการแสดงผล โดยปกติจะมี 2 แบบคือ static กับ dynamic (ใน Flutter มักจะเป็นการใช้คำว่า builder แต่ผมขอเรียก dynamic ตามความเข้าใจนะ)
- Sliver Widget คือ การนำ Widget ที่เราเตรียมไว้ใน Sliver delegate มาใช้งานร่วมกับคุณสมบัติอื่นๆของ Widget
SliverList + SliverChildListDelegate
SliverList คือ Sliver Widget ประเภทหนึ่งการทำงานของมันคือ เอา Widget มาเรียงเป็น List โดยการเรียงแนวตั้งหรือแนวนอนอยู่ที่การกำหนดของ CustomScrollView วิธีการใช้ SliverList เราจะต้องกำหนด delegate ให้มันด้วย
ในตัวอย่าง ขอเริ่มจากแบบที่ง่ายที่สุด คือ SliverChildListDelegate
ซึ่ง SliverChildListDelegate มันจะเอา Widget มาต่อกันแบบหยาบๆโดยจะ build ทุก Widget ลงไปใน SliverList มี 100 widget ก็สร้างทั้งหมดแล้วใส่ลงไปใน List
class MyHomePage extends StatelessWidget{ Widget build(BuildContext context) { return Scaffold( body: Container( padding: EdgeInsets.only(top: 24), child: CustomScrollView(slivers: [ SliverList( delegate: SliverChildListDelegate([ Container(width: 50, height: 50, color: Colors.blue[100]), Container(width: 50, height: 50, color: Colors.blue[200]), Container(width: 50, height: 50, color: Colors.blue[300]), Container(width: 50, height: 50, color: Colors.blue[400]), Container(width: 50, height: 50, color: Colors.blue[500]), ])), ]))); } }
ListView
ซึ่งการใช้ SliverList + SliverChildListDelegate ตัว Flutter ก็ทำมาให้ใช้ง่ายๆ คือ ListView
สรุปก็คือ ListView = CustomScrollView + SliverList + SliverChildListDelegate
ListView(children: <Widget>[ Container(width: 50, height: 50, color: Colors.blue[100]), Container(width: 50, height: 50, color: Colors.blue[200]), Container(width: 50, height: 50, color: Colors.blue[300]), Container(width: 50, height: 50, color: Colors.blue[400]), Container(width: 50, height: 50, color: Colors.blue[500]), ])
SliverList + SliverChildBuilderDelegate
ยังคงอยู่กับ SliverList แต่ทีนี้ ลองมาใช้ delegate อีกตัว คือ SliverChildBuilderDelegate ซึ่ง delegate ตัวนี้ มันคือการทำให้ item ใน Scroll มีความ Dynamic หรือก็คือตัว Widget แต่ละอันสามารถ recycle ได้ โดยจะสร้างเฉพาะ Widget ที่แสดงผลบนหน้าจอเท่านั้น ทำให้ไม่เปลือง memory ในกรณีที่มีข้อมูลมากๆ
เช่น
SliverList( delegate: SliverChildBuilderDelegate((context, index) { return Container(height: 50, color: Colors.green[index*100], child: Center(child: Text("$index"))); }, childCount: 9), ),
ListView.builder
ซึ่งการใช้ SliverList + SliverChildBuilderDelegate ตัว Flutter ก็ทำมาให้ใช้ง่ายๆโดยใช้ผ่าน ListView.builder นั่นเอง คนละอันกับ ListView() ก่อนหน้านี้นะ
ListView.builder = CustomScrollView + SliverList + SliverChildBuilderDelegate
ListView.builder(itemBuilder: (context, index) { return Container( color: Colors.purple[index * 100], child: Center(child: Text("$index"))); });
SliverGrid
มาถึงเรื่อง Grid กันบ้าง ซึ่งหากเราต้องการให้ Widget เรียงกันแบบตารางคล้าย Grid จะต้องใช้ SliverGrid
ซึ่ง SliverGrid จะมี delegate ต้องกำหนด 2 ตัว คือ delegate ปกติกับ gridDelegate
- sliver delegate ก็คือ static หรือ dynamic แปลว่าให้เลือกเอาระหว่าง SliverChildListDelegate กับ SliverChildBuilderDelegate
- grid delegate คือ อธิบายว่าจะให้แสดงผลอย่างไร เช่น แถวละ 3 คอลัม หรือ คอลัมนึง 150px เท่านั้น
SliverGrid + SliverChildListDelegate
มาลองแบบแรก SliverGrid ใช้ delegate แบบ static (SliverChildListDelegate)
ทีนี้ก็ต้องมาเลือก gridDelegate มาดูว่ามีแบบไหนกันบ้าง
แบบกำหนดจำนวนแนวหลักสูงสุด
จะใช้ gridDelegate คือ SliverGridDelegateWithFixedCrossAxisCount โดยจะสามารถกำหนด ว่า 1 แถวมีได้สูงสุดกี่คอลัม
และมันจะขยายความกว้างให้เต็มอัตโนมัติ
crossAxisCount คือ จำนวนคอลัมภ์
childAspectRatio คือ อัตราส่วนของความกว้างแต่ความสูง
เช่น กำหนดว่า แถวนึงมีได้สูงสุด 4 อัน แต่ละอันความกว้างจะเป็น 1.5 เท่าของความสูง
SliverGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, childAspectRatio: 1.5), delegate: SliverChildListDelegate([ Container(color: Colors.red[100]), Container(color: Colors.red[200]), Container(color: Colors.red[300]), Container(color: Colors.red[400]), Container(color: Colors.red[500]), Container(color: Colors.red[600]), ])),
หรือจะใช้คำสั่งย่อก็ได้ คือ SliverGrid.count
ก็คือ SliverGrid.count = SliverGrid + SliverChildListDelegate + SliverGridDelegateWithFixedCrossAxisCount
SliverGrid.count(crossAxisCount: 4, childAspectRatio: 1.5,children: <Widget>[ Container(color: Colors.red[100]), Container(color: Colors.red[200]), Container(color: Colors.red[300]), Container(color: Colors.red[400]), Container(color: Colors.red[500]) ])
แบบกำหนดความกว้างสูงสุด
GridDelegate อีกตัวคือ SliverGridDelegateWithMaxCrossAxisExtent มันคือตัวที่เราสามารถความกว้างให้กับคอลัมภ์ได้
maxCrossAxisExtent คือ ความกว้างสูงสุดในคอลัมภ์
childAspectRatio คือ อัตราส่วนความสูงต่อความกว้าง
เช่น กำหนดว่า แต่ละอันมีความกว้างสูงสุด 200px โดยกว้างจะเป็น 1.5 เท่าของความสูง
SliverGrid( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200, childAspectRatio: 1.5), delegate: SliverChildListDelegate([ Container(color: Colors.red[100]), Container(color: Colors.red[200]), Container(color: Colors.red[300]), Container(color: Colors.red[400]), Container(color: Colors.red[500]), Container(color: Colors.red[600]), ])),
ถ้าใช้ยาก ก็สามารถใช้แบบย่อได้ ด้วยคำสั่ง SliverGrid.extent
ก็คือ SliverGrid.extent = SliverGrid + SliverChildListDelegate + SliverGridDelegateWithMaxCrossAxisExtent
SliverGrid.extent(maxCrossAxisExtent: 200, childAspectRatio: 1.5,children: <Widget>[ Container(color: Colors.red[100]), Container(color: Colors.red[200]), Container(color: Colors.red[300]), Container(color: Colors.red[400]), Container(color: Colors.red[500]) ])
GridView
GridView คือ Widget ที่นำ GridSliver มาใช้กับ CustomScrollView ให้เราใช้งานง่ายๆ จะเรียกว่าเป็นรูปย่อก็ได้
เช่น โดย gridView จะแค่ใส่ children แล้วก็กำหนด gridDelegate เท่านั้น
GridView(children: <Widget>[ ... ], gridDelegate: ...)
GridView = CustomScrollView + SliverGrid + SliverChildListDelegate + [ gridDelegate ? ]
GridView.count
GrideView.countคือ รูปย่อของ CustomScrollView + SliverGrid + SliverChildListDelegate + SliverGridDelegateWithFixedCrossAxisCount เรียกว่าเป็น GridView ที่ระบุชัดเจนว่าใช้ gridDelegate แบบกำหนดจำนวนคอลัมภ์
GridView.count(crossAxisCount: 4 , children: [ ... ])
GridView.extent = CustomScrollView + SliverGrid + SliverChildListDelegate + SliverGridDelegateWithFixedCrossAxisCount
GrideView.extent
GrideView.extent คือ รูปย่อของ CustomScrollView + SliverGrid + SliverChildListDelegate + SliverGridDelegateWithMaxCrossAxisExtent เรียกว่าเป็น GridView ที่ระบุชัดเจนว่าใช้ gridDelegate แบบกำหนดความกว้างนั่นเอง
GridView.extent = CustomScrollView + SliverGrid + SliverChildListDelegate + SliverGridDelegateWithMaxCrossAxisExtent
GridView.extent(maxCrossAxisExtent: 100, children: <Widget>[ ... ])
SliverGrid + SliverChildBuilderDelegate
ลองเปลี่ยน delegate มาใช้แบบ dynamic กัน จริงๆก็แค่เปลี่ยน delegate มาใช้ SliverChildBuilderDelegate แทน SliverChildListDelegate แค่นี้ Grid ของเราก็จะทำงานแบบ dynamic แล้วละ ส่วน gridDelegate ก็เลือกเอาว่า จะกำหนดแบบไหนระหว่าง จากจำนวนคอลัม หรือความกว้าง
ตัวอย่างนี้ กำหนดให้ Grid มี 10 อัน และแสดงผลแบบกำหนดคอมลัมภ์ ให้มีคอลัมสูงสุด 5 คอลัมภ์ โดยใช้ delegate แบบ dynamic
SliverGrid( delegate: SliverChildBuilderDelegate((context, index) { return Container( color: Colors.purple[index*100], child: Center(child: Text("$index"))); }, childCount: 10), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 5,mainAxisSpacing: 2,crossAxisSpacing: 2))
GridView.builder
GridView.builder คือรูปย่อของ GridView ที่ใช้ delegate แบบ dynamic (SliverChildBuilderDelegate) ซึ่งก็จะต้องกำหนด gridDelegate ว่าจะใช้แบบกำหนดจำนวนคอลัมภ์หรือ กำหนดความกว้าง
GridView.builder(gridDelegate: ..., itemBuilder: (context ,index){ return ... })
GridView.builder = CustomScrollView + SliverGrid + SliverChildBuilderDelegate + [ gridDelegate ? ]
GridView.custom
GridView.custom คือรูปย่อของ SliverGrid ที่ต้องกำหนดทั้ง delegate และ gridDelegate
ดังนั้น การจะใช้คำสั่งนี้ได้จึงต้องเข้าใจ ว่า delegate และ gridDelegate คืออะไรนั่นเอง
GridView.custom(childrenDelegate: ... ,gridDelegate: ... );
GridView.builder = CustomScrollView + SliverGrid + [ delegate ? ] + [ gridDelegate ? ]
สรุป
ในบล็อกนี้ ผู้อ่านน่าจะได้เรียนรู้เกี่ยวกับ Sliver Delegate แบบต่างๆที่ใช้ใน Flutter ซึ่งเป็นพื้นฐานของการทำ ListView , GridView และการใช้รูปย่อต่างๆ รวมไปถึงที่มา หลักการทำงานของ Sliver ด้วย
ตารางสรุป Sliver
gridDelegate
|
Sliver Delegate | |||
SliverChildListDelegate | SliverChildBuilderDelegate | undefine | ||
SliverList | – | ListView | ListView.builder | – |
SiverGrid
|
SliverGridDelegateWithFixedCrossAxisCount | GridView.count | GridView.builder | – |
SliverGridDelegateWithMaxCrossAxisExtent | GridView.extent | GridView.builder | – | |
undefine | GridView | GridView.builder | GridView.custom |
โปรดติดตามตอนต่อไป
ในตอนหน้าจะพาไปลองเล่น Sliver แบบอื่นๆนอกจาก SliverList กับ SliverGrid ครับ