การใช้งาน RefreshIndicator ปัดเพื่อรีเฟรชข้อมูล ใน Flutter

บทความใหม่ เดือนที่แล้ว โดย Ninenik Narkdee
refreshindicator visibility flutter progressindicator

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ refreshindicator visibility flutter progressindicator



ต่อเนื่องจากเนื้อหาตอนที่แล้ว เนื้อหานี้เราจะมาดูเกี่ยวกับ
widget ที่ให้เราสามารถทำการโหลดข้อมูลใหม่หรือ refresh ข้อมูล
ดึงข้อมูลจาก server ใหม่ในรูปแบบ pull to refresh หรือการปัดจากขอบ
ด้านบนกรณีไม่สามารถเลื่อน scroll ได้แล้ว เป็น widget ที่ชื่อว่า
RefreshIndicator widget เนื้อหานี้อธิบายต่อเนื่องโดยใช้โค้ดจากบทความ
ตอนที่แล้ว 
    การใช้งาน Card Widget ร่วมกับ ListView ใน Flutter http://niik.in/1039
 
    *เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/961
 
 

การใช้งาน RefreshIndicator

    เราน่าจะคุ้นหรือรู้จักกับรูปแบบการปัดจากขอบหน้าจอด้านบนลงมา เพื่อทำการโหลดข้อมูลใหม่
หรือการรีเฟรชข้อมูล ซึ่งใน flutter ก็จะใช้ RefreshIndicator เป็นตัวจัดการ มีวิธีการใช้งาน
ที่ง่ายไม่ซับซ้อน จะใช้ได้กับส่วนของข้อมูลที่สามารถเลื่อนได้ เช่น ListView เมื่อเราปัดลงเพื่อเลื่อนมา
จนถึงด้านบนสุด ก็จะไม่สามารถเลื่อนได้ เมื่อเราลากลง หรือปัดลงมาเพื่อพยายามเลื่อน ก็จะมีตัว 
indicatior แสดงขึ้นมา และถ้าเราลากยาวลงมาอีกถึงจุดๆ หนึ่ง พอที่จะไปเรียกฟังก์ชั่นทำงาน โดยตัว
ฟังก์ชั่นจะคืนค่าเป็นข้อมูล Future<void> เมื่อรอสักครู่หรือจนกว่าตัวฟังก์ชั่นจะคืนค่ากลับมาแล้ว ตัว
indicator ก็จะหายไป และข้อมูลที่โหลดใหม่ก็จะมาแสดงแทนที่
    ก่อนลงรายละเอียด มาดูโค้ดส่วนของหน้า home บางส่วนจากตอนที่แล้ว เอามาเฉพาะส่วนของ 
_HomeState เท่านั้น
 
class _HomeState extends State<Home> {
 
    // กำนหดตัวแปรข้อมูล articles
    late Future<List<Article>> articles;
   
    @override
    void initState() {
      super.initState(); 
      articles = fetchArticle();
    }     
 
