การใช้งาน WebView แสดงเว็บไซต์ ใน Flutter
เขียนเมื่อ 1 ปีก่อน โดย Ninenik Narkdeewebview flutter เปิดหน้าเพจ แสดงเว็บไซต์
คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ webview flutter เปิดหน้าเพจ แสดงเว็บไซต์
ไปที่
Copy
ผลลัพธ์ที่ได้





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

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

เนื้อหานี้จะมาดูเกี่ยวกับการใช้งาน WebView widget ซึ่งจะเป็น
package ที่เราจะต้องติดตั้งเพิ่ม เพื่อใช้งานใน flutter ใช้สำหรับ
แสดงหน้าเว็บเพจใน flutter ถ้าเข้าใจอย่างง่ายก็คือเป็นเหมือนมี
บราวเซอร์เล็กๆ ใน app ของเรา สามารถลิ้งค์ไปยังหน้าเพจต่างๆ ได้
เช่น ใช้สำหรับนำเสนอข้อมูลหรือหน้าเพจบางอย่าง อย่างเช่นหน้า นโยบาย
ข้อมูลความเป้นส่วนตัว policy หรือหน้าเพจอื่นๆ ที่ต้องการ
เนื้อหานี้จะใช้รูปแบบเริ่มต้นจากลิ้งค์บทความด้านล่างเป็นแนวทาง
จัดการข้อมูล Model และแนวทางการนำมาใช้งาน ใน Flutter http://niik.in/1041
https://www.ninenik.com/content.php?arti_id=1041 via @ninenik
*เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ 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 เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ

อ่านต่อที่บทความ
-
01 Nov2021การกำหนดและใช้งาน PopupMenuButton ใน Flutter อ่าน 2,948
เนื้อหาตอนต่อไปนี้จะมาดู Widget เล็กๆ ที่มีรูปแบบการใช้งาน ง่ายๆ ที่เรียก
เนื้อหาที่เกี่ยวข้อง
-
18 Dec2019การใช้งาน BottomNavigationBar ใน Flutter เบื้องต้น อ่าน 17,690
ในตอนที่แล้ว เราได้รู้จัก การใช้งาน Drawer หรือที่เรียกว่า SideMenu เบื้อ
-
21 Oct2021แสดงข้อความด้านล่างหน้าจอด้วย SnackBar ใน flutter อ่าน 2,523
SnackBar เป็น widget ที่เราสามารถใช้สำหรับแสดง popup ข้อความ แ
-
22 Oct2021การกำหนดเพิ่ม Fonts เพื่อใช้งานใน App ของ Flutter อ่าน 3,657
เนื้อหานี้จะมาแนะนำแนวทางการเพิ่มฟอนท์สำหรับ ใช้งานใน app กรณีเราอยากได้ร
-
23 Oct2021จัดการ Tab ด้วย TabController ใน Flutter อ่าน 6,779
เนื้อหานี้มาดูเกี่ยวกับการใช้งาน tab ใน flutter ซึ่งเป็นรูปแบบ การใช้งานท
-
24 Oct2021การใช้งาน FutureBuilder ที่เป็น Async widgets ใน Flutter อ่าน 5,075
เนื้อหาตอนต่อไปนี้ จะมาดูเกี่ยวกับการใช้งาน async widgets ที่ชื่อว่า Futu
-
25 Oct2021การใช้งาน StreamBuilder จัดการข้อมูล Stream ใน Flutter อ่าน 4,526
ในตอนที่แล้วเรารู้จักกับ FutureBuilder ซึ่งเป็น async widgets หนึ่งใน Flu
-
26 Oct2021การใช้งาน Http ดึงข้อมูลจาก Server มาแสดงใน Flutter อ่าน 9,015
เนื้อหาตอนต่อไปนี้ เราจะมาดูเกี่ยวกับวิธีการดึงข้อมูลจาก Server ที่เป็นข้
-
27 Oct2021การใช้งาน Card Widget ร่วมกับ ListView ใน Flutter อ่าน 6,149
เนื้อหาตอนต่อไปนี้ จะมาดูเกี่ยวกับ widget เล็กๆ ที่จะมาช่วย ให้การแสดงผลใ
-
28 Oct2021การใช้งาน RefreshIndicator ปัดเพื่อรีเฟรชข้อมูล ใน Flutter อ่าน 3,976
ต่อเนื่องจากเนื้อหาตอนที่แล้ว เนื้อหานี้เราจะมาดูเกี่ยวกับ widget ที่ให้เ
-
29 Oct2021จัดการข้อมูล Model และแนวทางการนำมาใช้งาน ใน Flutter อ่าน 5,435
เนื้อหาตอนต่อไปนี้จะประยุกต์ต่อเนื่องจากตอนที่แล้ว เราจะลองใช้ ข้อมูลทดสอ
-
30 Oct2021การใช้งาน GridView widget ใน Flutter อ่าน 8,939
เนื้อหาตอนต่อไปนี้ จะมาดูเกี่ยวกับการใช้งาน GridView เป็นเนื้อหาต่อ
-
กำลังอ่านเนื้อหานี้อยู่31 Oct2021การใช้งาน WebView แสดงเว็บไซต์ ใน Flutter อ่าน 6,509
เนื้อหานี้จะมาดูเกี่ยวกับการใช้งาน WebView widget ซึ่งจะเป็น package ที่เ
-
01 Nov2021การกำหนดและใช้งาน PopupMenuButton ใน Flutter อ่าน 2,948
เนื้อหาตอนต่อไปนี้จะมาดู Widget เล็กๆ ที่มีรูปแบบการใช้งาน ง่ายๆ ที่เรียก
URL สำหรับอ้างอิง
Top
Copy
ขอบคุณทุกการสนับสนุน
![]()