เนื้อหาตอนต่อไปนี้ จะมาดูเกี่ยวกับ widget เล็กๆ ที่จะมาช่วย
ให้การแสดงผลใน ListView มีความหลากหลายขึ้น โดยจะใช้เนื้อหา
จากตอนที่แล้ว และปรับแต่งเล็กน้อย
การใช้งาน Http ดึงข้อมูลจาก Server มาแสดงใน Flutter http://niik.in/1038
https://www.ninenik.com/content.php?arti_id=1038 via @ninenik
*เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/961
การใช้งาน Card Widget
card widget เป็นส่วนหนึ่ง material widget ที่มีลักษณะเป็นแผ่นการ์ดมีความโค้งมนเล็ก
น้อยตรงมุม และมีเงาที่เกิดจากการยกสูงดูเป็นมิติ เหมือนเป็นกรอบสำหรับทำให้เนื้อหาด้านใน
ดูมีมิติเพิ่มขึ้น เราสามารถใช้ card widget ครอบส่วนของ ListTile หรือ widget อื่นๆ ได้
ในที่นี้เราจะมาประยุกต์ใช้งาน card ใน ListView จากเนื้อหาตอนที่แล้ว จะขอยกมาเฉพาะ
บางส่วนของโค้ด
รูปแบบการใช้งาน Card Widget
Card( child: Text('ข้อความใน card widget'), );
ไฟล์ home.dart บางส่วน
class _HomeState extends State<Home> { // กำนหดตัวแปรข้อมูล articles late Future<List<Article>> articles; @override void initState() { print("initState"); // สำหรับทดสอบ super.initState(); articles = fetchArticle(); } void _refreshData(){ setState(() { print("setState"); // สำหรับทดสอบ articles = fetchArticle(); }); } @override Widget build(BuildContext context) { print("build"); // สำหรับทดสอบ return Scaffold( appBar: AppBar( title: Text('Home'), ), body: Center( child: FutureBuilder<List<Article>>( // ชนิดของข้อมูล future: articles, // ข้อมูล Future builder: (context, snapshot) { print("builder"); // สำหรับทดสอบ print(snapshot.connectionState); // สำหรับทดสอบ // กรณีสถานะเป็น waiting ยังไม่มีข้อมูล แสดงตัว loading if (snapshot.connectionState == ConnectionState.waiting){ return const CircularProgressIndicator(); } if (snapshot.hasData) {// กรณีมีข้อมูล return Column( children: [ Container( // สร้างส่วน header ของลิสรายการ padding: const EdgeInsets.all(5.0), decoration: BoxDecoration( color: Colors.teal.withAlpha(100), ), child: Row( children: [ Text('Total ${snapshot.data!.length} items'), // แสดงจำนวนรายการ ], ), ), Expanded( // ส่วนของลิสรายการ child: snapshot.data!.length > 0 // กำหนดเงื่อนไขตรงนี้ ? ListView.separated( // กรณีมีรายการ แสดงปกติ itemCount: snapshot.data!.length, itemBuilder: (context, index) { return ListTile( title: Text(snapshot.data![index].title), ); }, separatorBuilder: (BuildContext context, int index) => const Divider(), ) : const Center(child: Text('No items')), // กรณีไม่มีรายการ ), ], ); } else if (snapshot.hasError) { // กรณี error return Text('${snapshot.error}'); } // กรณีสถานะเป็น waiting ยังไม่มีข้อมูล แสดงตัว loading return const CircularProgressIndicator(); }, ), ), floatingActionButton: FloatingActionButton( // ปุ่มทดสอบสำหรับดึงข้อมูลซ้ำ onPressed: _refreshData, child: const Icon(Icons.refresh), ), ); } }
ดูในส่วนของการอ้างอิงข้อมูล snapshot เพิ่มดึงหัวข้อมาแสดง เราใช้รูปแบบ
snapshot.data![index].title
ชนิดข้อมูล Future ที่เราได้มาก็คือ List<Article> ดังนั้น ค่า snapshot.data จึงเป็นข้อมูล List
หรืออาเรย์ มีการเรียกใช้งานแต่ละค่าผ่าน index หรือ key ของอาเรย์ เพื่อสะดวกในการเรียกใช้งาน
เราจะกำหนดค่าเป็นดังนี้
Article article = snapshot.data![index];
ดูเฉพาะในส่วนของ Expanded widget เราปรับใหม่เป็นดังนี้
Expanded( // ส่วนของลิสรายการ child: snapshot.data!.length > 0 // กำหนดเงื่อนไขตรงนี้ ? ListView.separated( // กรณีมีรายการ แสดงปกติ itemCount: snapshot.data!.length, itemBuilder: (context, index) { Article article = snapshot.data![index]; return ListTile( title: Text(article.title), ); }, separatorBuilder: (BuildContext context, int index) => const Divider(), ) : const Center(child: Text('No items')), // กรณีไม่มีรายการ ),
ตัวแปร article จะอ้างอิงแต่ละรายการในลูป แล้วเราสามารถเรียกใช้งานผ่านรูปแบบ article.title
ซึ่งจะสะดวกและกระชับกว่ากำหนดแบบเดิม
ต่อไปเราจะเพิ่มการแสดง Card เข้าไปดังนี้
Expanded( // ส่วนของลิสรายการ child: snapshot.data!.length > 0 // กำหนดเงื่อนไขตรงนี้ ? ListView.separated( // กรณีมีรายการ แสดงปกติ itemCount: snapshot.data!.length, itemBuilder: (context, index) { Article article = snapshot.data![index]; Widget card; // สร้างเป็นตัวแปร card = Card( child: ListTile( title: Text(article.title), ), ); return card; }, separatorBuilder: (BuildContext context, int index) => const SizedBox(), ) : const Center(child: Text('No items')), // กรณีไม่มีรายการ ),
เราเปลี่ยนในสองจุด คือ ส่วนการครอบ ListTile ด้วย Card widget และเปลี่ยนตรงตัวแบ่ง จากเดิม
เป็น Divider() ก็ใช้เป็น SizedBox()
เปรียบเทียบผลลัพธ์ ก่อนและหลังใช้ Card widget
เราสามารถปรับแต่งค่า property ของ card widget เพิ่มเติมได้ ไม่ว่าจะเป็นค่าการกำหนดการยกสูงของเงา
สีของเงา สีของ card ความโค้งมนของมุม ความห่างจากขอบของ card
ดูตัวอย่างการปรับแต่ง card
Card( color: Colors.yellow, // สี shadowColor: Colors.red.withAlpha(100), // สีของเงา elevation: 5.0, // การยกของเงา margin: EdgeInsets.all(8.0), // การเยื้องขอบ shape: BeveledRectangleBorder( // รูปแบบ borderRadius: BorderRadius.circular(20), side: BorderSide( width: 2, color: Colors.blue, style: BorderStyle.solid, ), ), child: ListTile( title: Text(article.title), ), );
ผลลัพธ์
ดูการปรับแต่งค่าเพิ่มเติม Card Widget
แนะนำเพิ่มเติม เกี่ยวกับการกำหนด Padding กับ Margin
ในการใช้งาน padding และ margin เราควรรู้จักรูปแบบการกำหนดค่า ดังนี้ไว้คือ ให้เข้าใจว่า margin
ก็คือห่างจากขอบด้านนอก ส่วน padding ก็คือห่างจากขอบด้านใน เราจะใช้รูปแบบเดียวกันในการกำหนดคือ
ถ้ากำหนดเป็นคู่ เช่น คู่บนล่าง(แนวตั้ง) หรือ คู่ซ้ายขวา(แนวนอน) จะใช้เป็น
const EdgeInsets.symmetric(vertical: 8.0) // horizontal | vertical
ถ้ากำหนดเหมือนกันทุกด้าน จะใช้เป็น
const EdgeInsets.all(8.0)
ถ้าอย่างเลือกกำหนดด้านใด ก็ได้ หนึ่งด้าน สองด้าน หรือทั้งสี่ด้าน แบบอันไหนก่อนหลังก็ได้ ใช้เป็น
const EdgeInsets.only(left: 40.0) // left | top | right | bottom const EdgeInsets.only(left: 40.0, right:10.0) // กำหนดสองด้าน const EdgeInsets.only(left: 40.0, right: 10.0,bottom: 5.0) กำหนดสามด้าน
ถ้าอย่างกำหนดทุกด้านแบบ เรียกตามรูปแบบ ซ้ายบนขวาล่าง จะใช้เป็น
// แบบนี้ต้องกำหนดทุกด้านตามรูปแบบ สลับตำแหน่งกันไม่ได้ const EdgeInsets.fromLTRB(double left, double top, double right, double bottom)
ตัวอย่างการเพิ่ม widget อื่นเข้าไปใน card นอกจาก ListTile
Card( margin: const EdgeInsets.all(5.0), // การเยื้องขอบ child: Column( children: [ ListTile( title: Text(article.title), ), const Divider(), Row( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ TextButton( child: const Text('Like'), onPressed: () {/* ... */}, ), const SizedBox(width: 8), TextButton( child: const Text('Comment'), onPressed: () {/* ... */}, ), const SizedBox(width: 8), Expanded( child: Container( color: Colors.green.withAlpha(50), alignment: Alignment.centerRight, child: TextButton( child: const Text('Share'), onPressed: () {/* ... */}, ), ), ), ], ), ], ) );
ผลลัพธ์
สำหรับเนื้อหาเกี่ยวกับการใช้งาน card widget ก็มีประมาณนี้ เนื้อหาตอนหน้า เราจะมาประยุกต์เพิ่มเติม
โดยจะใช้รูปแบบการแสดงจากผลลัพธ์ล่าสุดนี้ไปใช้งาน รอติดตาม