    void _refreshData(){
      setState(() {
       articles = fetchArticle();
      });
    }
 
    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text('Home'),
            ),
            body: Center(
              child: FutureBuilder<List<Article>>( // ชนิดของข้อมูล
                future: articles, // ข้อมูล Future
                builder: (context, snapshot) {
                  // มีข้อมูล และต้องเป็น done ถึงจะแสดงข้อมูล ถ้าไม่ใช่ ก็แสดงตัว loading 
                  if (snapshot.hasData &&  snapshot.connectionState == ConnectionState.done) {
                      return Column(
                          children: [
                            Container( // สร้างส่วน header ของลิสรายการ
                              padding: const EdgeInsets.all(5.0),
                              decoration: BoxDecoration(
                                color: Colors.orange.withAlpha(100),
                              ),                  
                              child: Row(                  
                                children: [
                                  Text('Total ${snapshot.data!.length} items'), // แสดงจำนวนรายการ
                                ],
                              ),
                            ),
                            Expanded( // ส่วนของลิสรายการ
                                child: snapshot.data!.isNotEmpty // กำหนดเงื่อนไขตรงนี้
                                ? ListView.separated( // กรณีมีรายการ แสดงปกติ
                                    itemCount: snapshot.data!.length,
                                    itemBuilder: (context, index) {
                                      Article article = snapshot.data![index];

                                      Widget card; // สร้างเป็นตัวแปร
                                      card = 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.orange.withAlpha(50),
                                                    alignment: Alignment.centerRight,
                                                    child: TextButton(
                                                      child: const Text('Share'),
                                                      onPressed: () {/* ... */},
                                                    ),
                                                  ),
                                                ),
                                              ],
                                            ),
                                          ],
                                        )
                                      );
                                      return card;
                                    },
                                    separatorBuilder: (BuildContext context, int index) => const SizedBox(),                    
                                )
                                : 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),
            ),
        );
    }
 
}
 
    เราจะมาดูและแก้ไขเฉพาะในส่วนนี้เท่านั้น ตัวอย่างหน้าผลลัพธ์ที่ได้
 
 


 
 
    จะเห็นว่าโค้ดเดิม เรามีการใช้งาน floatingActionButton สำหรับทำปุ่มรีเฟรชข้อมูล และมีการกำหนด
ฟังก์ชั่น ชื่อว่า _refreshData() เราจะตัดสองส่วนนี้ไป และจะใช้เป็นวิธีการรีเฟรชแบบใหม่แทน
ในการรีเฟรชข้อมูล หรือขณะกำลังรอข้อมูล จะมีตัวที่แสดงสถานะการดำเนินการอยู่ เรียกว่า Progress Indicator
ในตัวอย่างด้านบนเราใช้เป็นรูปแบบวงกลม ยังมีรูปแบบ Progress Indicator อีกสองรูปแบบที่จะแนะนำ คือ
แบบ รวมตัวที่แสดงแบบวงกลมด้วย
 
CircularProgressIndicator(); // แบบวงกลม
RefreshProgressIndicator(); // แบบตัวรีเฟรช
LinearProgressIndicator(); // แบบแถบเส้น
 
   แต่ละตัวสามารถกำหนดค่าต่างๆ เพิ่มเติม ในที่นี้เราจะไม่พูดถึง จะใช้ค่าเริ่มต้นเท่านั้น คือไม่กำหนดค่าอะไร
เพิ่ม ดูผลลัพธ์และหน้าตาของตัว progress แต่ละตัว
 
 


 
 
    โค้ดของเราเดิมใช้เป็นแบบแรกอยู่ คือแบบวงกลม สำหรับแบบวงกลม และแบบรีเฟรช มักจะแสดงไว้ตรงกลาง
ของหน้าจอขณะทำงาน ส่วนแบบแถบเส้น จะนิยมแสดงชิดขอบด้านบน
    เมื่อเราใช้งาน RefreshIndicator เราก็จะเปลี่ยนตัว progress มาเป็น RefreshProgressIndicator() แทนด้วย
 
 

    รูปแบบการใช้งาน RefreshIndicator

 
RefreshIndicator(
  onRefresh: _refresh,
  child: ListView()
) 
 
    การใช้งานก็คือเอาตัว RefreshIndicator ไปครอบส่วนของ ListView และกำหนดฟังก์ชั่น ให้กับ onRefresh
เราจะมาเริ่มประยุกต์กับโค้ดเก่าของเรา ดังนี้
 
void _refreshData(){
  setState(() {
    articles = fetchArticle();
  });
}
 
    ฟังก์ชั่นนี้เป็นตัวเก่าที่เราใช้กับปุ่ม refresh เดิม เราจะลบออก แล้วตั้งชื่อใหม่ เป็น _refresh และคืนค่าเป็น
ข้อมูล Future<void> ก็จะได้ฟังก์ชั่นใหม่เป็นดังนี้
 
