การกำหนดและใช้งาน PopupMenuButton ใน Flutter

บทความใหม่ เดือนนี้ โดย Ninenik Narkdee
enum type flutter popupmenubutton

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



เนื้อหาตอนต่อไปนี้จะมาดู Widget เล็กๆ ที่มีรูปแบบการใช้งาน
ง่ายๆ ที่เรียกว่า PopupMenuButton เป็นปุ่มเมนูเพิ่มเติมที่แสดงมา
ให้เราเลือกใช้งาน หรือกำหนดการทำคำสั่งที่ต้องการทำงานเพิ่มเติม
จะใช้เนื้อหาจากตอนที่แล้ว จะจัดการเฉพาะในไฟล์ article.dart
ทบทวนตอนที่แล้วได้ที่บทความ
    การใช้งาน WebView แสดงเว็บไซต์ ใน Flutter http://niik.in/1043
 
    *เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/961
 
 
 

การใช้งาน PopupMenuButton

    ตัว PopupMenuButton เมื่อกำหนดหรือเรียกใช้งาน จะแสดงเป็นปุ่มไอคอน จุด 3 จุดใน
แนวตั้งหรือชื่อไอคอนว่า Icons.more_vert เป็นการสื่อว่ามีเพิ่มเติม เมื่อเรากดที่ปุ่มนี้ก็จะแสดง
ลิสราายการปุ่มต่างๆ ให้เราเลือก ถ้าเราเลือกปุ่มรายการใดๆ ก็จะใช้ค่าปุ่มรายการนั้นๆ เป็นตัว
กำหนดเงื่อนไขการทำงานอีกที ถ้าเราไม่ต้องการเลือกรายการที่แสดง ก็สามารถกดไปที่พื้นที่ว่าง
นอกรายการเพื่อปิดปุ่มนั้นๆ ไป  ในการสร้างปุ่ม PopupMenuButton จะต้องมีการกำหนด itemBuilder
เพื่อสร้างรายการของปุ่ม 
 

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

 
PopupMenuButton<T>(
  onSelected: (T result) { },
  itemBuilder: (BuildContext context) => <PopupMenuEntry<T>>[
    const PopupMenuItem<T>(
      value: T.value,
      child: Text('Menu 1'),
    ),
    const PopupMenuItem<T>(
      value: T.value,
      child: Text('Menu 2'),
    ),    
  ],
)
 
    สัญลักษณ์ T คือข้อมูลประเภท Type หรือก็คือ class ดูตัวอย่าง type ในภาษา Dart 
 
// ข้อมูล type ColorOption
enum ColorOption { red, green, blue }
// ข้อมูล type Option
class Option{
  int a = 0;
}
 
    ทั้ง ColorOption และ Option เป็นรูปแบบหนึ่งของ class โดยตัว ColorOption จะใช้คำว่า enum เป็นคำ
keyword เป็น class พิเศษเฉพาะที่กำหนดจำนวนของค่าคงที่ ที่เรียกว่า enum type ข้อมูลที่มีการระบุแจกแจง
ค่าไว้อย่างชัดเจน ค่าของ Enum จะอ้างอิงผ่าน property ที่ชื่อ values
 
print(ColorOption.values); // แสดงข้อมูลของ enum type
 
    ก็จะได้เป็น List<ColorOption> มีค่าเป็น
 
[ColorOption.red, ColorOption.green, ColorOption.blue]
// ColorOption.values[0] = ColorOption.red
// ColorOption.values[1] = ColorOption.green
// ColorOption.values[2] = ColorOption.blue
 
    เราจะใช้ช้อมูล Enum type สำหรับกำหนดรายการให้กับ PopupMenuButton ยกตัวอย่างเช่นข้อมูล
 
enum ColorOption { red, green, blue }
 
    สามารถกำหนดใช้งานใน PopupMenuButton เป็นดังนี้
 
PopupMenuButton<ColorOption>(
  onSelected: (ColorOption result) { },
  itemBuilder: (BuildContext context) => <PopupMenuEntry<ColorOption>>[
    const PopupMenuItem<ColorOption>(
      value: ColorOption.red,
      child: Text('Menu 1 Red'),
    ),
    const PopupMenuItem<ColorOption>(
      value: ColorOption.green,
      child: Text('Menu 2 Green'),
    ),    
    const PopupMenuItem<ColorOption>(
      value: ColorOption.blue,
      child: Text('Menu 3 Blue'),
    ),        
  ],
)
 
    หรือกรณีเราใช้เป็นข้อมูล String type ก็จะเป็นประมาณนี้ 
 
