การใช้งาน WebView แสดงเว็บไซต์ ใน Flutter

เขียนเมื่อ 3 ปีก่อน โดย Ninenik Narkdee
เปิดหน้าเพจ แสดงเว็บไซต์ flutter webview

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ เปิดหน้าเพจ แสดงเว็บไซต์ flutter webview

ดูแล้ว 9,369 ครั้ง




เนื้อหานี้จะมาดูเกี่ยวกับการใช้งาน WebView widget ซึ่งจะเป็น
package ที่เราจะต้องติดตั้งเพิ่ม เพื่อใช้งานใน flutter ใช้สำหรับ
แสดงหน้าเว็บเพจใน flutter ถ้าเข้าใจอย่างง่ายก็คือเป็นเหมือนมี
บราวเซอร์เล็กๆ ใน app ของเรา สามารถลิ้งค์ไปยังหน้าเพจต่างๆ ได้
เช่น ใช้สำหรับนำเสนอข้อมูลหรือหน้าเพจบางอย่าง อย่างเช่นหน้า นโยบาย
ข้อมูลความเป้นส่วนตัว policy หรือหน้าเพจอื่นๆ ที่ต้องการ
เนื้อหานี้จะใช้รูปแบบเริ่มต้นจากลิ้งค์บทความด้านล่างเป็นแนวทาง
    จัดการข้อมูล Model และแนวทางการนำมาใช้งาน ใน Flutter http://niik.in/1041
 
    *เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/961
 
 

เตรียมข้อมูลสำหรับใช้งาน WebView

    ส่วนนี้จะยังไม่เกี่ยวกับการใช้งาน webview แต่จะเป็นการเตรียมข้อมูลสำหรับใช้งาน
ร่วมกับเนื้อหาในบทความ เราจะใช้ข้อมูลจากบทความในเว็บไซต์ ninenik.com โดยให้สร้าง
Data Model ไว้ในโฟลเดดร์ models ในชื่อไฟล์ article_model.dart และมีรูปแบบดังนี้
    lib > models > article_model.dart
 
    ไฟล์ article_model.dart 
 
// Data models
class Article {
  final String id;
  final String topic;
  final String description;
  final String date;
  final String? image;
  final String url;
  final String view;
  final String? lastvisited;

  Article({
    required this.id,
    required this.topic,
    required this.description,
    required this.date,
    this.image,
    required this.url,    
    required this.view,  
    this.lastvisited,
  });

  // ส่วนของ name constructor ที่จะแปลง json string มาเป็น Article object
  factory Article.fromJson(Map<String, dynamic> json) {
    return Article(
      id: json['id'],
      topic: json['topic'],
      description: json['description'],
      date: json['date'],
      image: json['img'],
      url: json['url'],      
      view: json['view'],    
      lastvisited: json['lastvisited'],    
    );
  }

} 
 
    ต่อไปสร้างไฟล์ที่จแสดงข้อมูลหรือใช้งาน webview ในโฟลเดอร์ screen ใช้ชื่อไฟล์เป็น article.dart
    lib > screen > article.dart
 
    ไฟล์ article.dart
 
import 'package:flutter/material.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> {
 
    @override
    Widget build(BuildContext context) {
 
        return Scaffold(
            appBar: AppBar(
                title: Text('Articles'),
            ),
            body: Center(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                        Text('Articles Screen'),
                    ],
                )
            ),
        );
    }
}
 
    ต่อไปส่วนของไฟล์ทดสอบหน้า home.dart จะเป็นดังนี้
 
    ไฟล์ 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;