Future<void> _refresh() async {
  setState(() {
    articles = fetchArticle();
  });
}
 
    เราได้ฟังก์ชั่นสำหรับ refresh ข้อมูลที่เป็น Future สำหรับใช้งานใน RefreshIndicator แล้วที่เหลือก็นำตัว
RefreshIndicator มาใช้งาน ก็จะได้เป็นดังนี้
 
class _HomeState extends State<Home> {
 
    // กำนหดตัวแปรข้อมูล articles
    late Future<List<Article>> articles;
   
    @override
    void initState() {
      super.initState(); 
      articles = fetchArticle();
    }     
 
    Future<void> _refresh() async {
      setState(() {
       articles = fetchArticle();
      });
    }
 
    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text('Home'),
            ),
            body: Center(
              child: FutureBuilder<List<Article>>( // ชนิดของข้อมูล
                future: articles, // ข้อมูล Future
                builder: (context, snapshot) {
                  // มีข้อมูล และต้องเป็น done ถึงจะแสดงข้อมูล ถ้าไม่ใช่ ก็แสดงตัว loading 
                  if (snapshot.hasData &&  snapshot.connectionState == ConnectionState.done) {
                      return Column(
                          children: [
                            Container( // สร้างส่วน header ของลิสรายการ
                              padding: const EdgeInsets.all(5.0),
                              decoration: BoxDecoration(
                                color: Colors.orange.withAlpha(100),
                              ),                  
                              child: Row(                  
                                children: [
                                  Text('Total ${snapshot.data!.length} items'), // แสดงจำนวนรายการ
                                ],
                              ),
                            ),
                            Expanded( // ส่วนของลิสรายการ
                                child: snapshot.data!.isNotEmpty // กำหนดเงื่อนไขตรงนี้
                                ? RefreshIndicator(
                                  onRefresh: _refresh,
                                  child: ListView.separated( // กรณีมีรายการ แสดงปกติ
                                        itemCount: snapshot.data!.length,
                                        itemBuilder: (context, index) {
                                          Article article = snapshot.data![index];

                                          Widget card; // สร้างเป็นตัวแปร
                                          card = 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.orange.withAlpha(50),
                                                        alignment: Alignment.centerRight,
                                                        child: TextButton(
                                                          child: const Text('Share'),
                                                          onPressed: () {/* ... */},
                                                        ),
                                                      ),
                                                    ),
                                                  ],
                                                ),
                                              ],
                                            )
                                          );
                                          return card;
                                        },
                                        separatorBuilder: (BuildContext context, int index) => const SizedBox(),                    
                                  ),
                                )
                                : const Center(child: Text('No items')), // กรณีไม่มีรายการ
                            ),
                          ],
                        );
                  } else if (snapshot.hasError) { // กรณี error
                    return Text('${snapshot.error}');
                  }
                  // กรณีสถานะเป็น waiting ยังไม่มีข้อมูล แสดงตัว loading
                  return const RefreshProgressIndicator();
                },
              ),  
            ),          
        );
    }
 
}
 
    ผลลัพธ์ที่ได้
 



 
    จะเห็นว่าตอนนี้ไม่มีปุ่มรีเฟรช มาบังเนื้อหาแล้ว เราสามารถทำการ รีเฟรช โดยปัดลากลงมาจากขอบด้าน
บนของหน้าจอ เมื่อกำลังทำการโหลดข้อมูลใหม่ ตัว RefreshProgressIndicator ก็จะแสดงสถานะกำลัง
ดำเนินการอยู่ จนกว่าข้อมูลจะส่งกลับมา แล้วก็จะหายไปพร้อมแสดงข้อมูลใหม่
 
    เราจะมาประยุกต์การทำงานเพิ่มเติม และรู้จักกับ widget เพิ่ม โดยสิ่งที่ต้องการคือ เมื่อเปิดมาครั้งแรก