PopupMenuButton<String>(
  onSelected: (String result) { },
  itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
    const PopupMenuItem<String>(
      value: '1',
      child: Text('Menu 1 Red'),
    ),
    const PopupMenuItem<String>(
      value: '2',
      child: Text('Menu 2 Green'),
    ),    
    const PopupMenuItem<String>(
      value: '3',
      child: Text('Menu 3 Blue'),
    ),        
  ],
)
 
    หรือกรณีเราใช้เป็นข้อมูล boolean type ก็จะเป็นประมาณนี้ 
 
PopupMenuButton<bool>(
  onSelected: (bool result) { },
  itemBuilder: (BuildContext context) => <PopupMenuEntry<bool>>[
    const PopupMenuItem<bool>(
      value: true,
      child: Text('Menu 1 Red'),
    ),
    const PopupMenuItem<bool>(
      value: false,
      child: Text('Menu 2 Green'),
    ),    
  ],
)
 
    เราสามารถสร้างลิสรายการจากข้อมูลอาเรย์หรือ List ได้ง่ายเพื่อลดขึ้นตอนการกำหนดแต่ละรายการ
 
// สร้างตัวแปร ลืสรายการเมนูที่เป็น String
var myMenuItems = <String>[
  'Home',
  'Profile',
  'Setting',
];    
 
    จากนั้นทำการวนลูปแสดงใน PopupMenuItem ดังนี้
 
PopupMenuButton<String>(
  onSelected: (String result) { },
  itemBuilder: (BuildContext context) {
    return myMenuItems.map((String choice) {
      return PopupMenuItem<String>(
        child: Text(choice),
        value: choice,
      );
    }).toList();
  }
)
 
    ผลลัพธ์ที่ได้
 


 
 
    กรณีประยุกต์กับ Map Type เพิ่ม FontAwesome ไอคอนเข้าไป
 
 
// สร้างตัวแปร ลืสรายการเมนูที่เป็น Map<dynamic, dynamic>
var myMenuItems = <Map>[
  {'icon':FontAwesomeIcons.home,'value':'home','label':'Home'},
  {'icon':FontAwesomeIcons.userAlt,'value':'profile','label':'Profile'},
  {'icon':FontAwesomeIcons.cog,'value':'setting','label':'Setting'}
];   
 
    จากนั้นทำการวนลูปแสดงใน PopupMenuItem ดังนี้
 
PopupMenuButton<Map>(
  onSelected: (Map result) { },
  itemBuilder: (BuildContext context) {
    return myMenuItems.map((Map choice) {
      return PopupMenuItem<Map>(
        child: ListTile(
          leading: Icon(choice['icon']),
          title: Text(choice['label'], style: Theme.of(context).textTheme.bodyText1),
        ),
        value: choice,
      );
    }).toList();
  }
)
 
    ผลลัพธ์ที่ได้
 


 
 
    ตอนนี้เราได้รู้จักแนวทางการประยุกต์การสร้างลิสรายการในรูปแบบต่างๆ ให้สังเกตให้ค่า value ของ
PopupMenuItem คือเมื่อเราแตะเลือกที่รายการใด ค่า value นี้จะถูกส่งเข้าไปใน callback ฟังก์ชั่นของ
onSelected ดังนั้นในการกำหนดเงื่อนไขการทำงาน ก็จะไปกำหนดในค่าที่เลือกว่าเป็นค่าใด และให้ทำงาน
อย่างเรา ยกตัวอย่างรูปแบบกรณีล่าสุด ก็จะเป็น
 
