ด้วยสํานึกในพระมหากรุณาธิคุณสมเด็จพระนางเจ้าสิริกิติ์เป็นล้นพ้นอันหาที่สุดมิได้


สแกน QR Code และ Barcode ด้วย mobile scanner ใน Flutter

เขียนเมื่อ 1 ปีก่อน โดย Ninenik Narkdee
flutter mobile scanner flutter ringtone player url launcher

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

ดูแล้ว 1,994 ครั้ง


บทความนี้ต่อยอดจากตอนที่แล้ว ที่เราได้รู้จักกับการสร้าง qrcode
เพื่อใช้งาน แชร์ หรือส่งต่อไปยังแอปอื่นหรือบันทึกลงไว้ในเครื่องไปแล้ว
เนื้อหาตอนนี้เราจะมาสร้างส่วนที่เราใช้สแกนหรือการตรวจจับ qrcode
เพื่ออ่านค่า และนำค่าที่ได้ไปใช้งาน โดยเราจะใช้ plugin ที่ชื่อว่า 
mobile_scanner ในการจัดการ
 
ทบทวนบทความตอนที่แล้วได้ที่
สร้างคิวอาร์โค้ดในแอป Flutter ด้วย qr flutter อย่างง่าย http://niik.in/1120
 
 

ติดตั้ง package ที่จำเป็นเพิ่มเติม ตามรายการด้านล่าง

    แพ็กเก็จที่จำเป็นต้องติดตั้งเพิ่มเติม สำหรับการทำงานมีดังนี้
 
  mobile_scanner: ^5.2.3
  flutter_ringtone_player: ^4.0.0+3
  url_launcher: ^6.3.0
 
    สำหรับ flutter_ringtone_player และ url_launcher ที่เพิ่มเข้ามา ก็เพื่อให้เราสามารถจัดการ
บางอย่างได้มากขึ้น โดย flutter_ringtone_player เราจะใช้สำหรับเล่นไฟล์เสียงจากเครื่อง เพื่อให้
รู้ว่ามีการสแกนและส่งผลลัพธ์กลับมาแล้ว ทำให้เรารับรู้และสังเกตได้ง่าย ส่วน url_launcher เราจะ
ใช้สำหรับจัดการกับรูปแบบข้อความที่สแกนได้เพื่อใช้งานต่อ เช่น ถ้าเป็นลิ้งค์ หรือข้อความ sms หรือ
ค่าอื่นๆ ตามรูปแบบข้อมูล ก็สามารถส่งต่อไปใช้งานได้เลย แทนที่จะแสดงข้อความธรรมดา โดยตัว 
url_launcher จะจัดการกับลิ้งค์หรือข้อความนั้นๆ แล้วส่งต่อไปจัดการได้
 

การกำหนดการขอสิทธิ์เข้าถึงการใช้งานข้อมูล

    จริงๆ เนื้อหานี้เป็นบทความต่อเนื่อง อย่างไรก็ดี เมื่อมีการใช้กล้อง เราควรเพิ่มการขอสิทธิ์การ
ใช้งานกล้องเข้าไปด้วย ด้านล่างเป็นสิทธิ์ทั้งหมดในโค้ดตัวอย่าง ดาวน์โหลดท้ายบทความ
ไฟล์ android > app > src > main > AndroidManifest.xml
 
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28"/>
    <!-- For Android 13+ -->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
    <application ....
....
</manifest>
 
 

การสแกน Qr Code และ Barcode ด้วย mobile_scanner

    ความสามารถของ mobile_scanner นอกจากจะสแกนคิวอาร์โค้ดแล้ว ยังรองรับการสแกน 
Barcode ได้อีกด้วย อย่างไรก็ตามการใช้งาน plugin ตัวนี้จะมีทั้งรูปแบบที่รวม MLKit 
Barcode-scanning for Android และแบบไม่รวม ซึ่งหากไม่ต้องการตั้งค่าอะไร จะเป็นแบบ
รวมเข้ามาแล้ว แต่ก็จะมีส่วนให้ขนาดไฟล์ของแอปเราเพิ่มขึ้น 3-10 MB โดยประมาณ ขึ้นกับการ
ใช้งาน ในที่นี้เราจะใช้แบบรวมมาแล้ว เพื่อให้รองรับกับ android หรือมือถือที่อาจจะไม่มี Google
Play ได้ และด้วยความสามารถที่น่าพอใจก็ดีพอที่จะใช้งานในรูปแบบนี้
 