import 'article.dart';
import '../models/article_model.dart';
  
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(
                                                  leading: Image.network(article.image!),
                                                  title: Text(article.topic,
                                                    maxLines: 2,
                                                    overflow: TextOverflow.ellipsis,
                                                  ),
                                                  subtitle: Text('View: ${article.view}'),
                                                  trailing: Icon(Icons.more_vert),
                                                  onTap: (){
                                                    Navigator.push(
                                                      context, 
                                                      MaterialPageRoute(builder: (context) => Articles(),
                                                        settings: RouteSettings(
                                                          arguments: article.url // ส่งค่าไปใน  arguments   
                                                        ),
                                                      ),                                             
                                                    );                                          
                                                  },
                                                ),
                                              ],
                                            )
                                          );
                                          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://www.ninenik.com/demo/article_api.php'));

  // เมื่อมีข้อมูลกลับมา
  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();
} 
 
    ผลลัพธ์ที่ได้
 
 


 
 
    การทำงานก็คือแสดงรายการบทความล่าสุด และเมื่อกดไปหน้าบทความใดๆ ก็จะเปิดไปหน้า
article เป็นหน้าแสดงรายละเอียดหรือหน้าที่จะเปิดหน้าเพจ ของ ลิ้งค์ url ที่ส่งไปมาแสดง
เกี่ยวกับโค้ดไฟล์ home.dart เป็นเนื้อหาเดิมทั้งหมดเปลี่ยนแค่ข้อมูล ทบทวนได้ที่บทความ
ผ่านๆ มา เราจะสนใจที่ไฟล์ article.dart ซึ่งจะเป็นไฟล์ที่เราจะใช้งาน webview เพื่อแสดงหน้าเพจ

 
 

เตรียมพร้อมก่อนใช้งาน WebView

    มาต่อในส่วนของการใช้งาน WebView ก่อนอื่นเราจะต้องทำการติดตั้ง package ที่ชื่อว่า
webview_flutter พยายามใช้ให้เป็นเวอร์ชั่นปัจจุบันที่สุด
 

    การติดตั้ง WebView package

    ในส่วนของไฟล์ pubspec.yaml ให้เราเพิ่มการเรียกใช้งาน package เข้าไปดังนี้
 
dependencies:
  webview_flutter: ^4.8.0
 
    ตัวอย่างการเพิ่ม

 


 
 
    เนื่องจากการใช้งาน WebView จะมีการกำหนดในเรื่องของ API level ของ android ต่ำสุดที่รองรับ
เราจะต้องกำหนดเป็น 19 หรือ 20 ขึ้นไป ในที่นี้จะกำหนดเป็น 19 โดยให้ไปแก้ไขที่ไฟล์ build.gradle
    android > app > build.gradle
 
 


 
 
    กำหนด minSdkVersion เป็น 19 ตามรูป ถ้าเราไม่กำหนด จะไม่สามารถ build ผ่านได้
 
    สำหรับหน้าทื่จะใช้งาน WebView เราก็ import package เข้ามาใช้งาน ดังนี้
 
import 'package:webview_flutter/webview_flutter.dart';
 
 
 

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

    เราจะมาลงรายละเอียดในการใช้งาน webview ในไฟล์ article.dart ซึ่งนอกจาก webview package แล้ว
เรายังต้องมีการใช้งาน async และ io  library ของ dart ร่วมด้วย ทั้งนี้เพราะ ในการแสดงข้อมูลและโหลด
ข้อมูลจะมีเรื่องของเวลาที่ต้องรอเข้ามาเกี่ยวข้อง รวมกึงการใช้งานส่วนของ io ที่จัดการเกี่ยวกับ input ouput
โดยเฉพาะ keyboard ให้สามารถใช้งานในหน้าเพจที่แสดงได้
    การกำหนดไฟล์ article.dart เพื่อแสดงเว็บเพจเบื้องต้น
 
    ไฟล์ 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 WebViewController _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            // Update loading bar.
          },
          onPageStarted: (String url) {},
          onPageFinished: (String url) {},
          onHttpError: (HttpResponseError error) {},
          onWebResourceError: (WebResourceError error) {},
          onNavigationRequest: (NavigationRequest request) {
            // ส่วนของการกำหนดเงื่อนไขการทำงาน เช่น ป้องกันไม่ให้เข้าเว็บยูทูป หรืออื่นๆ ถ้ามีเพิ่มเติม
            if (request.url.startsWith('https://www.youtube.com/')) {
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      );        
 
    @override
    void initState() {
      super.initState();
    }
 
    @override
    Widget build(BuildContext context) {
        // รับค่า url ที่ส่งมาใน arguments
        final url = ModalRoute.of(context)!.settings.arguments as String;
         _controller.loadRequest(Uri.parse(url));

        return Scaffold(
            appBar: AppBar(
                title: Text('Articles'),
            ),
            body: WebViewWidget(controller: _controller),
        );
    }
}
 
    ผลลัพธ์ที่ได้
 


 
 
    เรามีการใช้งาน Library และ Package ที่เกี่ยวข้อง
 
