สร้าง Photo Gallery จาก API อย่างง่าย ใน Flutter

เขียนเมื่อ 2 ปีก่อน โดย Ninenik Narkdee
flutter api pageview photo gallery

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

ดูแล้ว 6,421 ครั้ง




เนื้อหานี้จะเป็นแนวทางการประยุกต์ การดึงข้อมูล API
ของรุปภาพมาแสดงแบบ gallery ด้วย GridView และ
สามารถเปิดเลือกดูแต่ละรูปที่ต้องการได้ สามารถนำไป
ปรับใช้กรณีต้องการให้ app มีหน้า gallery รวมรูปภาพ
เนื้อหานี้เป็นการใช้แนวทางจากบทความที่เคยอธิบายไปแล้ว
ไม่ว่าจะเป็น การใช้งาน Http ดึงข้อมูลจาก Server หรือ
การใช้งาน GridView widget สามารถทบทวนได้ที่บทความ
    การใช้งาน Http ดึงข้อมูลจาก Server มาแสดงใน Flutter http://niik.in/1038
 
    การใช้งาน GridView widget ใน Flutter http://niik.in/1042 
 
    *เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/1070
 
 

ตัวอย่างผลลัพธ์และการทำงาน

 


 
 
    เราสร้างหน้าใหม่มาแล้วเพิ่มเข้าไปใน sidemenu ชื่อว่า gallery เมื่อเปิดไปยังหน้า gallery เราก็จะทำการ
ไปเรียกข้อมูลรูปภาพจาก api ของเว็บไซต์ unsplash.com สามารถเข้าไปสมัครสมาชิกแล้วใช้งาน api ได้
หรือจะสะดวกสร้างขึ้นมาเองก็ได้หากเข้าใจหลักการ
    เมื่อข้อมูลรูปภาพจาก api โหลดมาแล้วก็ทำการแสดงใน GridView widget โดยแสดงในลักษณะให้เต็ม
พื้นที่ของข้อมูลตามตัวอย่าง เมื่อเรากดเลือกจะดูรูปไหน ก็จะแสดงรูปนั้นขึ้นมาพร้อมที่สามารถปัดลงเพื่อปิด
หรือจะกดปุ่มปิดตรงมุมซ้ายก็ได้ แนวทางเบื้องต้นก็จะประมาณนี้
 
 

ขั้นตอนการสร้าง Photo Gallery

    เริ่มต้นเราสร้างไฟล์ gallery.dart ไว้ในโฟลเดอร์ screens
 
    lib > screens > gallery.dart  *ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/960
 

    ไฟล์ gallery.dart

 
import 'package:flutter/material.dart';
  
class Gallery extends StatefulWidget {
    static const routeName = '/gallery';
 
    const Gallery({Key? key}) : super(key: key);
  
    @override
    State<StatefulWidget> createState() {
        return _GalleryState();
    }
}
  
class _GalleryState extends State<Gallery> {
  
    @override
    Widget build(BuildContext context) {
  
        return Scaffold(
            appBar: AppBar(
                title: Text('Gallery'),
            ),
            body: Center(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                        Text('Gallery Screen'),
                    ],
                )
            ),
        );
    }
}
 
    จากนั้นเพิ่มเมนูเข้าไปใน sidemenu
 
    lib > components > sidemenu.dart
 

    ไฟล์ sidemenu.dart บางส่วนเฉพาะที่เพิ่มเมนู

 
...
ListTile(
    leading: Icon(FontAwesomeIcons.photoVideo),
    title: Text('Gallery'),
    onTap: () {
        Navigator.pushNamed(
            context,
            Gallery.routeName
        );
    },
),   
....
 
    จากนั้นเพิ่ม route ใหม่ของ gallery ในไฟล์ main.dart
 
    lib > main.dart
 

    ไฟล์ main.dart บางส่วนเฉพาะที่เพิ่ม gallery

 

...
routes: {
    Home.routeName: (context) => Home(),
    About.routeName: (context) => About(),
    Profile.routeName: (context) => Profile(),
    Contact.routeName: (context) => Contact(),
    Gallery.routeName: (context) => Gallery(),
    Settings.routeName: (context) => Settings(),
},
...

 
 
     การเพิ่มหน้า app ไหม่สามารถทบทวนที่บทความ http://niik.in/960
 
    ถ้าไม่มีอะไรผิดพลาดก็จะได้หน้าตา และการใช้งานเบื้องต้นดังนี้
 
 


 
 
    ต่อไปให้ปรับแก้ไฟล์ gallery.dart ใหม่ดังนี้
 