ก็แสดงปกติเหมือนโค้ดด้านบน มีการโหลดข้อมูล และตัว RefreshProgressIndicator แสดงตรงกลางหน้า
จอ app แต่เมื่อโหลดข้อมูล และทำการรีเฟรช สิ่งที่เป็นอยู่คือ หน้าจอก็จะมีตัว RefreshProgressIndicator
อยู่ตรงกลางเหมือนเดิม พร้อมข้อมูลที่โหลดมาครั้งแรกหายไป  เราต้องการให้ข้อมูลเดิมยังแสดงอยู่ แต่ให้มี
ตัว LinearProgressIndicator หรือตัว progress แบบแถบเส้นแสดงด้านบนเนื้อหาเดิม เพื่อจะบอกว่า ตอนนี้
กำลังโหลดข้อมูลใหม่ เพื่อไม่ให้หน้าจอโล่ง ขณะเดียวกัน ถ้ากำลังโหลดอยู่ เราเลื่อน scroll มาตำแหน่งอื่น
ก็จะให้เลื่อนกลับมาด้านบนเมื่อโหลดข้อมูลเสร็จ แบบนี้ที่ต้องการ สิ่งที่เราจะต้องใช้งานมี 3 ส่วนคือ เราจะ
เพิ่มตัว LinearProgressIndicator เข้ามาในกรณีที่มีข้อมูลเดิมแล้ว และกำลังโหลดข้อมูลใหม่ โดยที่ตัว
LinearProgressIndicator นี้จะแสดงก็ต่อเมื่อเข้าเงื่อนไข เราจะใช้ Visibility widget ซ่อนหรือแสดงตัว
progress นี้ และสุดท้าย เราจะใช้ ScrollController เพื่อควบคุมการเลื่อนตำแหน่งของ ListView 
 
    จะขอแยกส่วนโค้ดที่จะเพิ่มมาอธิบายดังนี้
 
// ตัว ScrollController สำหรับจัดการการ scroll ใน ListView
final ScrollController _scrollController = ScrollController();
 
    เราสร้าง property สำหรับจัดการกับการ scroll ของ ListView
 