ไฟล์ scanner.dart

 
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:flutter_ringtone_player/flutter_ringtone_player.dart';


class Scanners extends StatefulWidget {
  static const routeName = '/scanner';

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

  @override
  State<StatefulWidget> createState() {
    return _ScannersState();
  }
}

class _ScannersState extends State<Scanners> with WidgetsBindingObserver {
  // กำหนดส่วนควบคุม scanner ปรับค่าต่างๆ
  final MobileScannerController controller = MobileScannerController(
    autoStart: false, // ไม่เริ่มอัตโนมัติ
    torchEnabled: false, // ไม่เปิดแฟลชทันที
    useNewCameraSelector: true, // ใช้การเรียกใช้กล้องแบบ API ใหม่ 
  );

  // กำหนดค่าเก็บผลลัพธ์ ในที่นี้เราจะมีอยู่ 2 ค่า
  // _barcode ค่าที่ได้จากการสแกน และ _result ค่าไว้เก็บชั่วคราวไปส่งต่อ หรือปรับแต่ง
  Barcode? _result;
  Barcode? _barcode;
  // ตัวแปรตรวจจับข้อมูลที่มีการส่งมาอยางต่อเนื่อง เช่น  ข้อมูลเซ็นเซอร์ หรือข้อมูลจากกล้อง
  StreamSubscription<Object?>? _subscription;
  // ตัวจัดการควบคุมข้อมูล String ที่ได้จากการสแกน
  TextEditingController _textController = TextEditingController();

  // ฟังก์ชั่นจัดการข้อมูล barcode ถ้าไม่มีให้มีค่าเป็น null
  void _handleBarcode(BarcodeCapture barcodes) {
    if (mounted) {
      setState(() {
        _barcode = barcodes.barcodes.firstOrNull;
      });
    }
  }

  @override
  void initState() {
    super.initState();
    // ตรวจจับสถานะของแอป เมื่อใช้ร่วมกับ  mixin WidgetsBindingObserver
    WidgetsBinding.instance.addObserver(this);
    // ตรวจจับค่า stream การเปลี่ยนแปลงข้อมูลที่ต่อเนื่อง
    _subscription = controller.barcodes.listen(_handleBarcode);
    // เรียกใช้ controller.start() โดยไม่ต้องรอให้ทำงานเสร็จ
    unawaited(controller.start());
  }

  // ตรวจจับการเปลี่ยนแปลงของสถานะแอปต่างๆ แล้วกำหนดการทำงาน
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (!controller.value.isInitialized) {
      return;
    }