import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
 
    จากนั้นกำหนด controller สำหรับใช้งาน
 
   // กำหนดตัวแปร controler สำหรับควบคุมการทำงาน
    final WebViewController _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            // Update loading bar.
          },
          onPageStarted: (String url) {},
          onPageFinished: (String url) {},
          onHttpError: (HttpResponseError error) {},
          onWebResourceError: (WebResourceError error) {},
          onNavigationRequest: (NavigationRequest request) {
            // ส่วนของการกำหนดเงื่อนไขการทำงาน เช่น ป้องกันไม่ให้เข้าเว็บยูทูป หรืออื่นๆ ถ้ามีเพิ่มเติม
            if (request.url.startsWith('https://www.youtube.com/')) {
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      );       
 
 
    ส่วนของการรับค่า url ที่ส่งเข้ามา และนำไปเรียกใช้งาน
 
// รับค่า url ที่ส่งมาใน arguments
final url = ModalRoute.of(context)!.settings.arguments as String;
_controller.loadRequest(Uri.parse(url));
 
    สุดท้ายเรียกใช้งาน WebView widget
 
        return Scaffold(
            appBar: AppBar(
                title: Text('Articles'),
            ),
            body: WebViewWidget(controller: _controller),
        );
 
    ในการใช้งานร่วมกับ WebView การกำหนด controller ถือว่าเป็นสิ่งสำคัญมาก ทั้งนี้ก็เพราะว่าข้อมูล
ที่เกี่ยวกับข้องจะเป็นข้อมูลที่มีเรื่องของเวลาที่ต้องรอ หรือเป็นข้อมูลแบบ async การสร้าง widget ต่างๆ
มาใช้งานร่วมกับข้อมูลของเว็บเพจ จึงต้องมีข้อมูล Future มาเกี่ยวข้อง
 

    การตั้งค่าเพิ่มเติมใน WebView

 
    final WebViewController _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            print("WebView is loading (progress : $progress%)");
            // Update loading bar.
          },
          onPageStarted: (String url) { // กำหนดการทำงานเมื่อมีการโหลดเว็บเพจ
            print('Page started loading: $url');
          },
          onPageFinished: (String url) { // กำหนดการทำงานเมื่อโหลดเว็บเพจเสร็จสิ้น 100%
            print('Page finished loading: $url');
          },
          onHttpError: (HttpResponseError error) {},
          onWebResourceError: (WebResourceError error) {},
          onNavigationRequest: (NavigationRequest request) {// กำหนดการทำงานเมื่อคลิกลิ้งค์ในเว็บเพจ
            // เช่นการตรวจ url และ block ไม่ให้ใช้้งาน url ที่กำหนด
            if (request.url.startsWith('https://www.youtube.com/')) {
              print('blocking navigation to $request}');
              return NavigationDecision.prevent; // ถ้าเป็นจากลิ้งค์ youtube ให้ block
            }
            print('allowing navigation to $request');
            return NavigationDecision.navigate; // ถ้าเป็นลิ้งค์อื่นๆ เข้าไปปกติ
          },
        ),
      );   
 
    การ block ลิ้งค์ตามรูปแบบเงื่อนไขที่กำหนด หน้าเพจจะไม่มีการเปลี่ยนแปลงเมื่อคลิกไปยังลิ้งค์
ที่ถูก block
 
 

    การสร้างปุ่มควบคุมใน WebView และจัดการด้วย controller

    เราต้องการให้มีปุ่มควบคุมพื้นฐานใน appbar เช่น ปุ่มย้อนกลับ ปุ่มไปข้างหน้า และปุ่ม รีเฟรช เพื่อจัดการกับ
