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

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

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



เนื้อหานี้จะมาดูเกี่ยวกับการใช้งาน 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: ^2.1.2
 
    ตัวอย่างการเพิ่ม

 


 
 
    เนื่องจากการใช้งาน 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 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'),
            ),
            body: Builder(builder: (BuildContext context) {
              // ใช้งาน WebView กำหนดค่าเบื้องต้น 
              return WebView(
                initialUrl: url, // ใช้ url จากหน้าที่ส่งมา
                javascriptMode: JavascriptMode.unrestricted, // ใช้งาน JavaScript ได้
                onWebViewCreated: (WebViewController webViewController) { // เมื่อสร้าง webviewเสร็จ
                  _controller.complete(webViewController); // การใช้งาน async เมื่อ controller พร้อมใช้งาน
                },
              );
            }),
        );
    }
}
 
    ผลลัพธ์ที่ได้
 


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

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
 
    จากนั้นกำหนด controller สำหรับใช้งาน
 
// กำหนดตัวแปร controler สำหรับควบคุมการทำงาน
final Completer<WebViewController> _controller =
    Completer<WebViewController>();
 
 
    กำหนดการใช้งานให้รองรับการป้อนข้อมูลจาก keyboard ใน android
 
@override
void initState() {
  super.initState();
  // กำหนดการใช้งาน ที่รับการใช้งาน keyboard สำหรับ android
  if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
 
    สุดท้ายเรียกใช้งาน WebView widget
 
// ใช้งาน WebView กำหนดค่าเบื้องต้น 
return WebView(
  initialUrl: url, // ใช้ url จากหน้าที่ส่งมา
  javascriptMode: JavascriptMode.unrestricted, // ใช้งาน JavaScript ได้
  onWebViewCreated: (WebViewController webViewController) { // เมื่อสร้าง webviewเสร็จ
    _controller.complete(webViewController); // การใช้งาน async เมื่อ controller พร้อมใช้งาน
  },
);
 
    ในการใช้งานร่วมกับ WebView การกำหนด controller ถือว่าเป็นสิ่งสำคัญมาก ทั้งนี้ก็เพราะว่าข้อมูล
ที่เกี่ยวกับข้องจะเป็นข้อมูลที่มีเรื่องของเวลาที่ต้องรอ หรือเป็นข้อมูลแบบ async การสร้าง widget ต่างๆ
มาใช้งานร่วมกับข้อมูลของเว็บเพจ จึงต้องมีข้อมูล Future มาเกี่ยวข้อง
 

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

 
// ใช้งาน WebView กำหนดค่าเบื้องต้น 
return WebView(
  initialUrl: url, // ใช้ url จากหน้าที่ส่งมา
  javascriptMode: JavascriptMode.unrestricted, // ใช้งาน JavaScript ได้
  onWebViewCreated: (WebViewController webViewController) { // เมื่อสร้าง webviewเสร็จ
    _controller.complete(webViewController); // การใช้งาน async เมื่อ controller พร้อมใช้งาน
  },
  onProgress: (int progress) { // กำหนดการทำงาน ตามสถานะการโหลดเว็บเพจ 0-100%
    print("WebView is loading (progress : $progress%)");
  },
  navigationDelegate: (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; // ถ้าเป็นลิ้งค์อื่นๆ เข้าไปปกติ
  },
  onPageStarted: (String url) { // กำหนดการทำงานเมื่อมีการโหลดเว็บเพจ
    print('Page started loading: $url');
  },
  onPageFinished: (String url) { // กำหนดการทำงานเมื่อโหลดเว็บเพจเสร็จสิ้น 100%
    print('Page finished loading: $url');
  },
  gestureNavigationEnabled: true, // กำหนดให้รองรับ gusture ต่างๆ เช่นการปัด เพื่อเลื่อนไปหน้าอื่น
);
 
    การ block ลิ้งค์ตามรูปแบบเงื่อนไขที่กำหนด หน้าเพจจะไม่มีการเปลี่ยนแปลงเมื่อคลิกไปยังลิ้งค์
ที่ถูก block
 
    เราสามารถกำหนดตั้งค่าเกี่ยวกับการเล่นไฟล์เสียง หรือวิดีโอในหน้าเพจที่แสดง โดยเฉพาะไฟล์คลิป
วิดีโอ ไม่ว่าจะเป็นการป้องกันการเล่นไฟล์อัตโนมัติ หรือการเปิดเสียงอัตโนมัติ สามารถตั้งค่าเพิ่มเติมได้
จาก 2 ค่าด้านล่างนี้
 
// อนุญาตเล่นอัตโนมัติได้
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,  
// ป้องกันการเล่นไฟล์วิดีโออัตโนมัติ โดยผู้ใช้ต้องกดเล่นก่อนเท่านั้น
// initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,  
// กำหนดเล่นไฟล์มีเดียต่างๆ ที่ฝังในเพจนั้น
allowsInlineMediaPlayback: true, // true | false 
 
    สำหรับการทำสอบ 2 ค่าข้างต้นต้องทดสอบกับมือถือจริง ถึงจะเห็นผล เท่าที่ลองกับ emulator จะยังไม่
เป็นไปตามค่าที่กำหนด
 
 

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

    เราต้องการให้มีปุ่มควบคุมพื้นฐานใน appbar เช่น ปุ่มย้อนกลับ ปุ่มไปข้างหน้า และปุ่ม รีเฟรช เพื่อจัดการกับ
webview ที่กำลังใช้งานอยู่ โดยจะทำการสร้าง widget มาใช้งานดังนี้
 
// สร้าง 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(); // โหลดหน้าเว็บเพจใหม่อีกครั้ง
                    },
            ),
          ],
        );
      },
    );
  }
}
 
    เพื่อควบคุมการทำงานของ webview เราจึงส่งค่า WebViewController เป็น paramter เข้ามา