// สร้าง List ของ url ภาพที่จะนำมาแสดง
List<String> _photos = [];  
// ตัว ScrollController สำหรับจัดการการ scroll ใน GridView
final ScrollController _scrollController = ScrollController();  
 
    เพิ่มส่วนของตัวแปรสำหรับเก็บ url ของรูปในแบบ List<String> และตัวแปรสำหรับจัดการ
ScrollController มีในบทความก่อนๆ 
 
    ต่อไปเป็นฟังก์ชั่นสำหรับดึงข้อมูล
 
// สรัางฟังก์ชั่นดึงข้อมูล คืนค่ากลับมาเป็นข้อมูล Future ประเภท List ของ String
Future<List<String>> getPhotos() async {
  _photos.clear(); // ล้างข้อมูลเดิมถ้ามี 
  try{
    // ใช้งาน api ของ unsplash.com เปลี่ยนค่า client_id เป็นของเรา
    final response = await http
        .get(Uri.parse('https://api.unsplash.com/photos/?client_id=ค่าของเราเอง'));
  
    // เมื่อมีข้อมูลกลับมา
    if (response.statusCode == 200) {
      // แปลงข้อมูล JSON String data เป็น List<dynamic>
      var _result = await json.decode(response.body);  
      // วนลูปข้อมูล 
      _result.forEach((ele){
        // เอาเฉพาะข้อมูล url รุปภาพไม่่เพิ่มในตัวแปร _photos
        _photos.add(ele['urls']['regular']);
      });
    }
  }catch(e){
    print(e);
  }
  return _photos;
}

// ฟังก์ชั่นสำหรับ รีเฟรสโดยการดึงจากขอบบนลง มีอธิบานในบทความก่อนๆ หน้า
Future<void> _refresh() async {
  _photos.clear(); // ล้างข้อมูลเดิมถ้ามี 
  _photos = await getPhotos(); // ดึง url รูปใหม่
  setState(() {});
}
 
    คำสั่ง _refresh() เป็นการเรียกให้ฟังก์ชั่น getPhotos() ทำงานอีกครั้ง
 

    ไฟล์ gallery.dart 

 
import 'dart:async'; // สำหรับจัดการข้อมูลแบบ async
import 'dart:convert'; // สำหรับจัดการข้อมูล JSON data
 
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; // ดึงข้อมูลจัดการข้อมูลบนเครือข่าย internet
  
class Gallery extends StatefulWidget {
  static const routeName = '/gallery';

  const Gallery({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
      return _GalleryState();
  }
}
  
class _GalleryState extends State<Gallery> {
  
  // สร้าง List ของ url ภาพที่จะนำมาแสดง
  List<String> _photos = [];  
  // ตัว ScrollController สำหรับจัดการการ scroll ใน GridView
  final ScrollController _scrollController = ScrollController();  

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  void dispose() {
    _scrollController.dispose(); 
    super.dispose();
  }

  // สรัางฟังก์ชั่นดึงข้อมูล คืนค่ากลับมาเป็นข้อมูล Future ประเภท List ของ String
  Future<List<String>> getPhotos() async {
    _photos.clear(); // ล้างข้อมูลเดิมถ้ามี 
    try{
      // ใช้งาน api ของ unsplash.com เปลี่ยนค่า client_id เป็นของเรา
      final response = await http
          .get(Uri.parse('https://api.unsplash.com/photos/?client_id=abc'));
    
      // เมื่อมีข้อมูลกลับมา
      if (response.statusCode == 200) {
        // แปลงข้อมูล JSON String data เป็น List<dynamic>
        var _result = await json.decode(response.body);  
        // วนลูปข้อมูล 
        _result.forEach((ele){
          // เอาเฉพาะข้อมูล url รุปภาพไม่่เพิ่มในตัวแปร _photos
          _photos.add(ele['urls']['regular']);
        });
      }
    }catch(e){
      print(e);
    }
    return _photos;
  }