webview ที่กำลังใช้งานอยู่ โดยจะทำการสร้าง widget มาใช้งานดังนี้
 
// สร้าง widget สำหรับทำปุ่มควบคุม เช่น ก่อนหน้า ย้อนหลัง รีเฟรช
class NavigationControls extends StatelessWidget {

  // กำหนด class constructor รับค่าที่จำเป็น
  const NavigationControls({
    required this.controller,
    required this.canGoBack,
    required this.canGoForward,
    Key? key,
  }) : super(key: key);

  // กำหนดตัวแปรที่เกี่ยวข้อง
  /*
   ValueNotifier เป็นชนิดข้อมูลใน Flutter ซึ่งเป็น subclass ของ ChangeNotifier 
   ที่ใช้ในการเก็บข้อมูลและแจ้งเตือนผู้ฟัง (listeners) เมื่อค่าของข้อมูลเปลี่ยนแปลง 
   ชนิดข้อมูลนี้มีประโยชน์ในการจัดการสถานะ (state) อย่างง่ายดาย โดยไม่ต้องใช้ state management 
   library ที่ซับซ้อน เช่น Provider หรือ Bloc
  */
  final WebViewController controller;
  final ValueNotifier<bool> canGoBack;
  final ValueNotifier<bool> canGoForward;

  /*
  ValueListenableBuilder เป็น widget ที่ใช้ในการสร้าง UI ที่ฟังการเปลี่ยนแปลงค่าของ 
  ValueNotifier และทำการ rebuild UI เมื่อค่าของ ValueNotifier มีการเปลี่ยนแปลง
  */
  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        ValueListenableBuilder<bool>(
          valueListenable: canGoBack,
          builder: (context, value, child) {
            return IconButton(
              icon: const Icon(Icons.arrow_back),
              onPressed: value ? () => controller.goBack() : null,
            );
          },
        ),
        ValueListenableBuilder<bool>(
          valueListenable: canGoForward,
          builder: (context, value, child) {
            return IconButton(
              icon: const Icon(Icons.arrow_forward),
              onPressed: value ? () => controller.goForward() : null,
            );
          },
        ),
        IconButton(
          icon: const Icon(Icons.refresh),
          onPressed: () => controller.reload(),
        ),
      ],
    );
  }
}
 
    เพื่อควบคุมการทำงานของ webview เราจึงส่งค่า WebViewController เป็น paramter เข้ามา
ใช้งานใน widget นี้ หลักการทำงานก็คือสร้าง ปุ่ม ก่อหน้า ย้อนหลัง และรีเฟรช แล้วควบคุมด้วย /div>
controller ที่เราส่งเข้ามา เช่น
 
controller.goBack(); // ย้อนหลังไปหน้าก่อนหน้า
controller.goForward(); // ไปหน้าถัดไป
controller!.reload(); // โหลดหน้าเว็บเพจใหม่อีกครั้ง
 