....
child: ListView.separated( // กรณีมีรายการ แสดงปกติ
      controller: _scrollController, // กำนหนด controller ที่จะใช้งานร่วม
      itemCount: snapshot.data!.length,
      itemBuilder: (context, index) {
....
 
    ต่อไปในส่วนของ ListView เราก็กำหนด controller ที่จะใช้งาน 
 
    จัดการเงื่อนไขการแสดงข้อมูล จากเดิม
 
if (snapshot.hasData &&  snapshot.connectionState == ConnectionState.done) {
 
    เงื่อนไขนี้เป็นเงื่อนไขเดิม ที่เมื่อจะแสดงข้อมูลในเงื่อนไขนี้ ก็ต่อเมื่อมีข้อมูลและมีสถานะเป้น done
แล้วเท่านั้น นั่นหมายความว่า เงื่อนไขนี้ จะไม่มีทางแสดงข้อมูลเก่าที่โหลดมาครั้งแรก เพราะทุกครั้งที่โหลด
ใหม่ ก็จะมีสถานะเป็น waiting  เราจะเปลี่ยนให้สามารถแสดงข้อมูลเก่าก่อน ก็จะให้เหลือเป็น
 
 if (snapshot.hasData) { // จะตรวจสอบแค่มีข้อมูลก่อน 
 
    นั่นคือแค่มีข้อมูลครั้งแรก ตัวข้อมูลเก่าก็จะยังแสดงค้างอยู่ เราค่อยเอาเงื่อนไข  ConnectionState เข้า
ไปตรวจสอบต่อด้านในอีกที ดังนี้
 
bool _visible = false; // กำหนดสถานะการแสดง หรือมองเห็น เป็นไม่แสดง
if(snapshot.connectionState == ConnectionState.waiting){ // เมื่อกำลังรอข้อมูล
  _visible = true; // เปลี่ยนสถานะเป็นแสดง
} 
if(_scrollController.hasClients){ //เช็คว่ามีตัว widget ที่ scroll ได้หรือไม่ ถ้ามี
  // เลื่อน scroll มาด้านบนสุด
_scrollController.animateTo(0, duration: Duration(milliseconds: 500), curve: Curves.fastOutSlowIn);
}
 
    เรากำหนดตัวแปรให้กับสถานะการแสดงตัว progress ตามเงื่อนไขของ ConnectionState คือทุกครั้งที่มี
การรีเฟรชข้อมูลใหม่ เราจะให้ตัว progress นี้แสดงด้านบน  ต่อด้วย ถ้ามีข้อมูลใหม่ส่งกลับมาแล้ว
เราจะให้ทำงานส่วนของ scrollController ให้เลื่อนรายการไปไว้ด้านบน
 
    สุดท้ายแทรกการใช้งาน Visibility widget เพื่อแสดง LinearProgressIndicator ด้านบนลิสรายการ ดังนี้
 
Visibility(
  child: const LinearProgressIndicator(),
  visible: _visible,
),
 
    มาดูโค้ดแบบเต็ม และผลลัพธ์การทำงาน
 
    ไฟล์ home.dart
 
import 'dart:async';
import 'dart:convert';
 
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
   
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> {
 
    // กำนหดตัวแปรข้อมูล articles
    late Future<List<Article>> articles;
    // ตัว ScrollController สำหรับจัดการการ scroll ใน ListView
    final ScrollController _scrollController = ScrollController();
   
    @override
    void initState() {
      super.initState(); 
      articles = fetchArticle();
    }     
 
    Future<void> _refresh() async {
      setState(() {
       articles = fetchArticle();
      });
    }

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text('Home'),
            ),
            body: Center(
              child: FutureBuilder<List<Article>>( // ชนิดของข้อมูล
                future: articles, // ข้อมูล Future
                builder: (context, snapshot) {
                  // มีข้อมูล และต้องเป็น done ถึงจะแสดงข้อมูล ถ้าไม่ใช่ ก็แสดงตัว loading 
                  if (snapshot.hasData) {
                      bool _visible = false; // กำหนดสถานะการแสดง หรือมองเห็น เป็นไม่แสดง
                      if(snapshot.connectionState == ConnectionState.waiting){ // เมื่อกำลังรอข้อมูล
                        _visible = true; // เปลี่ยนสถานะเป็นแสดง
                      } 
                      if(_scrollController.hasClients){ //เช็คว่ามีตัว widget ที่ scroll ได้หรือไม่ ถ้ามี
                        // เลื่อน scroll มาด้านบนสุด
                      _scrollController.animateTo(0, duration: Duration(milliseconds: 500), curve: Curves.fastOutSlowIn);
                      }
                      return Column(
                          children: [
                            Visibility(
                              child: const LinearProgressIndicator(),
                              visible: _visible,
                            ),
                            Container( // สร้างส่วน header ของลิสรายการ
                              padding: const EdgeInsets.all(5.0),
                              decoration: BoxDecoration(
                                color: Colors.orange.withAlpha(100),
                              ),                  
                              child: Row(                  
                                children: [
                                  Text('Total ${snapshot.data!.length} items'), // แสดงจำนวนรายการ
                                ],
                              ),
                            ),
                            Expanded( // ส่วนของลิสรายการ
                                child: snapshot.data!.isNotEmpty // กำหนดเงื่อนไขตรงนี้
                                ? RefreshIndicator(
                                  onRefresh: _refresh,
                                  child: ListView.separated( // กรณีมีรายการ แสดงปกติ
                                        controller: _scrollController, // กำนหนด controller ที่จะใช้งานร่วม
                                        itemCount: snapshot.data!.length,
                                        itemBuilder: (context, index) {
                                          Article article = snapshot.data![index];

                                          Widget card; // สร้างเป็นตัวแปร
                                          card = 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.orange.withAlpha(50),
                                                        alignment: Alignment.centerRight,
                                                        child: TextButton(
                                                          child: const Text('Share'),
                                                          onPressed: () {/* ... */},
                                                        ),
                                                      ),
                                                    ),
                                                  ],
                                                ),
                                              ],
                                            )
                                          );
                                          return card;
                                        },
                                        separatorBuilder: (BuildContext context, int index) => const SizedBox(),                    
                                  ),
                                )
                                : const Center(child: Text('No items')), // กรณีไม่มีรายการ
                            ),
                          ],
                        );
                  } else if (snapshot.hasError) { // กรณี error
                    return Text('${snapshot.error}');
                  }
                  // กรณีสถานะเป็น waiting ยังไม่มีข้อมูล แสดงตัว loading
                 return const RefreshProgressIndicator();
                },
              ),  
            ),          
        );
    }
 
}