  // ฟังก์ชั่นสำหรับ รีเฟรสโดยการดึงจากขอบบนลง มีอธิบานในบทความก่อนๆ หน้า
  Future<void> _refresh() async {
    _photos.clear(); // ล้างข้อมูลเดิมถ้ามี 
    _photos = await getPhotos(); // ดึง url รูปใหม่
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
        appBar: AppBar(
            title: Text('Gallery'),
        ),
        body: Center(
          child: FutureBuilder<List<String>>( // ชนิดของข้อมูล
            future: getPhotos(), // ข้อมูล 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: 
                              Padding(
                                padding: const EdgeInsets.all(0.0),
                                child: GridView.builder(
                                  controller: _scrollController, // กำนหนด controller ที่จะใช้งานร่วม                                    
                                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                                    crossAxisCount: 2,
                                  ),
                                  itemCount: snapshot.data!.length,
                                  itemBuilder: (BuildContext context, int index) {
                                    var photo = snapshot.data![index]; 
 
                                    Widget card; // สร้างเป็นตัวแปร
                                    card =  Container( 
                                              child: InkWell(
                                                onTap: (){
                                                  Navigator.of(context).push(_viewPhoto(context, photo));   
                                                },
                                                child: Card(
                                                  // color: Colors.black,
                                                  child: Column(
                                                    children: [
                                                      AspectRatio(
                                                        aspectRatio: 1.0, 
                                                        child: Container(
                                                          decoration: BoxDecoration(
                                                            image: DecorationImage(
                                                              image: NetworkImage(photo),
                                                              fit: BoxFit.cover,
                                                            ),                                                            
                                                            // color: Colors.black,
                                                          ),
                                                          // child: Image.network(photo),
                                                        ),
                                                      ),
                                                    ],
                                                  ),
                                                ),
                                              )
                                          );
                                        return card;
                                      },                   
                                ),
                              ),
                            )
                            : const Center(child: Text('No items')), // กรณีไม่มีรายการ
                        ),
                      ],
                    );
              } else if (snapshot.hasError) { // กรณี error
                return Text('${snapshot.error}');
              }
              // กรณีสถานะเป็น waiting ยังไม่มีข้อมูล แสดงตัว loading
              return const RefreshProgressIndicator();
            },
          ),  
        ),  
    );
  }

  // สร้างฟังก์ชั่น ที่คืนค่าเป็น route ของ object ฟังก์ชั่นนี้ มี context และ photo เป็น parameter
  static Route<Object?> _viewPhoto(BuildContext context, photo) {
    return DialogRoute<void>(
      context: context,
      builder: (BuildContext context) => // ใช้ arrow aฟังก์ชั่น
        Dismissible( // คืนค่าเป็น dismissible widget
          direction: DismissDirection.vertical, // เมื่อปัดลงในแนวตั้ง
          key: const Key('key'), // ต้องกำหนด key ใช้ค่าตามนี้ได้เลย
          onDismissed: (_) => Navigator.of(context).pop(), // ปัดลงเพื่อปิด             
          child: Scaffold(
            backgroundColor: Colors.black,
            extendBodyBehindAppBar: true, // แสดงพื้นที่ appbar แยก ให้ขายเต็มจอ
            appBar: AppBar( 
                leading: IconButton(
                  onPressed: (){
                    Navigator.of(context).pop();
                  }, 
                  icon: Icon(Icons.close,color: Colors.white),
                ),
                backgroundColor: Colors.transparent,
                elevation: 0.0,
            ),
            body: Center(
              child: Container(
                  child: SingleChildScrollView(
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Column(
                          mainAxisAlignment: MainAxisAlignment.start,
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: <Widget>[
                            Image.network(photo), // แสดงรูปจาก url รูปที่ส่งเข้ามา
                          ],
                      ),
                    ),
                  )
              ),
            ),
          ),
        ),
    );
  }       


}
 
    จุดที่น่าสนใจคือ เราทำการกำหนดให้รุปที่ดึงมา แสดงเป็น background ของ Container
ดูส่วนของโค้ดบรรทัดนี้
 
child: Container(
  decoration: BoxDecoration(
    image: DecorationImage(
      image: NetworkImage(photo), // รูปที่จะแสดง
      fit: BoxFit.cover, // กำหนดให้แสดงคลุมเต็มพื้นที่รูปจะโดน crop ไปบ้าง
    ),                                                            
    // color: Colors.black,
  ),
  // child: Image.network(photo),
),
 
    ผลลัพธ์หน้า gallery
 


 
 
    เราลองเปลี่ยนใช้ตัวที่คอมเม้นท์ปิดไว้แทน ก็จะเป็น
 