ใช้งานใน widget นี้ โดยเป็นข้อมูล Future ดังนั้น เวลาสร้าง widget เราก็จะสร้างด้วย FutureBuilder
รายละเอียดเกี่ยวกับ FutureBuilder มีอธิบายในบทความตามลิ้งค์ในโค้ด หลักการทำงานก็คือสร้าง
ปุ่ม ก่อหน้า ย้อนหลัง และรีเฟรช แล้วควบคุมด้วย controller ที่เราส่งเข้ามา เช่น
 
controller.goBack(); // ย้อนหลังไปหน้าก่อนหน้า
controller.goForward(); // ไปหน้าถัดไป
controller!.reload(); // โหลดหน้าเว็บเพจใหม่อีกครั้ง
 
    ทุกครั้งที่กดแต่ละปุ่ม ก็จะรอค่าที่เป็น controller ที่เป็นข้อมูล Future จากนั้นก็ทำงานตามคำสั่ง
    เมื่อสร้าง widget สำหรับกำหนดเป็นปุ่มแล้ว ก็เรียกใช้งานในส่วนของ appbar ดังนี้
 
appBar: AppBar(
    title: Text('Articles'),
    actions: <Widget>[ // สร้างอาเรย์หรือ List ของปุ่มใน action
      NavigationControls(_controller.future),
    ],
),
 
    สังเกตว่าค่าที่ส่งเป็นค่าเข้าไปจะเป็น controller ที่เป็นข้อมูล Future โดยใช้ _controller.future ซึ่ง
เป็น property ข้อมูลที่เป็น Future
 
    ผลลัพธ์ที่ได้
 


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


  // สร้างฟังก์ชั่น คืนค่าเป็น widget
  Widget favoriteButton() {
    return FutureBuilder<WebViewController>(
        future: _controller.future, // ใช้งาน controller future ได้เลย
        builder: (BuildContext context,
            AsyncSnapshot<WebViewController> controller) {
          if (controller.hasData) { // มีข้อมูล
            return FloatingActionButton( // คืนค่าเป็นปุ่มรูปหัวใจ
              onPressed: () async { // ถ้ากด
                // เรียกดู url ที่กำลังใช้งานอยู่
                final String url = (await controller.data!.currentUrl())!;
                // จำลองการทำงานเท่านั้น โดยเสดงข้อความว่าเพิ่มเป็น Favorited แล้ว
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('Favorited $url')),
                );
              },
              child: const Icon(Icons.favorite),
            );
          }
          return Container(); // ยังไม่มีข้อมูล คืนค่า container ว่างไปแสดงที่ปุ่ม
        });
  }

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

 
 
    
    หรือจะประยุกต์สร้างฟังก์ชั่นเลื่อนหน้าเพจไปด้านบนสุด แบบนี้ได้
 