    switch (state) {
      case AppLifecycleState.detached:
      case AppLifecycleState.hidden:
      case AppLifecycleState.paused:
        return;
      // แอปกลับมาทำงานต่อ  
      case AppLifecycleState.resumed:
        // ตรวจจับค่าข้อมูล
        _subscription = controller.barcodes.listen(_handleBarcode);
        unawaited(controller.start());
      case AppLifecycleState.inactive:
        // เมื่อไม่ active หรือเปิดแอปอื่นขึ้นมาใช้งานอยู่
        unawaited(_subscription?.cancel());
        _subscription = null;
        unawaited(controller.stop());
    }
  }

  // ฟังก์ชั่นสำหรับเปิดหรือเรียกใช้ค่าจาก รูปแบบข้อมูล
  Future<void> _launchURL(String url) async {
    try {
      // ตรวจสอบว่ามันเป็น URL หรือไม่
      final Uri? parsedUrl = Uri.tryParse(url);
      
      if (parsedUrl != null && (parsedUrl.isAbsolute || url.startsWith('geo:'))) {
        // ถ้าเป็น URL ที่ถูกต้องหรือ geo link
        if (await canLaunchUrl(parsedUrl)) {
          await launchUrl(parsedUrl);
        } else {
          throw 'xdebug: Could not launch $url';
        }
      } else {
        String finalUrl = url; 
          // ถ้าไม่ใช่ URL ให้ใช้ canLaunchUrlString
          if (await canLaunchUrlString(finalUrl)) {
            await launchUrlString(finalUrl);
          } else {
            throw 'xdebug: Could not launch $url';
          }
      }
    } catch (e) {
      print('Error launching URL: $e');
      // สามารถแสดงข้อความแสดงข้อผิดพลาดให้ผู้ใช้ได้ที่นี่ เช่น:
      // ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error launching URL: $e')));
    }
  }

  // การล้างค่าต่างๆ เมื่อไม่มีการใช้งาน
  @override
  Future<void> dispose() async {
    _textController.dispose();
    WidgetsBinding.instance.removeObserver(this);
    unawaited(_subscription?.cancel());
    _subscription = null;
    super.dispose();
    await controller.dispose();
  }

  // ฟังก์ชั่นสำหรับคัดลองข้อความที่สแกน
  Future<void> _copyToClipboard() async {
    await Clipboard.setData(ClipboardData(text: _textController.text));

  // แสดงข้อความเมื่อคัดลอกข้อความแล้ว
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('คัดลอกข้อมูลเรียบร้อยแล้ว')),
    );    
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('สแกน บาร์โค้ด / คิวอาร์โค้ด'),
      ),
      body: SingleChildScrollView(
        child: Column(
          // mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            Container(
              height: MediaQuery.of(context).size.height * 0.60, // กำหนดความสูงที่ต้องการ
              child: _buildScanner(context),
            ),            
          //  Expanded(flex: 3, child: _buildScanner(context)),
            Container(
              child: Column(
                // mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  // ส่วนของการแสดงข้อมูล เมื่อสแกนแล้ว ไม่เป็นค่า null
                  if (_barcode != null)
                    Builder(builder: (context) {
              //        print("xdebug: ${_barcode!.rawValue!}");
                      // ถ้าผลลลัพธ์ชั่วคราวเป้น null หรือ ค่าเดิมไม่เหมือนค่าใหม่ที่สแกน
                      if (_result == null ||
                          (_result!.displayValue! != _barcode!.displayValue!)) {
                        // เก็บค่าชั่วคราว จากค่าที่เพิ่งสแกนมา    
                        _result = _barcode;
                        // เล่นไฟล์เสียงแจ้งเตือนใช้ค่าเริ่มต้นของระบบ
                        FlutterRingtonePlayer().playNotification();
                      }
                      // กำหนดค่า string ข้อความที่สแกนมาได้
                    /*  _textController.value =
                          TextEditingValue(text: _barcode!.displayValue!); */
                      // กรณีเป็นค่าที่ไม่ใช่ข้อความ เช่น พวก vCard WIFI Event ใช้ค่า rawValue 
                      _textController.value =
                          TextEditingValue(text: _barcode!.rawValue!);       

                      // กำหนดรูปแบบข้อมูลที่รองรับการเปิด้วย url_launcher
                      // ในที่นี้จะไม่รองรับการเปิด WIFI VCard และ Event Calendar
                      final canLaunchList = ['http:','https:','tel:','sms:', 'mailto:', 'geo:'];
                      bool showLaunch = canLaunchList.any((field) => _textController.text.toString().contains(field));

                      return Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Container(
                          width: 300.0,
                          child: TextFormField(
                            controller: _textController,
                            decoration: InputDecoration(
                              fillColor: Color(0xFFfeeeee), // สีพื้นหลัง
                              filled: true, // กำหนดถ้ามีใส่สีพื้นหลัง fillColor
                              prefixIcon: IconButton(
                                  onPressed: _copyToClipboard, // คัดลอกข้อความ
                                  icon: const FaIcon(FontAwesomeIcons.copy)),
                              // ตรวจสอบรูปแบบข้อความ เพื่อกำหนดปุ่มด้านหลัง สามารถเรียกใช้งานหรือไม่                      
                              suffixIcon: showLaunch
                                  ? IconButton(
                                      onPressed: () {
                                        // ถ้าเป็นลิ้งค์ เรียกใช้งานฟังก์ชั่น _launchURL
                                        _launchURL(_textController.text);
                                      },
                                      icon: Builder(builder: (context) {
                                        // ตรวจสอบว่าเป็นลิ้งค์ประเภทไหน เช่น ไลน์ อีเมล sms เบอร์โทร หรืออื่นๆ
                                        // แล้วกำหนดรูปแบบไอคอนให้สอดคล้อง
                                        if (_textController.text
                                                .toString()
                                                .indexOf(':\/\/line') >
                                            -1) {
                                          return const FaIcon(
                                              FontAwesomeIcons.line);
                                        } else if (_textController.text
                                                .toString()
                                                .indexOf('mailto:') >
                                            -1) {
                                          return const FaIcon(
                                              FontAwesomeIcons.envelope);
                                        } else if (_textController.text
                                                .toString()
                                                .indexOf('sms:') >
                                            -1) {
                                          return const FaIcon(
                                              FontAwesomeIcons.commentSms);
                                        } else if (_textController.text
                                                .toString()
                                                .indexOf('tel:') >
                                            -1) {
                                          return const FaIcon(
                                              FontAwesomeIcons.mobileScreenButton);
                                        } else if (_textController.text
                                                .toString()
                                                .indexOf('geo:') >
                                            -1) {
                                          return const FaIcon(
                                              FontAwesomeIcons.map);
                                        } else if (_textController.text
                                                    .toString()
                                                    .indexOf('youtu.be') >
                                                -1 ||
                                            _textController.text
                                                    .toString()
                                                    .indexOf('youtube.com') >
                                                -1) {
                                          return const FaIcon(
                                              FontAwesomeIcons.youtube);
                                        } else if (_textController.text
                                                    .toString()
                                                    .indexOf('facebook.com') >
                                                -1 ||
                                            _textController.text
                                                    .toString()
                                                    .indexOf('fb:\/\/') >
                                                -1) {
                                          return const FaIcon(
                                              FontAwesomeIcons.facebook);
                                        } else if (_textController.text
                                                .toString()
                                                .indexOf('tiktok.com') >
                                            -1) {
                                          return const FaIcon(
                                              FontAwesomeIcons.tiktok);
                                        } else {
                                          return const FaIcon(FontAwesomeIcons
                                              .upRightFromSquare);
                                        }
                                      }),
                                    )
                                  : null,
                            ),
                            maxLines: null, // ทำให้ TextField ขยายได้ไม่จำกัด
                            minLines: 1, // จำนวนแถวขั้นต่ำ
                            readOnly: true, // ทำให้เป็นแบบอ่านอย่างเดียว
                          ),
                        ),
                      );
                    }),
                    // แสดงข้อความแนะนำ หรือคำอธิบายการใช้งาน
                  Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Center(
                        child: const Text(
                          'สแกนบาร์โค้ด หรือคิวอาร์โค้ด',
                          style: TextStyle(
                            fontSize: 20.0,
                            color: Colors.black54,
                          ),
                        ),
                      ),
                    ],
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      // ส่วนของการกำหนดการใช้งานแฟลช
                      ValueListenableBuilder(
                        valueListenable: controller,
                        builder: (context, state, child) {
                          // ถ้ากล้องไม่พร้อมใช้งานหรือยังไม่ทำงน แสดง
                          // สร้าง widget กล่องที่มีขนาดเล็กที่สุดเท่าที่จะเป็นไปได้ ขนาด 0
                          // หรือ widget ที่ "ว่างเปล่า"                          
                          if (!state.isInitialized || !state.isRunning) {
                            return const SizedBox.shrink();
                          }
                          // เงื่อนไขของการแสดงปุ่มเปิดแฟลช หรือปิดแฟลช หรือแบบ auto
                          switch (state.torchState) {
                            case TorchState.auto:
                              return ElevatedButton(
                                style: ElevatedButton.styleFrom(
                                    //        backgroundColor: Color(0xFFf8a8ab),
                                    ),
                                onPressed: () async {
                                  await controller.toggleTorch();
                                  setState(() {});
                                },
                                child: const Icon(
                                  Icons.flash_auto,
                                  color: Color(0xFF404040),
                                ),
                              );
                            case TorchState.off:
                              return ElevatedButton(
                                style: ElevatedButton.styleFrom(
                                    //        backgroundColor: Color(0xFFf8a8ab),
                                    ),
                                onPressed: () async {
                                  await controller.toggleTorch();
                                  setState(() {});
                                },
                                child: const Icon(
                                  Icons.flash_off,
                                  color: Color(0xFF404040),
                                ),
                              );
                            case TorchState.on:
                              return ElevatedButton(
                                style: ElevatedButton.styleFrom(
                                    //       backgroundColor: Color(0xFFf8a8ab),
                                    ),
                                onPressed: () async {
                                  await controller.toggleTorch();
                                  setState(() {});
                                },
                                child: const Icon(
                                  Icons.flash_on,
                                  color: Color(0xFF404040),
                                ),
                              );
                            case TorchState.unavailable:
                              return ElevatedButton(
                                style: ElevatedButton.styleFrom(
                                    //            backgroundColor: ui.Color.fromARGB(255, 226, 221, 222),
                                    ),
                                onPressed: () async {
                                  setState(() {});
                                },
                                child: const Icon(
                                  Icons.no_flash,
                                  color: Colors.grey,
                                ),
                              );
                          }
                        },
                      ),
                      SizedBox(
                        width: 10.0,
                      ),
                      // ส่วนของการกำหนดกล้องหน้า กล้องหลัง ถ้ามี ตรวจสอบจาก controller
                      ValueListenableBuilder(
                        valueListenable: controller,
                        builder: (context, state, child) {
                          // ถ้ากล้องไม่พร้อมใช้งานหรือยังไม่ทำงน แสดง
                          // สร้าง widget กล่องที่มีขนาดเล็กที่สุดเท่าที่จะเป็นไปได้ ขนาด 0
                          // หรือ widget ที่ "ว่างเปล่า"
                          if (!state.isInitialized || !state.isRunning) {
                            return const SizedBox.shrink();
                          }
                          
                          // ตรวจสอบค่าสถานะจากกล้อง
                          final int? availableCameras = state.availableCameras;
                     //     print("xdebug: ${availableCameras}");
                            // ตรวจสอบค่าสถานะจำนวนกล้องที่รองรับ 
                            // ถ้ามีแค่กล้องเดียว ไม่ต้องแสดงอะไร โขว์ widget ที่ "ว่างเปล่า" แทน
                            // เพราะไม่จำเป็นต้องสลับกล้อง
                          if (availableCameras != null &&
                              availableCameras < 2) {
                            return const SizedBox.shrink();
                          }
                          // กำหนด widget สำหรับไอคอนแสดงกล้อง
                          final Widget icon;
                          // กรณีมีมากกว่า 1 กล้อง
                          // ใช้กล้องไหนอยู่แสดงไอคอนตามกล้องนั้น
                          switch (state.cameraDirection) {
                            case CameraFacing.front:
                              icon = const Icon(Icons.camera_front,
                                  color: Color(0xFF404040));
                            case CameraFacing.back:
                              icon = const Icon(Icons.camera_rear,
                                  color: Color(0xFF404040));
                          }
                          // แสดงปุ่มสลับกล้อง
                          return ElevatedButton(
                            style: ElevatedButton.styleFrom(
                              // backgroundColor: Color(0xFFf8a8ab),
                              // elevation: 0.0,
                            ),
                            onPressed: () async {
                              // เรียกใช้คำสั่งสลับกล้อง
                              await controller.switchCamera();
                              setState(() {});
                            },
                            child: icon,
                          );
                        },
                      ),
                      SizedBox(
                        width: 10.0,
                      ),
                      // ส่วนปุ่มใช้งาน และหยุดชั่วคราว ให้แสดงขึ้นกับค่า controller
                      ValueListenableBuilder(
                        valueListenable: controller,
                        builder: (context, state, child) {
                          // ถ้ากล้องหยุดชั่วคราวอยู่ หรือยังไม่ทำงาน แสดงปุ่มใช้งานต่อ
                          if (!state.isInitialized || !state.isRunning) {
                            return ElevatedButton(
                              style: ElevatedButton.styleFrom(
                                backgroundColor: Colors.white,
                              ),
                              onPressed: () async {
                                // เรียรกใช้คำสั่งใช้งานกล้องต่อ
                                await controller.start();
                                setState(() {});
                              },
                              child: const Text('ใช้งานต่อ',
                                  style: TextStyle(
                                      fontSize: 20,
                                      color:
                                          Color(0xFFf92D050))),
                            );
                          }
                          // ถ้ากล้องใช้งานอยู่ แสดงปุม หยุดชั่วคราว
                          return ElevatedButton(
                            style: ElevatedButton.styleFrom(
                              backgroundColor: Colors.white,
                            ),
                            onPressed: () async {
                              // เรียกใช้คำส่งหยุดกล้องชั่วคราว
                              await controller.stop();
                              setState(() {});
                            },
                            child: const Text('หยุดชั่วคราว',
                                style: TextStyle(
                                    fontSize: 20,
                                    color: Color(0xFFf92D050))),
                          );
                        },
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  // ฟังก์ชั่นสร้างส่วนของช่องสแกน
  Widget _buildScanner(BuildContext context) {
    // กำหนดความกว้าง ความสูง ตามต้องการ
    final double overlayHeight = 250.0; // Set custom height
    final double overlayWidth = 300.0; // Set custom width
    return Stack(
      fit: StackFit.expand,
      children: [
        Positioned.fill(
          child: Container(
            color: Colors.black, // Background overlay
          ),
        ),
        // วางตำแหน่งของสแกนเนอร์
        Center(
          child: Container(
            width: overlayWidth,
            height: overlayHeight,
            child: ClipRRect(
              borderRadius: BorderRadius.circular(
                  16.0), // กำหนดความมนของกรอบ
              child: MobileScanner(
                fit: BoxFit.cover,
                controller: controller,
              ),
            ),
          ),
        ),
        // ปรับแต่งพื้นที่สแกนเพิ่มเติม เช่นใส่เส้นขอบ
        Center(
          child: Container(
            width: overlayWidth,
            height: overlayHeight,
            decoration: BoxDecoration(
              border: Border.all(
                color: Color(0xFFf92D050),
                width: 3.0,
              ),
              borderRadius: BorderRadius.circular(16.0),
            ),
          ),
        ),
      ],
    );
  }
}
 

ผลลัพธ์ที่ได้

 


 
 
    เราสามารถนำไปสแกนรูปแบบ qrcode จากเนื้อหาตอนที่แล้วทั้ง 10 รูปแบบได้ โดยในโค้ดตัวอย่าง
จะรองรับ ข้อความทั่วไป ลิ้งค์เว็บไซต์ทั่วไป sms เบอร์โทร อีเมล พิกัดในแผนที่ โดยจะแสดงทั้งข้อมูล
และสามารถกดลิ้งค์ด้านหลังเพื่อเปิดไปยังแอปที่เกี่ยวข้องได้ เช่น เบอร์โทร กดเข้าไปก็จะไปหน้าโทรให้
อีเมล ก็จะไปหน้าส่งอีเมลให้ แบบนี้เป็นต้น ส่วนอีก 3 รูปแบบที่เหลือ คือ WIFI VCard และ Calendar 
Event จะไม่รองรับการเรียกใช้งานด้วย url_launcher ดังนั้น เราจึงแสดงแค่ปุ่มด้านหน้า ให้สามารถ
คัดลอกข้อความได้เท่านั้น ตัวสแกนเนอร์นี้ รองรับการสแกน Barcode ด้วย
 

เพิ่มเติมส่วนของ url_launcher

    ในตัวอย่างจะเห็นว่าเรามีการเปิดไปยังแอปต่างๆ ที่เกี่ยวข้องกับรูปแบบข้อมูลของ qrcode ซึ่งในส่วนนี้
เราจำเป็นต้องกำหนดเพิ่มเติมในไฟล์ AndroidManifest.xml ส่วนของค่าการ <queries> หรือการ
บอกให้แอปนั้นสามารถรองรับการทำงานบางสิ่งบางอย่าง หรือการใช้งานร่วมกับแอปที่เกี่ยวข้องได้
 
ไฟล์ android > app > src > main > AndroidManifest.xml
 
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28"/>
    <!-- For Android 13+ -->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

    <application
        android:label="Demo App"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
     ..........
    </application>

    <queries>
        <intent>
            <action android:name="android.intent.action.PROCESS_TEXT"/>
            <data android:mimeType="text/plain"/>
        </intent>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="sms" />
        </intent>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="tel" />
        </intent>
        <intent>
            <action android:name="android.support.customtabs.action.CustomTabsService" />
        </intent>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="geo" />
        </intent>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="http" />
        </intent>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="https" />
        </intent>
        <intent>
            <action android:name="android.intent.action.SENDTO" />
            <data android:scheme="mailto" />
        </intent>
    </queries>
</manifest>
 
    การกำหนดค่าภายในแท็ก <queries> ในไฟล์ AndroidManifest.xml เป็นการแจ้งว่าแอป
ของเราต้องการค้นหาและสามารถทำงานร่วมกับแอปพลิเคชันอื่นๆ ที่สามารถตอบสนองต่อ intent 
ที่กำหนดไว้ได้ โดยเฉพาะการทำงานร่วมกับข้อมูลหรือบริการต่างๆ ที่ระบุโดย scheme เช่น ข้อความ
(SMS), การโทร (tel), แผนที่ (geo), เว็บไซต์ (http, https), อีเมล (mailto) เป็นต้น
 
    หวังว่าเนื้อหาตอนนี้จะเป็นประโยชน์ไม่มากก็น้อยในการนำปรับไปประยุกต์ใช้งานต่อไป สำหรับ
บทความในตอนหน้าจะเป็นอะไร รอติดตาม


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


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

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


   เพิ่มเติมเนื้อหา ครั้งที่ 2 วันที่ 11-10-2024


การสแกนจากรูปที่มีรหัส Qrcode หรือ Barcode ในเครื่อง

 
นอกจจากจะสามารถสแกนผ่านกล้องโดยตรงแล้ว เรายังสามารถสแกนจากรูปภาพภายในเครื่องได้
โดยจะมีการประยุกต์เล็กน้อย โดยตัองติดตั้งตัวแพ็กเก็จที่ชื่อว่า image_picker ในไฟล์ 
pubspec.yaml เนื้อหาเกี่ยวกับการใช้งาน image_picker ดูได้ที่บทความ http://niik.in/1118
 
image_picker: ^1.1.2
 
จากนั้นก็กำหนดตัวแปร ที่เกี่ยวข้อง
 
    final ImagePicker _picker = ImagePicker();
    XFile? _imageFile; // ตัวแปรสำหรับเก็บไฟล์รูปภาพที่เลือก
 
ตามด้วยกำหนดฟังก์ชันที่ใช้งาน จะมีแค่ 2 ฟังก์ชั่น คือตัวใช้อ่านรหัสจากรูป และฟังก์ชั่นแสดง การ
แจ้งเตือนกรณีไม่มีหรือไม่พบ code ในภาพ
 
  // ฟังก์ชั่นสแกนจากรูปที่เลือก
  void _scanImageFromGallery() async {
    _imageFile = await _picker.pickImage(source: ImageSource.gallery);
    if (_imageFile != null) {
      final barcodes = await controller.analyzeImage(_imageFile!.path);
      if (barcodes != null) {
        setState(() {
          _barcode = barcodes.barcodes.firstOrNull;
        });        
      } else {
        _showAlert('ไม่พบ Qrcode หรือ Barcode');
      }
    }
  }

  // ฟังก์ชั่นใช้สำหรับการแจ้งเตือน
  void _showAlert(String message) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text('Scan Result'),
          content: Text(message),
          actions: <Widget>[
            TextButton(
              child: Text('OK'),
              onPressed: () {
                Navigator.of(context).pop(); // Close the dialog
              },
            ),
          ],
        );
      },
    );
  }
 
และส่วนสุดท้าย เราก็แค่เพิ่มปุ่มตรง action มุมบนขวาเข้าไป เพื่อเรียกใช้งาน
 
      appBar: AppBar(
        title: Text('สแกน บาร์โค้ด / คิวอาร์โค้ด'),
        actions: [
          IconButton(
            onPressed: () {
              _scanImageFromGallery();
            }, //
            icon: FaIcon(FontAwesomeIcons.image),
          ),
        ],
      ),
 
เท่านี้เราก็สามารถสแกนรหัส qrcode และ barcode ได้ทั้งจากในไฟล์ที่เครื่อง และการสแกนจาก
กล้อง ทำให้สะดวกมากขึ้น ส่วนการจัดการอื่นๆ ก็เหมือนเดิม ไม่ต้องปรับอะไร
 

ผลลัพธ์ที่ได้


 


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



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



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









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






เนื้อหาพิเศษ เฉพาะสำหรับสมาชิก

กรุณาล็อกอิน เพื่ออ่านเนื้อหาบทความ

ยังไม่เป็นสมาชิก

สมาชิกล็อกอิน



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




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










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