child: Container(
  decoration: BoxDecoration(
/*    image: DecorationImage(
      image: NetworkImage(photo), // รูปที่จะแสดง
      fit: BoxFit.cover, // กำหนดให้แสดงคลุมเต็มพื้นที่รูปจะโดน crop ไปบ้าง
    ),  */                                                          
    color: Colors.black,
  ),
  child: Image.network(photo),
),
 
    ผลลัพธ์หน้า gallery
 


 
 
    แบบที่สองจะเป็นการแทรกรูปเข้าไปใน Container โดยไม่ได้กำหนดใช้เป็นพื้นหลัง ดังนั้นขนาด
ของรุปที่แสดงก็จะเห็นรายละเอียดรุปทั้งหมด แต่ก็จะมีช่องว่างของขอบรูป ที่ขนาดของรุปไม่สอด
คล้องกับพื้นที่แสดง และเราแทนพื้นหลังเป็นสีดำ
    แบบพื้นหลังเราก็ได้ GridView ที่แสดงรุปสวยงาม ในขณะที่แบบไม่ใช้พื้นหลังเราก็จะได้ GridView
ที่ได้รายละเอียดของ รู้ว่าเป็นรูปแนวตั้งหรือแนวนอน จุดนี้เราสามารถเลือกใช้ได้ ในตัวอย่างเราจะใช้เป็น
แบบพื้นหลัง และรูปแสดงเต็มพื้นที่ในช่อง Grid
 
    ต่อไปเราจะประยุกต์เพิ่มเติมอีกเล็กน้อย คือเดิม เวลาเราเลือกดูรูปใดๆ แล้ว เราต้องปิดรูปนั้นลงมาก่อน
แล้วก็กดเลือกรูปใหม่ เพราะตอนแสดงรูป เราส่งแค่รูปเดียวไปแสดง ทีนี้เราจะเปลี่ยนเป็นส่งรูปทั้งหมด
ไปแสดงใน PageView ซึ่งตัว PageView เราสามารถกำหนดให้สามารถปัดเลื่อนบนล่าง หรือซ้ายขวา
เพื่อดูรายการต่อๆ ไป โดยไม่ต้องปิด แล้วเปิดใหม่
    สิ่งที่เราต้องกำหนดเพิ่มคือ 
 
  int _currentPhoto = 0; // ตัวแปรเก็บ index รูปที่เลือกดู
 
    เมื่อกดที่รูปไหน ก็ให้เก็บ index ค่านั้นไว้ในตัวแปรนี้
 
onTap: () {
  _currentPhoto = index; // เก็บ index ที่กดดูรุป                                          
  Navigator.of(context).push(_viewPhoto(context, photos));   
},
 
    สังเกตว่าเดิมเราส่งแค่รูปเดียวไปแสดง
 
var photo = snapshot.data![index]; 
Navigator.of(context).push(_viewPhoto(context, photo));   
 
    ก็เปลี่ยนเป็นส่งรูปทั้งหมดไปแสดง แล้วค่อยใช้ค่า _currentPhoto บอกว่าให้รูปไหนเริ่มต้น
 
var photos = snapshot.data!;
Navigator.of(context).push(_viewPhoto(context, photos));   
 
    เมื่อเปิดหน้ารูปภาพมาแล้ว ก็จะแสดงรูปที่เรากำหนดในค่า _currentPhoto แล้วก็สามารถเลื่อนดู
รูปอื่นๆ ได้ ดูตัวอย่างการประยุกต์ ส่วนของการแสดง ก็จะเป็นดังนี้
 
// สร้างฟังก์ชั่น ที่คืนค่าเป็น route ของ object ฟังก์ชั่นนี้ มี context และ photos เป็น parameter
Route<Object?> _viewPhoto(BuildContext context, photos) {     
  return DialogRoute<void>(
    context: context,
    builder: (context){       
      return Dismissible( // คืนค่าเป็น dismissible widget
        direction: DismissDirection.vertical, // เมื่อปัดลงในแนวตั้ง
        key: const Key('key'), // ต้องกำหนด key ใช้ค่าตามนี้ได้เลย
        onDismissed: (_) => Navigator.of(context).pop(), // ปัดลงเพื่อปิด             
        child: Scaffold(
          backgroundColor: Colors.black,
          extendBodyBehindAppBar: true, // แสดงพื้นที่ appbar แยก ให้ขายเต็มจอ
          appBar: AppBar( 
              leading: IconButton(
                onPressed: (){
                  Navigator.of(context).pop();
                }, 
                icon: Icon(Icons.close,color: Colors.white),
              ),
              backgroundColor: Colors.transparent,
              elevation: 0.0,
          ),
          body: PageView.builder(
            scrollDirection: Axis.horizontal, // ปัดเลื่อนในแนวนอน
            controller: PageController(initialPage:_currentPhoto), // รูปแรกที่แสดง
            itemCount: photos.length, // จำนวนรูปทั้งหมด
            itemBuilder: (context, _index){ // วนลูปสร้างรูปที่จะแสดงทั้งหมด               
              return Image.network(photos[_index]);   
            }
          ),
        ),
      );
    }, 
  );
}       
 
    ดูผลลัพธ์และการทำงาน
 


 
 
    ตอนนี้เราสามารถเลือกรูปที่ต้องการจะเปิด และสามารถเลื่อนดูรูปอืนๆ ต่อได้โดยที่ไม่ต้องปิดลงมา