PopupMenuButton<Map>(
  onSelected: (Map result) {
    result = Map<String, dynamic>.from(result); // แปลงค่ากลับ
    switch (result['value']) { // ตรวจสอบค่าที่จะใช้งานเป็นเงื่อนไข
      case 'home':
        print('Home clicked');
        break;
      case 'profile':
        print('Profile clicked');
        break;
      case 'setting':
        print('Setting clicked');
        break;
    }
  },
  itemBuilder: (BuildContext context) {
    return myMenuItems.map((Map choice) {
      return PopupMenuItem<Map>(
        child: ListTile(
          leading: Icon(choice['icon']),
          title: Text(choice['label'], style: Theme.of(context).textTheme.bodyText1),
        ),
        value: choice,
      );
    }).toList();
  }
)
 
    เนื่องจากค่าจาก Map type เป็นข้อมูลที่มีความซับซ้อนดังนั้น จึงมีการแปลงกลับมาในรูปแบบที่สามารถ
อ้างอิงการใช้งานได้ก่อน แต่ถ้าเป็นค่าอื่นๆ เช่น boolean Sring Int Enum เหล่านี้ สามารถนำค่าไปตรวจ
สอบเป็นเงื่อนไขได้เลย 
    ในตัวอย่างการทำคำสั่งเมื่อเข้าเงื่อนไข จะใช้เป็นการเรียกใช้ฟังก์ชั่นอีกที เพราะถ้าเขียนการทำงานในนี้
ก็จะยาวเกินไป ข้างต้นเราแค่ทดสอบแสดงข้อความเท่านั้น
 

    ไฟล์ article.dart แบบเต็ม

 
import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
 
class Articles extends StatefulWidget {
    static const routeName = '/articles';

    const Articles({Key? key}) : super(key: key);
 
    @override
    State<StatefulWidget> createState() {
        return _ArticlesState();
    }
}
 
class _ArticlesState extends State<Articles> {
    // กำหนดตัวแปร controler สำหรับควบคุมการทำงาน
    final Completer<WebViewController> _controller =
        Completer<WebViewController>();

    @override
    void initState() {
      super.initState();
      // กำหนดการใช้งาน ที่รับการใช้งาน keyboard สำหรับ android
      if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
    }

    @override
    Widget build(BuildContext context) {
        // รับค่า url ที่ส่งมาใน arguments
        final url = ModalRoute.of(context)!.settings.arguments as String;

        return Scaffold(
            appBar: AppBar(
                title: Text('Articles'),
                actions: <Widget>[ // สร้างอาเรย์หรือ List ของปุ่มใน action
                  NavigationControls(_controller.future),
                  SampleMenu(_controller.future),
                ],
            ),
            body: Builder(builder: (BuildContext context) {
              // ใช้งาน WebView กำหนดค่าเบื้องต้น 
              return WebView(
                initialUrl: url, // ใช้ url จากหน้าที่ส่งมา 
                javascriptMode: JavascriptMode.unrestricted, // ใช้งาน JavaScript ได้
                onWebViewCreated: (WebViewController webViewController) { // เมื่อสร้าง webviewเสร็จ
                  _controller.complete(webViewController); // การใช้งาน async เมื่อ controller พร้อมใช้งาน
                },
                javascriptChannels: <JavascriptChannel>{ // กำหนดการใช้งาน javascriptChannels
                  _toasterJavascriptChannel(context),
                },                
                gestureNavigationEnabled: true, // กำหนดให้รองรับ gusture ต่างๆ เช่นการปัด เพื่อเลื่อนไปหน้าต่างอื่น
              );
            }),
            floatingActionButton: scrollTopButton(),
        );
    }

    // สร้าง JavascriptChannel สำหรับรับค่าข้อมูลที่ส่งผ่านทาง JavaScript
    JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
      return JavascriptChannel(
        name: 'Toaster', // กำหนดชื่อที่จะใช้
        onMessageReceived: (JavascriptMessage message) {
          // ในที่นี้เมื่อได้ค่ามาแล้ว จะแสดงข้อความด้วย SnackBar
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text(message.message)),
          );
      });
    }    

    // สร้างฟังก์ชั่น คืนค่าเป็น widget
    Widget scrollTopButton() {
      return FutureBuilder<WebViewController>(
          future: _controller.future, // ใช้งาน controller future ได้เลย
          builder: (BuildContext context,
              AsyncSnapshot<WebViewController> controller) {
            if (controller.hasData) { // มีข้อมูล
              return FloatingActionButton( // คืนค่าเป็นปุ่มลูกศรเลื่อนบน
                onPressed: () async { // ถ้ากด
                  // เรียกคำสั่ง javascript  เลื่อน scroll ไปด้านบนสุด
                  await controller.data!.evaluateJavascript('window.scrollTo(0, 0);');
                },
                child: const Icon(Icons.arrow_upward),
              );
            }
            return Container(); // ยังไม่มีข้อมูล คืนค่า container ว่างไปแสดงที่ปุ่ม
          });
    }

}