// สรัางฟังก์ชั่นดึงข้อมูล คืนค่ากลับมาเป็นข้อมูล Future ประเภท List ของ Article
Future<List<Article>> fetchArticle() async {
  // ทำการดึงข้อมูลจาก server ตาม url ที่กำหนด
  final response = await http
      .get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));
 
  // เมื่อมีข้อมูลกลับมา
  if (response.statusCode == 200) {
    // ส่งข้อมูลที่เป็น JSON String data ไปทำการแปลง เป็นข้อมูล List<Article
    // โดยใช้คำสั่ง compute ทำงานเบื้องหลัง เรียกใช้ฟังก์ชั่นชื่อ parseArticles
    // ส่งข้อมูล JSON String data ผ่านตัวแปร response.body
    return compute(parseArticles, response.body);
  } else { // กรณี error
    throw Exception('Failed to load article');
  }
}
 
// ฟังก์ชั่นแปลงข้อมูล JSON String data เป็น เป็นข้อมูล List<Article>
List<Article> parseArticles(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
  return parsed.map<Article>((json) => Article.fromJson(json)).toList();
} 
 
// Data models
class Article {
  final int userId;
  final int id;
  final String title;
  final String body;
 
  Article({
    required this.userId,
    required this.id,
    required this.title,
    required this.body,
  });
 
  // ส่วนของ name constructor ที่จะแปลง json string มาเป็น Article object
  factory Article.fromJson(Map<String, dynamic> json) {
    return Article(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
      body: json['body'],
    );
  }
 
}
 
    ผลลัพธ์
 



 
    เราสามารถปรับแต่งตำแหน่งของตัว progress ต่างๆ ได้โดยใช้ Align widget ตัวอย่างเช่น
 
const Align(
  alignment: Alignment.topCenter, // ไว้ตรงกลางด้านบน
  child: LinearProgressIndicator(
    backgroundColor: Colors.yellow, // กำหนดสีพื้นหลัง
    valueColor: AlwaysStoppedAnimation<Color>(Colors.red), // กำหนดสีข้อมูล
  ),
),
 
    เกี่ยวกับการปรับแต่งตัว ProgressIndicator และ Widget ต่างๆ แต่ละตัวสามารถดูการกำหนดค่าเพิ่มเติมได้ที่ลิ้งค์ API 
แต่ละตัวดังนี้  
 
    CircularProgressIndicator(); // แบบวงกลม
    RefreshProgressIndicator(); // แบบตัวรีเฟรช
    LinearProgressIndicator(); // แบบแถบเส้น

    RefreshIndicator
    หวังว่าเนื้อหาจากบทความนี้จะเป็นประโยชน์ในการนำไปปรับประยุกต์ใช้ หรือทำความเข้าใจต่อไป
เนื้อหาในตอนหน้าจะเป็นอะไรรอติดตาม


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



อ่านต่อที่บทความ









เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง











เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