// สร้างฟังก์ชั่น คืนค่าเป็น 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 ว่างไปแสดงที่ปุ่ม
      });
}
 
    ผลลัพธ์ที่ได้
 
 

 
 
 
    เมื่อกดเลื่อนที่ลูกศร หน้าเพจก็เลื่อนไปด้านบนสุด ตัว controller จะทำคำสั่ง evaluateJavascript
เพื่อทำงานในคำสั่ง JavaScript ที่กำหนด
     ในโค้ดตัวอย่างด้านบน ตัวแปร controller ที่กำหนดในส่วนนี้
 
AsyncSnapshot<WebViewController> controller) {
 
    คือ snapshot แต่แค่เราใช้ชื่อเป้น controller ดังนั้นค่า controller จริงของ WebViewController 
จะอยู่ในค่า controller.data! เราจึงเห็นการใช้งานผ่านคำสั่ง
 
controller.data!.evaluateJavascript()
 
    ทั้งนี้ก็เพื่อลดขั้นตอนการกำหนดตัวแปรเท่านั้น ดูแบบเต็มในรูปแบบที่เป็นการสร้าง widget class ของปุ่ม
ควบคุมในห้วข้อด้านบนก่อนหน้า
 
    มาดูโค้ดเต็มของไฟล์ 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 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),
                ],
            ),
            body: Builder(builder: (BuildContext context) {
              // ใช้งาน WebView กำหนดค่าเบื้องต้น 
              return WebView(
                initialUrl: url, // ใช้ url จากหน้าที่ส่งมา 
                javascriptMode: JavascriptMode.unrestricted, // ใช้งาน JavaScript ได้
                onWebViewCreated: (WebViewController webViewController) { // เมื่อสร้าง webviewเสร็จ
                  _controller.complete(webViewController); // การใช้งาน async เมื่อ controller พร้อมใช้งาน
                },
                /* onProgress: (int progress) { // กำหนดการทำงาน ตามสถานะการโหลดเว็บเพจ 0-100%
                  print("WebView is loading (progress : $progress%)");
                },
                navigationDelegate: (NavigationRequest request) { // กำหนดการทำงานเมื่อคลิกลิ้งค์ในเว็บเพจ
                  // เช่นการตรวจ url และ block ไม่ให้ใช้้งาน url ที่กำหนด
                  if (request.url.startsWith('https://www.ninenik.com/')) {
                    print('blocking navigation to $request}');
                    return NavigationDecision.prevent; // ถ้าเป็นจากลิ้งค์ youtube ให้ block
                  }
                  print('allowing navigation to $request');
                  return NavigationDecision.navigate; // ถ้าเป็นลิ้งค์อื่นๆ เข้าไปปกติ
                },
                onPageStarted: (String url) { // กำหนดการทำงานเมื่อมีการโหลดเว็บเพจ
                  print('Page started loading: $url');
                },
                onPageFinished: (String url) { // กำหนดการทำงานเมื่อโหลดเว็บเพจเสร็จสิ้น 100%
                  print('Page finished loading: $url');
                }, */
                gestureNavigationEnabled: true, // กำหนดให้รองรับ gusture ต่างๆ เช่นการปัด เพื่อเลื่อนไปหน้าต่างอื่น
              );
            }),
            floatingActionButton: scrollTopButton(),
        );
    }


    // สร้างฟังก์ชั่น คืนค่าเป็น 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 ว่างไปแสดงที่ปุ่ม
          });
    }

}

// สร้าง 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(); // โหลดหน้าเว็บเพจใหม่อีกครั้ง
                    },
            ),
          ],
        );
      },
    );
  }
}
 
    เกี่ยวกับการใช้งาน WebView เบื้องต้น ก็ขอจบเพียงเท่านี้ ยังมีส่วนที่ยังไม่กล่าวถึง อาจจะได้มานำ
เสนอในตอนต่อๆ ไป เนื้อหาตอนหน้าจะเป็นอะไร รอติดตาม


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



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









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









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











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