แล้วเลือกใหม่ ทำให้ใช้งานสะดวกขึ้น
 

    ไฟล์ gallery.dart

 
import 'dart:async'; // สำหรับจัดการข้อมูลแบบ async
import 'dart:convert'; // สำหรับจัดการข้อมูล JSON data
 
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; // ดึงข้อมูลจัดการข้อมูลบนเครือข่าย internet
  
class Gallery extends StatefulWidget {
  static const routeName = '/gallery';

  const Gallery({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
      return _GalleryState();
  }
}
  
class _GalleryState extends State<Gallery> {
  
  // สร้าง List ของ url ภาพที่จะนำมาแสดง
  List<String> _photos = [];  
  // ตัว ScrollController สำหรับจัดการการ scroll ใน GridView
  final ScrollController _scrollController = ScrollController();  
  int _currentPhoto = 0; // รูปที่เลือกดู

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  void dispose() {
    _scrollController.dispose(); 
    super.dispose();
  }

  // สรัางฟังก์ชั่นดึงข้อมูล คืนค่ากลับมาเป็นข้อมูล Future ประเภท List ของ String
  Future<List<String>> getPhotos() async {
    _photos.clear(); // ล้างข้อมูลเดิมถ้ามี 
    try{
      // ใช้งาน api ของ unsplash.com เปลี่ยนค่า client_id เป็นของเรา
      final response = await http
          .get(Uri.parse('https://api.unsplash.com/photos/?client_id=abc'));
    
      // เมื่อมีข้อมูลกลับมา
      if (response.statusCode == 200) {
        // แปลงข้อมูล JSON String data เป็น List<dynamic>
        var _result = await json.decode(response.body);  
        // วนลูปข้อมูล 
        _result.forEach((ele){
          // เอาเฉพาะข้อมูล url รุปภาพไม่่เพิ่มในตัวแปร _photos
          _photos.add(ele['urls']['regular']);
        });
      }
    }catch(e){
      print(e);
    }
    return _photos;
  }