ปรับแต่งไฟล์ artcile.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> {

    // แก้ไขตัวแปรสำหรับ contrller ใหม่ ให้เป็นชนิดข้อมูล late 
    late final WebViewController _controller;
  /*
   ValueNotifier เป็นชนิดข้อมูลใน Flutter ซึ่งเป็น subclass ของ ChangeNotifier 
   ที่ใช้ในการเก็บข้อมูลและแจ้งเตือนผู้ฟัง (listeners) เมื่อค่าของข้อมูลเปลี่ยนแปลง 
   ชนิดข้อมูลนี้มีประโยชน์ในการจัดการสถานะ (state) อย่างง่ายดาย โดยไม่ต้องใช้ state management 
   library ที่ซับซ้อน เช่น Provider หรือ Bloc
  */
  // กำหนดค่าเริ่มต้นเป็น false    
    final ValueNotifier<bool> _canGoBack = ValueNotifier<bool>(false);
    final ValueNotifier<bool> _canGoForward = ValueNotifier<bool>(false);       


    @override
    void initState() {
      super.initState();

      _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            print("WebView is loading (progress : $progress%)");
            // Update loading bar.
          },
          onPageStarted: (String url) async {
            _canGoBack.value = await _controller.canGoBack();
            _canGoForward.value = await _controller.canGoForward();
          },
          onPageFinished: (String url) async {
            _canGoBack.value = await _controller.canGoBack();
            _canGoForward.value = await _controller.canGoForward();
          },          
          onHttpError: (HttpResponseError error) {},
          onWebResourceError: (WebResourceError error) {},
          onNavigationRequest: (NavigationRequest request) {// กำหนดการทำงานเมื่อคลิกลิ้งค์ในเว็บเพจ
            // เช่นการตรวจ url และ block ไม่ให้ใช้้งาน url ที่กำหนด
            if (request.url.startsWith('https://www.youtube.com/')) {
              print('blocking navigation to $request}');
              return NavigationDecision.prevent; // ถ้าเป็นจากลิ้งค์ youtube ให้ block
            }
            print('allowing navigation to $request');
            return NavigationDecision.navigate; // ถ้าเป็นลิ้งค์อื่นๆ เข้าไปปกติ
          },
        ),
      );       

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

        return Scaffold(
            appBar: AppBar(
                title: Text('Articles'),
                actions: <Widget>[
                  NavigationControls(
                    controller: _controller, 
                    canGoBack: _canGoBack, 
                    canGoForward: 
                    _canGoForward
                  ),
                ],                
            ),
            body: WebViewWidget(controller: _controller),
        );
    }
}

// สร้าง widget สำหรับทำปุ่มควบคุม เช่น ก่อนหน้า ย้อนหลัง รีเฟรช
class NavigationControls extends StatelessWidget {

  // กำหนด class constructor รับค่าที่จำเป็น
  const NavigationControls({
    required this.controller,
    required this.canGoBack,
    required this.canGoForward,
    Key? key,
  }) : super(key: key);

  // กำหนดตัวแปรที่เกี่ยวข้อง
  /*
   ValueNotifier เป็นชนิดข้อมูลใน Flutter ซึ่งเป็น subclass ของ ChangeNotifier 
   ที่ใช้ในการเก็บข้อมูลและแจ้งเตือนผู้ฟัง (listeners) เมื่อค่าของข้อมูลเปลี่ยนแปลง 
   ชนิดข้อมูลนี้มีประโยชน์ในการจัดการสถานะ (state) อย่างง่ายดาย โดยไม่ต้องใช้ state management 
   library ที่ซับซ้อน เช่น Provider หรือ Bloc
  */
  final WebViewController controller;
  final ValueNotifier<bool> canGoBack;
  final ValueNotifier<bool> canGoForward;

  /*
  ValueListenableBuilder เป็น widget ที่ใช้ในการสร้าง UI ที่ฟังการเปลี่ยนแปลงค่าของ 
  ValueNotifier และทำการ rebuild UI เมื่อค่าของ ValueNotifier มีการเปลี่ยนแปลง
  */
  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        ValueListenableBuilder<bool>(
          valueListenable: canGoBack,
          builder: (context, value, child) {
            return IconButton(
              icon: const Icon(Icons.arrow_back),
              onPressed: value ? () => controller.goBack() : null,
            );
          },
        ),
        ValueListenableBuilder<bool>(
          valueListenable: canGoForward,
          builder: (context, value, child) {
            return IconButton(
              icon: const Icon(Icons.arrow_forward),
              onPressed: value ? () => controller.goForward() : null,
            );
          },
        ),
        IconButton(
          icon: const Icon(Icons.refresh),
          onPressed: () => controller.reload(),
        ),
      ],
    );
  }
}
 
    ผลลัพธ์ที่ได้
 


 
 
    เท่านี้ เราก็มีเมนูควบคุม webview พื้นฐานให้ใช้งาน
 
    นอกจากการสร้างเป็น widget class แล้วส่ง controller เข้าไปเรียกใช้งานแล้ว เรายังสามารถสร้างเป็น
ฟังก์ชั่น ภายในแทน เพื่อสร้าง widget โดยไม่ต้องส่งค่า controller เหมือนวิธีก่อนหน้า แต่สามารถเรียกใช้งาน
ได้เลย เช่น สมมติเราจะใช้ปุ่ม floatingActionButton ทำปุ่มสำหรับ เพิ่ม หน้าเพจที่เป็นอยู่นั้นไว้ใน favorite
จะได้เป็นดังนี้
 