// กำหนด Enum Type สำหรับเป็นลิสรายการของ PopupMenuButton
enum MenuOptions {
  showUserAgent,
  listCookies,
  clearCookies,
  addToCache,
  listCache,
  clearCache,
}

// สร้าง widget สำหรับทำปุ่มควบคุม เพิ่มเติมแบบ PopupMenuButton
class SampleMenu extends StatelessWidget {
  SampleMenu(this.controller);

  final Future<WebViewController> controller; // ใช้งาน WebViewController
  final CookieManager cookieManager = CookieManager(); // ใช้งาน CookieManager

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<WebViewController>(
      future: controller,
      builder:
          (BuildContext context, AsyncSnapshot<WebViewController> controller) {
        return PopupMenuButton<MenuOptions>(
          onSelected: (MenuOptions value) {
            switch (value) { // ใช้เงื่อนไขค่าที่เลือก ทำฟังก์ชั่นที่ต้องการ
              case MenuOptions.showUserAgent:
                _onShowUserAgent(controller.data!, context);
                break;
              case MenuOptions.listCookies:
                _onListCookies(controller.data!, context);
                break;
              case MenuOptions.clearCookies:
                _onClearCookies(context);
                break;
              case MenuOptions.addToCache:
                _onAddToCache(controller.data!, context);
                break;
              case MenuOptions.listCache:
                _onListCache(controller.data!, context);
                break;
              case MenuOptions.clearCache:
                _onClearCache(controller.data!, context);
                break;
            }
          },
          itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
            PopupMenuItem<MenuOptions>(
              value: MenuOptions.showUserAgent,
              child: const Text('Show user agent'),
              enabled: controller.hasData,
            ),
            const PopupMenuItem<MenuOptions>(
              value: MenuOptions.listCookies,
              child: Text('List cookies'),
            ),
            const PopupMenuItem<MenuOptions>(
              value: MenuOptions.clearCookies,
              child: Text('Clear cookies'),
            ),
            const PopupMenuItem<MenuOptions>(
              value: MenuOptions.addToCache,
              child: Text('Add to cache'),
            ),
            const PopupMenuItem<MenuOptions>(
              value: MenuOptions.listCache,
              child: Text('List cache'),
            ),
            const PopupMenuItem<MenuOptions>(
              value: MenuOptions.clearCache,
              child: Text('Clear cache'),
            ),
          ],
        );
      },
    );
  }

  // ส่วนของฟังก์ชั่นการทำงานต่างๆ 

  // ฟังก์ชั่นแสดง UserAgent ของ WebView
  void _onShowUserAgent(
      WebViewController controller, BuildContext context) async {
    await controller.evaluateJavascript(
        'Toaster.postMessage("User Agent: " + navigator.userAgent);');
  }

  // ฟังก์ชั่นแสดงรายการ cookie
  void _onListCookies(
      WebViewController controller, BuildContext context) async {
    final String cookies =
        await controller.evaluateJavascript('document.cookie');
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          const Text('Cookies:'),
          _getCookieList(cookies),
        ],
      ),
    ));
  }

  // ฟังก์ชั่นเพิ่มรายการ cache
  void _onAddToCache(WebViewController controller, BuildContext context) async {
    await controller.evaluateJavascript(
        'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";');
    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
      content: Text('Added a test entry to cache.'),
    ));
  }

  // ฟังก์ชั่นแสดงรายการ cache
  void _onListCache(WebViewController controller, BuildContext context) async {
    await controller.evaluateJavascript('caches.keys()'
        '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
        '.then((caches) => Toaster.postMessage(caches))');
  }

  // ฟังก์ชั่นล้างค่า cache
  void _onClearCache(WebViewController controller, BuildContext context) async {
    await controller.clearCache();
    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
      content: Text("Cache cleared."),
    ));
  }

  // ฟังก์ชั่นล้างค่า cookie
  void _onClearCookies(BuildContext context) async {
    final bool hadCookies = await cookieManager.clearCookies();
    String message = 'There were cookies. Now, they are gone!';
    if (!hadCookies) {
      message = 'There are no cookies.';
    }
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Text(message),
    ));
  }

  // ฟังก์ชั่นแสดงรายการ cookie
  Widget _getCookieList(String cookies) {
    if (cookies == null || cookies == '""') {
      return Container();
    }
    final List<String> cookieList = cookies.split(';');
    final Iterable<Text> cookieWidgets =
        cookieList.map((String cookie) => Text(cookie));
    return Column(
      mainAxisAlignment: MainAxisAlignment.end,
      mainAxisSize: MainAxisSize.min,
      children: cookieWidgets.toList(),
    );
  }
}