  // ฟังก์ชั่นสำหรับ รีเฟรสโดยการดึงจากขอบบนลง มีอธิบานในบทความก่อนๆ หน้า
  Future<void> _refresh() async {
    _photos.clear(); // ล้างข้อมูลเดิมถ้ามี 
    _photos = await getPhotos(); // ดึง url รูปใหม่
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
        appBar: AppBar(
            title: Text('Gallery'),
        ),
        body: Center(
          child: FutureBuilder<List<String>>( // ชนิดของข้อมูล
            future: getPhotos(), // ข้อมูล 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: 
                              Padding(
                                padding: const EdgeInsets.all(0.0),
                                child: GridView.builder(
                                  controller: _scrollController, // กำนหนด controller ที่จะใช้งานร่วม                                    
                                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                                    crossAxisCount: 2,
                                  ),
                                  itemCount: snapshot.data!.length,
                                  itemBuilder: (BuildContext context, int index) {
                                    var photos = snapshot.data!;
                                    var photo = snapshot.data![index]; 
 
                                    Widget card; // สร้างเป็นตัวแปร
                                    card =  Container( 
                                              child: InkWell(
                                                onTap: () {
                                                  _currentPhoto = index;                                                
                                                  Navigator.of(context).push(_viewPhoto(context, photos));   
                                                },
                                                child: Card(
                                                  // color: Colors.black,
                                                  child: Column(
                                                    children: [
                                                      AspectRatio(
                                                        aspectRatio: 1.0, 
                                                          child: Container(
                                                            decoration: BoxDecoration(
                                                              image: DecorationImage(
                                                                image: NetworkImage(photo),
                                                                fit: BoxFit.cover,
                                                              ),                                                            
                                                              // color: Colors.black,
                                                            ),
                                                            // child: Image.network(photo),
                                                          ),
                                                      ),
                                                    ],
                                                  ),
                                                ),
                                              )
                                          );
                                        return card;
                                      },                   
                                ),
                              ),
                            )
                            : const Center(child: Text('No items')), // กรณีไม่มีรายการ
                        ),
                      ],
                    );
              } else if (snapshot.hasError) { // กรณี error
                return Text('${snapshot.error}');
              }
              // กรณีสถานะเป็น waiting ยังไม่มีข้อมูล แสดงตัว loading
              return const RefreshProgressIndicator();
            },
          ),  
        ),  
    );
  }

  // สร้างฟังก์ชั่น ที่คืนค่าเป็น route ของ object ฟังก์ชั่นนี้ มี context และ photos เป็น parameter
  Route<Object?> _viewPhoto(BuildContext context, photos) {     
    return DialogRoute<void>(
      context: context,
      builder: (context){       
        return Dismissible( // คืนค่าเป็น dismissible widget
          direction: DismissDirection.vertical, // เมื่อปัดลงในแนวตั้ง
          key: const Key('key'), // ต้องกำหนด key ใช้ค่าตามนี้ได้เลย
          onDismissed: (_) => Navigator.of(context).pop(), // ปัดลงเพื่อปิด             
          child: Scaffold(
            backgroundColor: Colors.black,
            extendBodyBehindAppBar: true, // แสดงพื้นที่ appbar แยก ให้ขายเต็มจอ
            appBar: AppBar( 
                leading: IconButton(
                  onPressed: (){
                    Navigator.of(context).pop();
                  }, 
                  icon: Icon(Icons.close,color: Colors.white),
                ),
                backgroundColor: Colors.transparent,
                elevation: 0.0,
            ),
            body: PageView.builder(
              scrollDirection: Axis.horizontal, // ปัดเลื่อนในแนวนอน
              controller: PageController(initialPage:_currentPhoto), // รูปแรกที่แสดง
              itemCount: photos.length, // จำนวนรูปทั้งหมด
              itemBuilder: (context, _index){ // วนลูปสร้างรูปที่จะแสดงทั้งหมด               
                return Image.network(photos[_index]);   
              }
            ),
          ),
        );
      }, 
    );
  }      

}
 
    เท่านี้เราก็สามารถสร้าง photo gallery จากการใช้งานรูปบน server มาแสดงใน app ของเราได้
แนวทางนี้ใช้รูปจาก api ของ unsplash.com ดังนั้นเวลานำไปปรับใช้ ให้ดูเรื่องของการกำหนดรุปแบบ
ข้อมุลที่ได้มาจะต้องเป็น List<String> หรือ url ของรูปภาพ ตัวอย่าง JSON String data ของเว็บไซต์
เป็น https://www.ninenik.com/demo/article_api.php 
     ก็สามารถกำหนดเป็นดังนี้
 
// สรัางฟังก์ชั่นดึงข้อมูล คืนค่ากลับมาเป็นข้อมูล Future ประเภท List ของ String
Future<List<String>> getPhotos() async {
  _photos.clear(); // ล้างข้อมูลเดิมถ้ามี 
  try{
    final response = await http
        .get(Uri.parse('https://www.ninenik.com/demo/article_api.php'));
  
    // เมื่อมีข้อมูลกลับมา
    if (response.statusCode == 200) {
      // แปลงข้อมูล JSON String data เป็น List<dynamic>
      var _result = await json.decode(response.body);  
      // วนลูปข้อมูล 
      _result.forEach((ele){
        // เอาเฉพาะข้อมูล url รุปภาพไม่่เพิ่มในตัวแปร _photos
        _photos.add(ele['img']);
      });
    }
  }catch(e){
    print(e);
  }
  return _photos;
}
 
    ผลลัพธ์ก็จะประมาณนี้
 


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


   เพิ่มเติมเนื้อหา ครั้งที่ 1 วันที่ 04-08-2024


ดาวน์โหลดโค้ดตัวอย่าง สามารถนำไปประยุกต์ หรือ run ทดสอบได้

http://niik.in/download/flutter/demo_036_04082024_source.rar


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



ทบทวนบทความที่แล้ว









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









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





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


    ( หรือ เข้าใช้งานผ่าน Social Login )







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