class _ArticlesState extends State<Articles> {
.....
.....
    @override
    Widget build(BuildContext context) {
.......
....
            }),
            floatingActionButton: favoriteButton(), // เรียกใช้ปุ่มจากฟังก์ชั่น
        );
    }


    // สร้างฟังก์ชั่น คืนค่าเป็น widget
    Widget favoriteButton() {
      return FloatingActionButton( // คืนค่าเป็นปุ่มรูปหัวใจ
          onPressed: () async { // ถ้ากด
            print('Favorited');
            String fav_url = (await _controller.currentUrl())!;
            fav_url = Uri.decodeFull(fav_url);
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Favorited $fav_url')),
            );             
          },
          child: const Icon(Icons.favorite),
        );
    }

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

 
 
    
    หรือจะประยุกต์สร้างฟังก์ชั่นเลื่อนหน้าเพจไปด้านบนสุด แบบนี้ได้
 
class _ArticlesState extends State<Articles> {
.....
.....
    @override
    Widget build(BuildContext context) {
.......
....
            }),
            floatingActionButton: scrollTopButton(), // เรียกใช้ปุ่มจากฟังก์ชั่น
        );
    }


    // สร้างฟังก์ชั่น คืนค่าเป็น widget
    Widget scrollTopButton() {
      return FloatingActionButton( // คืนค่าเป็นปุ่มรูปหัวใจ
          onPressed: () async { // ถ้ากด
              // เรียกคำสั่ง javascript  เลื่อน scroll ไปด้านบนสุด
              await _controller.runJavaScript('window.scrollTo(0, 0);');      
          },
          child: const Icon(Icons.arrow_upward),
        );
    }

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

 
 
 
    เมื่อกดเลื่อนที่ลูกศร หน้าเพจก็เลื่อนไปด้านบนสุด ตัว controller จะทำคำสั่ง runJavascript
เพื่อทำงานในคำสั่ง JavaScript ที่กำหนด
 
    มาดูโค้ดเต็มของไฟล์ article.dart ของบทความนี้
 
    ไฟล์ 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> {

    // แก้ไขตัวแปรสำหรับ contrller ใหม่ ให้เป็นชนิดข้อมูล late 
    late final WebViewController _controller;
  /*
   ValueNotifier เป็นชนิดข้อมูลใน Flutter ซึ่งเป็น subclass ของ ChangeNotifier 
   ที่ใช้ในการเก็บข้อมูลและแจ้งเตือนผู้ฟัง (listeners) เมื่อค่าของข้อมูลเปลี่ยนแปลง 
   ชนิดข้อมูลนี้มีประโยชน์ในการจัดการสถานะ (state) อย่างง่ายดาย โดยไม่ต้องใช้ state management 
   library ที่ซับซ้อน เช่น Provider หรือ Bloc
  */
  // กำหนดค่าเริ่มต้นเป็น false    
    final ValueNotifier<bool> _canGoBack = ValueNotifier<bool>(false);
    final ValueNotifier<bool> _canGoForward = ValueNotifier<bool>(false);       


    @override
    void initState() {
      super.initState();

      _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            print("WebView is loading (progress : $progress%)");
            // Update loading bar.
          },
          onPageStarted: (String url) async {
            _canGoBack.value = await _controller.canGoBack();
            _canGoForward.value = await _controller.canGoForward();
          },
          onPageFinished: (String url) async {
            _canGoBack.value = await _controller.canGoBack();
            _canGoForward.value = await _controller.canGoForward();
          },          
          onHttpError: (HttpResponseError error) {},
          onWebResourceError: (WebResourceError error) {},
          onNavigationRequest: (NavigationRequest request) {// กำหนดการทำงานเมื่อคลิกลิ้งค์ในเว็บเพจ
            // เช่นการตรวจ url และ block ไม่ให้ใช้้งาน url ที่กำหนด
            if (request.url.startsWith('https://www.youtube.com/')) {
              print('blocking navigation to $request}');
              return NavigationDecision.prevent; // ถ้าเป็นจากลิ้งค์ youtube ให้ block
            }
            print('allowing navigation to $request');
            return NavigationDecision.navigate; // ถ้าเป็นลิ้งค์อื่นๆ เข้าไปปกติ
          },
        ),
      );       

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

        return Scaffold(
            appBar: AppBar(
                title: Text('Articles'),
                actions: <Widget>[
                  NavigationControls(
                    controller: _controller, 
                    canGoBack: _canGoBack, 
                    canGoForward: 
                    _canGoForward
                  ),
                ],                
            ),
            body: WebViewWidget(controller: _controller),
            floatingActionButton: scrollTopButton(), // เรียกใช้ปุ่มจากฟังก์ชั่น
        );
    }

    // สร้างฟังก์ชั่น คืนค่าเป็น widget
    Widget scrollTopButton() {
      return FloatingActionButton( // คืนค่าเป็นปุ่มรูปหัวใจ
          onPressed: () async { // ถ้ากด
              // เรียกคำสั่ง javascript  เลื่อน scroll ไปด้านบนสุด
              await _controller.runJavaScript('window.scrollTo(0, 0);');      
          },
          child: const Icon(Icons.arrow_upward),
        );
    }

}