// สร้าง widget สำหรับทำปุ่มควบคุม เช่น ก่อนหน้า ย้อนหลัง รีเฟรช
class NavigationControls extends StatelessWidget {
  // รับค่าข้อมูล Future WebViewController ผ่าน parameter เข้ามาใช้งาน
  const NavigationControls(this._webViewControllerFuture);

  // กำหนดตัวแปรสำหรับรับค่าและเรียกใช้งาน WebViewController ใน widget นี้
  final Future<WebViewController> _webViewControllerFuture;

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<WebViewController>( // ดูเพิ่มเติมได้ที่ http://niik.in/1036
      future: _webViewControllerFuture,
      builder: (BuildContext context, AsyncSnapshot<WebViewController> snapshot) {
        // กำหนดตัวแปรเงื่อนไข controller พร้อมทำงานหรือ webview พร้อมทำงาน
        final bool webViewReady =
        snapshot.connectionState == ConnectionState.done;
        // กำนหดตัวแปร controller เพื่อควบคุม webview
        final WebViewController? controller = snapshot.data;
        // สร้าง widget ปุมต่างๆ ไปใช้งาน
        return Row(
          children: <Widget>[
            IconButton(
              icon: const Icon(Icons.arrow_back_ios),
              onPressed: !webViewReady // ไม่พร้อมทำงาน
                  ? null // คืนค่า null 
                  : () async { // พร้อมทำงาน
                      if (await controller!.canGoBack()) { //เช็คย้อนหลังได้ไหม
                        await controller.goBack(); // ถ้าได้ ก็ย้อนหลัง
                      } else {
                        // ย้อนหลังไม่ได้ แสดงข้อความแจ้ง
                        ScaffoldMessenger.of(context).showSnackBar(
                          const SnackBar(content: Text("No back history item")),
                        );
                        return;
                      }
                    },
            ),
            IconButton(
              icon: const Icon(Icons.arrow_forward_ios),
              onPressed: !webViewReady // ไม่พร้อมทำงาน
                  ? null // คืนค่า null 
                  : () async { // พร้อมทำงาน
                      if (await controller!.canGoForward()) { // เช็คไปหน้าได้ไหม
                        await controller.goForward(); // ถ้าได้ ก็ไปหน้าถัดไป
                      } else {
                        // ถ้าไปหน้าไม่ได้ แสดงข้อความแจ้ง
                        ScaffoldMessenger.of(context).showSnackBar(
                          const SnackBar(
                              content: Text("No forward history item")),
                        );
                        return;
                      }
                    },
            ),
            IconButton(
              icon: const Icon(Icons.replay),
              onPressed: !webViewReady // ไม่พร้อมทำงาน
                  ? null // คืนค่า null 
                  : () { // พร้อมทำงาน
                      controller!.reload(); // โหลดหน้าเว็บเพจใหม่อีกครั้ง
                    },
            ),
          ],
        );
      },
    );
  }
}
 
    ผลลัพธ์ที่ได้
 


 
 
    เนื้อหานี้จะเน้นไปที่การใช้งาน PopupMenuButton รายละเอียดโค้ดอื่นๆ ที่เสริมเข้ามามีรูปแบบ
การใช้งานเหมือนบทความตอนที่แล้ว คำอธิบายแสดงในโค้ด
    หวังว่าเนื้อหานี้จะทำให้เราสามารถประยุกต์การใช้งาน PopupMenuButton เพื่อกำหนดคำสั่งเพิ่ม
เติมที่ต้องการได้ เนื้อหาตอนหน้าจะเป็นอะไร รอติดตาม


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



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









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









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











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