// สร้าง widget สำหรับทำปุ่มควบคุม เช่น ก่อนหน้า ย้อนหลัง รีเฟรช
class NavigationControls extends StatelessWidget {

  // กำหนด class constructor รับค่าที่จำเป็น
  const NavigationControls({
    required this.controller,
    required this.canGoBack,
    required this.canGoForward,
    Key? key,
  }) : super(key: key);

  // กำหนดตัวแปรที่เกี่ยวข้อง
  /*
   ValueNotifier เป็นชนิดข้อมูลใน Flutter ซึ่งเป็น subclass ของ ChangeNotifier 
   ที่ใช้ในการเก็บข้อมูลและแจ้งเตือนผู้ฟัง (listeners) เมื่อค่าของข้อมูลเปลี่ยนแปลง 
   ชนิดข้อมูลนี้มีประโยชน์ในการจัดการสถานะ (state) อย่างง่ายดาย โดยไม่ต้องใช้ state management 
   library ที่ซับซ้อน เช่น Provider หรือ Bloc
  */
  final WebViewController controller;
  final ValueNotifier<bool> canGoBack;
  final ValueNotifier<bool> canGoForward;

  /*
  ValueListenableBuilder เป็น widget ที่ใช้ในการสร้าง UI ที่ฟังการเปลี่ยนแปลงค่าของ 
  ValueNotifier และทำการ rebuild UI เมื่อค่าของ ValueNotifier มีการเปลี่ยนแปลง
  */
  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        ValueListenableBuilder<bool>(
          valueListenable: canGoBack,
          builder: (context, value, child) {
            return IconButton(
              icon: const Icon(Icons.arrow_back),
              onPressed: value ? () => controller.goBack() : null,
            );
          },
        ),
        ValueListenableBuilder<bool>(
          valueListenable: canGoForward,
          builder: (context, value, child) {
            return IconButton(
              icon: const Icon(Icons.arrow_forward),
              onPressed: value ? () => controller.goForward() : null,
            );
          },
        ),
        IconButton(
          icon: const Icon(Icons.refresh),
          onPressed: () => controller.reload(),
        ),
      ],
    );
  }
}
 
    เกี่ยวกับการใช้งาน WebView เบื้องต้น ก็ขอจบเพียงเท่านี้ ยังมีส่วนที่ยังไม่กล่าวถึง อาจจะได้มานำ
เสนอในตอนต่อๆ ไป เนื้อหาตอนหน้าจะเป็นอะไร รอติดตาม


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


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

ตัวอย่างการใช้งาน  แบบยังไม่เพิ่มปุ่มควบคุม

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


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


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

ตัวอย่างการใช้งาน  แบบเพิ่มปุ่มควบคุม แล้ว

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


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



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



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









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






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

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

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

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



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




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





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

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


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


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







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