1. บทนำ
อัปเดตล่าสุด 19-10-2021
คุณเพิ่มวิดเจ็ต WebView ลงในแอป Flutter สำหรับ Android หรือ iOS ได้ด้วยปลั๊กอิน WebView Flutter ใน iOS วิดเจ็ต WebView ได้รับการสนับสนุนโดย WKWebView ขณะที่ใน Android วิดเจ็ต WebView จะได้รับการสนับสนุนโดย WebView ปลั๊กอินแสดงผลวิดเจ็ต Flutter ผ่านมุมมองเว็บได้ ตัวอย่างเช่น คุณสามารถแสดงผลเมนูแบบเลื่อนลงในมุมมองเว็บได้
สิ่งที่คุณจะสร้าง
ใน Codelab นี้ คุณจะได้สร้างแอปบนอุปกรณ์เคลื่อนที่พร้อม WebView แบบทีละขั้นตอนโดยใช้ Flutter SDK แอปของคุณจะ
- แสดงเนื้อหาเว็บใน
WebView
- แสดงวิดเจ็ต Flutter ซ้อนบน
WebView
- ตอบสนองต่อเหตุการณ์ความคืบหน้าในการโหลดหน้าเว็บ
- ควบคุม
WebView
ผ่านWebViewController
- บล็อกเว็บไซต์โดยใช้
NavigationDelegate
- ประเมินนิพจน์ JavaScript
- จัดการ Callback จาก JavaScript ด้วย
JavascriptChannels
- ตั้งค่า นำออก เพิ่ม หรือแสดงคุกกี้
- โหลดและแสดง HTML จากเนื้อหา ไฟล์ หรือสตริงที่มี HTML
สิ่งที่คุณจะได้เรียนรู้
ใน Codelab นี้ คุณจะได้เรียนรู้วิธีใช้ปลั๊กอิน webview_flutter
ในหลายวิธี เช่น
- วิธีกำหนดค่าปลั๊กอิน
webview_flutter
- วิธีฟังเหตุการณ์เกี่ยวกับความคืบหน้าในการโหลดหน้าเว็บ
- วิธีควบคุมการนำทางหน้าเว็บ
- วิธีสั่งให้
WebView
กรอกลับและไปข้างหน้าผ่านประวัติ - วิธีประเมิน JavaScript รวมถึงการใช้ผลลัพธ์ที่ส่งคืน
- วิธีลงทะเบียน Callback เพื่อเรียกโค้ด Dart จาก JavaScript
- วิธีจัดการคุกกี้
- วิธีโหลดและแสดงหน้า HTML จากเนื้อหาหรือไฟล์ หรือสตริงที่มี HTML
สิ่งที่ต้องมี
- Android Studio 4.1 ขึ้นไป (สำหรับการพัฒนา Android)
- Xcode 12 ขึ้นไป (สำหรับการพัฒนา iOS)
- Flutter SDK
- ตัวแก้ไขโค้ด เช่น Android Studio, โค้ด Visual Studio หรือ Emacs
2. ตั้งค่าสภาพแวดล้อมในการพัฒนาซอฟต์แวร์ Flutter
ห้องทดลองนี้ต้องมีซอฟต์แวร์ 2 ประเภท ได้แก่ Flutter SDK และเครื่องมือแก้ไข
คุณเรียกใช้ Codelab ได้โดยใช้อุปกรณ์ต่อไปนี้
- อุปกรณ์ Android หรือ iOS ที่เชื่อมต่อกับคอมพิวเตอร์และตั้งค่าเป็นโหมดนักพัฒนาซอฟต์แวร์
- เครื่องมือจำลอง iOS (ต้องติดตั้งเครื่องมือ Xcode)
- โปรแกรมจำลอง Android (ต้องตั้งค่าใน Android Studio)
3. เริ่มต้นใช้งาน
การเริ่มต้นใช้งาน Flutter
การสร้างโปรเจ็กต์ Flutter ใหม่ทำได้หลายวิธี โดยทั้ง Android Studio และ โค้ด Visual Studio จะมีเครื่องมือสำหรับงานนี้ โปรดทำตามขั้นตอนที่ลิงก์เพื่อสร้างโปรเจ็กต์ หรือเรียกใช้คำสั่งต่อไปนี้ในเทอร์มินัลบรรทัดคำสั่งที่เป็นประโยชน์
$ flutter create --platforms=android,ios webview_in_flutter Creating project webview_in_flutter... Resolving dependencies in `webview_in_flutter`... Downloading packages... Got dependencies in `webview_in_flutter`. Wrote 74 files. All done! You can find general documentation for Flutter at: https://docs.flutter.dev/ Detailed API documentation is available at: https://api.flutter.dev/ If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev In order to run your application, type: $ cd webview_in_flutter $ flutter run Your application code is in webview_in_flutter/lib/main.dart.
การเพิ่มปลั๊กอิน WebView Flutter เป็นทรัพยากร Dependency
การเพิ่มความสามารถเพิ่มเติมให้กับแอป Flutter นั้นทำได้ง่ายโดยใช้แพ็กเกจ Pub ใน Codelab นี้ คุณจะเพิ่มปลั๊กอิน webview_flutter
ลงในโปรเจ็กต์ เรียกใช้คำสั่งต่อไปนี้ในเทอร์มินัล
$ cd webview_in_flutter $ flutter pub add webview_flutter Resolving dependencies... Downloading packages... leak_tracker 10.0.4 (10.0.5 available) leak_tracker_flutter_testing 3.0.3 (3.0.5 available) material_color_utilities 0.8.0 (0.11.1 available) meta 1.12.0 (1.14.0 available) + plugin_platform_interface 2.1.8 test_api 0.7.0 (0.7.1 available) + webview_flutter 4.7.0 + webview_flutter_android 3.16.0 + webview_flutter_platform_interface 2.10.0 + webview_flutter_wkwebview 3.13.0 Changed 5 dependencies! 5 packages have newer versions incompatible with dependency constraints. Try `flutter pub outdated` for more information.
หากคุณตรวจสอบ pubspec.yaml คุณจะเห็นบรรทัดในส่วนทรัพยากร Dependency สำหรับปลั๊กอิน webview_flutter
กำหนดค่า Android minSDK
หากต้องการใช้ปลั๊กอิน webview_flutter
บน Android คุณต้องตั้งค่า minSDK
เป็น 20
แก้ไขไฟล์ android/app/build.gradle
ดังนี้
android/app/build.gradle
android {
//...
defaultConfig {
applicationId = "com.example.webview_in_flutter"
minSdk = 20 // Modify this line
targetSdk = flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
4. การเพิ่มวิดเจ็ต WebView ลงในแอป Flutter
ในขั้นตอนนี้ คุณจะต้องเพิ่มWebView
ลงในแอปพลิเคชัน WebView เป็นมุมมองเนทีฟที่โฮสต์อยู่ คุณในฐานะนักพัฒนาแอปมีตัวเลือกในการโฮสต์มุมมองเนทีฟเหล่านี้ในแอปของคุณ ใน Android คุณจะมีตัวเลือกระหว่างหน้าจอเสมือน ซึ่งปัจจุบันเป็นค่าเริ่มต้นสำหรับ Android กับองค์ประกอบแบบผสม อย่างไรก็ตาม iOS จะใช้องค์ประกอบแบบผสมเสมอ
หากต้องการพูดคุยอย่างละเอียดเกี่ยวกับความแตกต่างระหว่างการแสดงผลเสมือนกับองค์ประกอบแบบผสม โปรดอ่านเอกสารเกี่ยวกับการโฮสต์มุมมองดั้งเดิมของ Android และ iOS ในแอป Flutter ด้วยมุมมองแพลตฟอร์ม
การใส่ WebView บนหน้าจอ
แทนที่เนื้อหาของ lib/main.dart
ด้วยข้อมูลต่อไปนี้
lib/main.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const WebViewApp(),
),
);
}
class WebViewApp extends StatefulWidget {
const WebViewApp({super.key});
@override
State<WebViewApp> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<WebViewApp> {
late final WebViewController controller;
@override
void initState() {
super.initState();
controller = WebViewController()
..loadRequest(
Uri.parse('https://flutter.dev'),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView'),
),
body: WebViewWidget(
controller: controller,
),
);
}
}
การเรียกใช้บน iOS หรือ Android จะแสดง WebView เป็นหน้าต่างเบราว์เซอร์แบบไม่มีขอบในอุปกรณ์ ซึ่งหมายความว่าเบราว์เซอร์จะแสดงในอุปกรณ์แบบเต็มหน้าจอโดยไม่มีเส้นขอบหรือขอบในรูปแบบใดก็ตาม โดยในขณะที่เลื่อน คุณจะสังเกตเห็นส่วนที่อาจดูแปลกไปเล็กน้อย นั่นเป็นเพราะว่าตอนนี้ JavaScript ปิดใช้งานอยู่และการแสดงผล flutter.dev ต้องใช้ JavaScript อย่างถูกต้อง
การเรียกใช้แอป
เรียกใช้แอป Flutter ใน iOS หรือ Android เพื่อดู WebView ซึ่งแสดงเว็บไซต์ flutter.dev หรือเรียกใช้แอปในโปรแกรมจำลองของ Android หรือเครื่องจำลองของ iOS คุณสามารถแทนที่ URL ของ WebView เริ่มต้นด้วยเว็บไซต์ของคุณเองได้
$ flutter run
สมมติว่าคุณมีเครื่องจำลองหรือโปรแกรมจำลองที่เหมาะสมทำงานอยู่หรือมีอุปกรณ์จริงเชื่อมต่ออยู่ หลังจากคอมไพล์และนำแอปไปใช้กับอุปกรณ์ของคุณแล้ว คุณควรจะเห็นข้อมูลดังนี้
5. การรอฟังเหตุการณ์การโหลดหน้าเว็บ
วิดเจ็ต WebView
จะแสดงเหตุการณ์ความคืบหน้าในการโหลดหน้าเว็บหลายรายการ ซึ่งแอปฟังได้ ระหว่างรอบการโหลดหน้าเว็บ WebView
มีเหตุการณ์การโหลดหน้าเว็บ 3 เหตุการณ์ที่แตกต่างกันที่เริ่มทำงาน ได้แก่ onPageStarted
, onProgress
และ onPageFinished
ในขั้นตอนนี้ คุณจะใช้ตัวบ่งชี้การโหลดหน้าเว็บ ตัวเลือกนี้จะแสดงให้เห็นว่าคุณแสดงเนื้อหา Flutter ในพื้นที่เนื้อหา WebView
ได้
การเพิ่มเหตุการณ์การโหลดหน้าเว็บลงในแอป
สร้างไฟล์ต้นฉบับใหม่ที่ lib/src/web_view_stack.dart
และกรอกเนื้อหาต่อไปนี้ลงในไฟล์
lib/src/web_view_stack.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewStack extends StatefulWidget {
const WebViewStack({super.key});
@override
State<WebViewStack> createState() => _WebViewStackState();
}
class _WebViewStackState extends State<WebViewStack> {
var loadingPercentage = 0;
late final WebViewController controller;
@override
void initState() {
super.initState();
controller = WebViewController()
..setNavigationDelegate(NavigationDelegate(
onPageStarted: (url) {
setState(() {
loadingPercentage = 0;
});
},
onProgress: (progress) {
setState(() {
loadingPercentage = progress;
});
},
onPageFinished: (url) {
setState(() {
loadingPercentage = 100;
});
},
))
..loadRequest(
Uri.parse('https://flutter.dev'),
);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
WebViewWidget(
controller: controller,
),
if (loadingPercentage < 100)
LinearProgressIndicator(
value: loadingPercentage / 100.0,
),
],
);
}
}
โค้ดนี้ได้รวมวิดเจ็ต WebView
ไว้ใน Stack
โดยวางซ้อน WebView
แบบมีเงื่อนไขด้วย LinearProgressIndicator
เมื่อเปอร์เซ็นต์การโหลดหน้าเว็บน้อยกว่า 100% เนื่องจากกรณีนี้เกี่ยวข้องกับสถานะของโปรแกรมที่มีการเปลี่ยนแปลงเมื่อเวลาผ่านไป คุณจึงจัดเก็บสถานะนี้ไว้ในคลาส State
ที่เชื่อมโยงกับ StatefulWidget
หากต้องการใช้วิดเจ็ต WebViewStack
ใหม่นี้ ให้แก้ไข lib/main.dart ดังนี้
lib/main.dart
import 'package:flutter/material.dart';
import 'src/web_view_stack.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const WebViewApp(),
),
);
}
class WebViewApp extends StatefulWidget {
const WebViewApp({super.key});
@override
State<WebViewApp> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<WebViewApp> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView'),
),
body: const WebViewStack(),
);
}
}
เมื่อคุณเรียกใช้แอป ทั้งนี้ขึ้นอยู่กับเงื่อนไขของเครือข่าย และเบราว์เซอร์ได้แคชหน้าเว็บที่คุณกำลังเข้าชมอยู่หรือไม่ คุณจะเห็นตัวบ่งชี้การโหลดหน้าเว็บซ้อนทับเหนือพื้นที่เนื้อหาของ WebView
6. การทำงานกับ WebViewController
การเข้าถึง WebViewController จากวิดเจ็ต WebView
วิดเจ็ต WebView
เปิดใช้การควบคุมแบบเป็นโปรแกรมด้วย WebViewController
ตัวควบคุมนี้จะพร้อมใช้งานหลังจากสร้างวิดเจ็ต WebView
ผ่าน Callback ลักษณะอะซิงโครนัสของความพร้อมใช้งานตัวควบคุมนี้ทำให้เป็นตัวเลือกหลักสำหรับคลาส Completer<T>
อะซิงโครนัสของ Dart
อัปเดต lib/src/web_view_stack.dart
ดังนี้
lib/src/web_view_stack.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewStack extends StatefulWidget {
const WebViewStack({required this.controller, super.key}); // MODIFY
final WebViewController controller; // ADD
@override
State<WebViewStack> createState() => _WebViewStackState();
}
class _WebViewStackState extends State<WebViewStack> {
var loadingPercentage = 0;
// REMOVE the controller that was here
@override
void initState() {
super.initState();
// Modify from here...
widget.controller.setNavigationDelegate(
NavigationDelegate(
onPageStarted: (url) {
setState(() {
loadingPercentage = 0;
});
},
onProgress: (progress) {
setState(() {
loadingPercentage = progress;
});
},
onPageFinished: (url) {
setState(() {
loadingPercentage = 100;
});
},
),
);
// ...to here.
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
WebViewWidget(
controller: widget.controller, // MODIFY
),
if (loadingPercentage < 100)
LinearProgressIndicator(
value: loadingPercentage / 100.0,
),
],
);
}
}
ตอนนี้วิดเจ็ต WebViewStack
ใช้ตัวควบคุมที่สร้างในวิดเจ็ตรอบๆ ซึ่งจะทำให้แชร์ตัวควบคุมของ WebViewWidget
กับส่วนอื่นๆ ของแอปได้อย่างง่ายดาย
การร่างตัวควบคุมทิศทาง
การมี WebView
ที่ใช้งานได้ก็เป็นสิ่งสำคัญ แต่ความสามารถในการย้อนกลับหรือย้อนกลับไปยังประวัติหน้าเว็บ รวมถึงโหลดหน้าเว็บซ้ำได้นั้นจะเป็นชุดการเพิ่มที่มีประโยชน์ โชคดีที่ WebViewController
จะช่วยให้คุณเพิ่มฟังก์ชันนี้ลงในแอปได้
สร้างไฟล์ต้นฉบับใหม่ที่ lib/src/navigation_controls.dart
แล้วกรอกข้อมูลต่อไปนี้
lib/src/navigation_controls.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class NavigationControls extends StatelessWidget {
const NavigationControls({required this.controller, super.key});
final WebViewController controller;
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () async {
final messenger = ScaffoldMessenger.of(context);
if (await controller.canGoBack()) {
await controller.goBack();
} else {
messenger.showSnackBar(
const SnackBar(content: Text('No back history item')),
);
return;
}
},
),
IconButton(
icon: const Icon(Icons.arrow_forward_ios),
onPressed: () async {
final messenger = ScaffoldMessenger.of(context);
if (await controller.canGoForward()) {
await controller.goForward();
} else {
messenger.showSnackBar(
const SnackBar(content: Text('No forward history item')),
);
return;
}
},
),
IconButton(
icon: const Icon(Icons.replay),
onPressed: () {
controller.reload();
},
),
],
);
}
}
วิดเจ็ตนี้ใช้ WebViewController
ที่แชร์กับตัวเองในเวลาสร้างเพื่อให้ผู้ใช้ควบคุม WebView
ได้ผ่านชุด IconButton
การเพิ่มตัวควบคุมการไปยังส่วนต่างๆ ใน AppBar
เมื่อมี WebViewStack
ที่อัปเดตแล้วและ NavigationControls
ที่สร้างขึ้นใหม่อยู่ในมือ ตอนนี้ก็ถึงเวลาที่คุณจะนำข้อมูลทั้งหมดมารวมกันใน WebViewApp
ที่อัปเดตแล้ว ตรงนี้คือส่วนที่เราสร้าง WebViewController
ที่แชร์ หากมี WebViewApp
อยู่ใกล้ด้านบนของโครงสร้างวิดเจ็ตในแอปนี้ การสร้างแอปในระดับนี้จึงเป็นความคิดที่เหมาะสม
อัปเดตไฟล์ lib/main.dart
รายการดังนี้
lib/main.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart'; // ADD
import 'src/navigation_controls.dart'; // ADD
import 'src/web_view_stack.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const WebViewApp(),
),
);
}
class WebViewApp extends StatefulWidget {
const WebViewApp({super.key});
@override
State<WebViewApp> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<WebViewApp> {
// Add from here...
late final WebViewController controller;
@override
void initState() {
super.initState();
controller = WebViewController()
..loadRequest(
Uri.parse('https://flutter.dev'),
);
}
// ...to here.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView'),
// Add from here...
actions: [
NavigationControls(controller: controller),
],
// ...to here.
),
body: WebViewStack(controller: controller), // MODIFY
);
}
}
การเรียกใช้แอปควรแสดงหน้าเว็บที่มีการควบคุมดังนี้
7. การติดตามการนำทางด้วย NavigationDelegate
WebView
ให้ NavigationDelegate,
กับแอปของคุณ ซึ่งช่วยให้แอปติดตามและควบคุมการนำทางหน้าเว็บของวิดเจ็ต WebView
ได้ เมื่อ WebView,
เริ่มต้นการนำทาง เช่น เมื่อผู้ใช้คลิกลิงก์ ระบบจะเรียก NavigationDelegate
สามารถใช้การเรียกกลับ NavigationDelegate
เพื่อควบคุมว่าจะให้ WebView
ดำเนินการตามการนำทางหรือไม่
ลงทะเบียน NavigationDelegate ที่กำหนดเอง
ในขั้นตอนนี้ คุณจะต้องลงทะเบียน Callback ของ NavigationDelegate
เพื่อบล็อกการนำทางไปยัง YouTube.com โปรดทราบว่าการใช้งานที่เรียบง่ายนี้ยังบล็อกเนื้อหา YouTube ในหน้าซึ่งจะปรากฏในหน้าเอกสารประกอบของ Flutter API ต่างๆ อีกด้วย
อัปเดต lib/src/web_view_stack.dart
ดังนี้
lib/src/web_view_stack.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewStack extends StatefulWidget {
const WebViewStack({required this.controller, super.key});
final WebViewController controller;
@override
State<WebViewStack> createState() => _WebViewStackState();
}
class _WebViewStackState extends State<WebViewStack> {
var loadingPercentage = 0;
@override
void initState() {
super.initState();
widget.controller.setNavigationDelegate(
NavigationDelegate(
onPageStarted: (url) {
setState(() {
loadingPercentage = 0;
});
},
onProgress: (progress) {
setState(() {
loadingPercentage = progress;
});
},
onPageFinished: (url) {
setState(() {
loadingPercentage = 100;
});
},
// Add from here...
onNavigationRequest: (navigation) {
final host = Uri.parse(navigation.url).host;
if (host.contains('youtube.com')) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Blocking navigation to $host',
),
),
);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
// ...to here.
),
);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
WebViewWidget(
controller: widget.controller,
),
if (loadingPercentage < 100)
LinearProgressIndicator(
value: loadingPercentage / 100.0,
),
],
);
}
}
ในขั้นตอนต่อไป คุณจะต้องเพิ่มรายการในเมนูเพื่อเปิดใช้การทดสอบ NavigationDelegate
โดยใช้คลาส WebViewController
จึงเป็นเพียงแบบฝึกหัดสำหรับผู้อ่านเพื่อเพิ่มตรรกะของ Callback ให้บล็อกการนำทางแบบเต็มหน้าไปยัง YouTube.com เท่านั้น และยังคงอนุญาตเนื้อหา YouTube แบบในหน้าในเอกสารประกอบของ API
8. การเพิ่มปุ่มเมนูลงใน AppBar
ในอีก 2-3 ขั้นตอนถัดไป คุณจะสร้างปุ่มเมนูในวิดเจ็ต AppBar
ที่ใช้ประเมิน JavaScript, เรียกใช้แชแนล JavaScript และจัดการคุกกี้ โดยรวมแล้ว เป็นเมนูที่มีประโยชน์อย่างแท้จริง
สร้างไฟล์ต้นฉบับใหม่ที่ lib/src/menu.dart
แล้วกรอกข้อมูลต่อไปนี้
lib/src/menu.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
enum _MenuOptions {
navigationDelegate,
}
class Menu extends StatelessWidget {
const Menu({required this.controller, super.key});
final WebViewController controller;
@override
Widget build(BuildContext context) {
return PopupMenuButton<_MenuOptions>(
onSelected: (value) async {
switch (value) {
case _MenuOptions.navigationDelegate:
await controller.loadRequest(Uri.parse('https://youtube.com'));
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
],
);
}
}
เมื่อผู้ใช้เลือกตัวเลือกเมนูไปที่ YouTube ระบบจะดำเนินการตามเมธอด loadRequest
ของ WebViewController
การนำทางนี้จะถูกบล็อกโดย Callback ของ navigationDelegate
ที่คุณสร้างในขั้นตอนก่อนหน้า
หากต้องการเพิ่มเมนูลงในหน้าจอของ WebViewApp
ให้แก้ไข lib/main.dart
ดังนี้
lib/main.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'src/menu.dart'; // ADD
import 'src/navigation_controls.dart';
import 'src/web_view_stack.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const WebViewApp(),
),
);
}
class WebViewApp extends StatefulWidget {
const WebViewApp({super.key});
@override
State<WebViewApp> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<WebViewApp> {
late final WebViewController controller;
@override
void initState() {
super.initState();
controller = WebViewController()
..loadRequest(
Uri.parse('https://flutter.dev'),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView'),
actions: [
NavigationControls(controller: controller),
Menu(controller: controller), // ADD
],
),
body: WebViewStack(controller: controller),
);
}
}
เรียกใช้แอปและแตะรายการในเมนูไปยัง YouTube คุณควรได้รับการต้อนรับด้วย SnackBar แจ้งว่าตัวควบคุมการนำทางบล็อกการนำทางไปยัง YouTube ไว้
9. การประเมิน JavaScript
WebViewController
สามารถประเมินนิพจน์ JavaScript ในบริบทของหน้าปัจจุบัน การประเมิน JavaScript มี 2 วิธี ได้แก่ ใช้ runJavaScript
สำหรับโค้ด JavaScript ที่ไม่แสดงผลค่า ให้ใช้ runJavaScriptReturningResult
หากต้องการเปิดใช้ JavaScript คุณต้องกำหนดค่า WebViewController
โดยตั้งค่าพร็อพเพอร์ตี้ javaScriptMode
เป็น JavascriptMode.unrestricted
โดยค่าเริ่มต้น javascriptMode
จะตั้งค่าเป็น JavascriptMode.disabled
อัปเดตชั้นเรียน _WebViewStackState
โดยเพิ่มการตั้งค่า javascriptMode
ดังนี้
lib/src/web_view_stack.dart
class _WebViewStackState extends State<WebViewStack> {
var loadingPercentage = 0;
@override
void initState() {
super.initState();
widget.controller
..setNavigationDelegate( // Modify this line to use .. instead of .
NavigationDelegate(
onPageStarted: (url) {
setState(() {
loadingPercentage = 0;
});
},
onProgress: (progress) {
setState(() {
loadingPercentage = progress;
});
},
onPageFinished: (url) {
setState(() {
loadingPercentage = 100;
});
},
onNavigationRequest: (navigation) {
final host = Uri.parse(navigation.url).host;
if (host.contains('youtube.com')) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Blocking navigation to $host',
),
),
);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..setJavaScriptMode(JavaScriptMode.unrestricted); // Add this line
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
WebViewWidget(
controller: widget.controller,
),
if (loadingPercentage < 100)
LinearProgressIndicator(
value: loadingPercentage / 100.0,
),
],
);
}
}
เมื่อ WebViewWidget
เรียกใช้ JavaScript ได้แล้ว คุณสามารถเพิ่มตัวเลือกลงในเมนูเพื่อใช้เมธอด runJavaScriptReturningResult
ได้
แปลงคลาสเมนูเป็น StatefulWidget ในการใช้เครื่องมือแก้ไขหรือแป้นพิมพ์ แก้ไข lib/src/menu.dart
เพื่อให้ตรงกับรายการต่อไปนี้
lib/src/menu.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
enum _MenuOptions {
navigationDelegate,
userAgent, // Add this line
}
class Menu extends StatefulWidget { // Convert to StatefulWidget
const Menu({required this.controller, super.key});
final WebViewController controller;
@override // Add from here
State<Menu> createState() => _MenuState();
}
class _MenuState extends State<Menu> { // To here.
@override
Widget build(BuildContext context) {
return PopupMenuButton<_MenuOptions>(
onSelected: (value) async {
switch (value) {
case _MenuOptions.navigationDelegate: // Modify from here
await widget.controller
.loadRequest(Uri.parse('https://youtube.com'));
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
)); // To here.
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
const PopupMenuItem<_MenuOptions>( // Add from here
value: _MenuOptions.userAgent,
child: Text('Show user-agent'),
), // To here.
],
);
}
}
เมื่อคุณแตะที่ "แสดง user-agent" ตัวเลือกเมนู ผลของการเรียกใช้นิพจน์ JavaScript navigator.userAgent
จะปรากฏใน Snackbar
เมื่อเรียกใช้แอป คุณอาจสังเกตเห็นว่าหน้า Flutter.dev ดูแตกต่างออกไป ซึ่งเป็นผลมาจากการทำงานโดยเปิดใช้งาน JavaScript
10. การใช้งานแชแนล JavaScript
แชแนล JavaScript ทำให้แอปของคุณสามารถลงทะเบียนตัวแฮนเดิล Callback ในบริบท JavaScript ของ WebViewWidget
ซึ่งสามารถเรียกใช้เพื่อถ่ายทอดค่ากลับไปยังโค้ด Dart ของแอปได้ ในขั้นตอนนี้ คุณจะต้องลงทะเบียนช่อง SnackBar
ซึ่งระบบจะเรียกใช้ด้วยผลลัพธ์ของ XMLHttpRequest
อัปเดตชั้นเรียน WebViewStack
ดังนี้
lib/src/web_view_stack.dart
class WebViewStack extends StatefulWidget {
const WebViewStack({required this.controller, super.key});
final WebViewController controller;
@override
State<WebViewStack> createState() => _WebViewStackState();
}
class _WebViewStackState extends State<WebViewStack> {
var loadingPercentage = 0;
@override
void initState() {
super.initState();
widget.controller
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (url) {
setState(() {
loadingPercentage = 0;
});
},
onProgress: (progress) {
setState(() {
loadingPercentage = progress;
});
},
onPageFinished: (url) {
setState(() {
loadingPercentage = 100;
});
},
onNavigationRequest: (navigation) {
final host = Uri.parse(navigation.url).host;
if (host.contains('youtube.com')) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Blocking navigation to $host',
),
),
);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
// Modify from here...
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'SnackBar',
onMessageReceived: (message) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(message.message)));
},
);
// ...to here.
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
WebViewWidget(
controller: widget.controller,
),
if (loadingPercentage < 100)
LinearProgressIndicator(
value: loadingPercentage / 100.0,
),
],
);
}
}
สำหรับแชแนล JavaScript แต่ละแชแนลใน Set
ออบเจ็กต์แชแนลจะใช้ได้ในบริบทของ JavaScript เป็นพร็อพเพอร์ตี้หน้าต่างที่มีชื่อเดียวกับแชแนล JavaScript name
การใช้สิ่งนี้จากบริบท JavaScript จะเกี่ยวข้องกับการเรียกใช้ postMessage
บนแชแนล JavaScript เพื่อส่งข้อความที่ส่งไปยังเครื่องจัดการ Callback onMessageReceived
ของ JavascriptChannel
ที่มีชื่อ
หากต้องการใช้ประโยชน์จากแชแนล JavaScript ที่เพิ่มไว้ด้านบน ให้เพิ่มรายการในเมนูที่เรียกใช้ XMLHttpRequest
ในบริบท JavaScript และส่งกลับผลลัพธ์โดยใช้แชแนล JavaScript SnackBar
เมื่อ WebViewWidget
รู้เกี่ยวกับแชแนล JavaScript ของเราแล้ว,
คุณจะเพิ่มตัวอย่างเพื่อขยายแอปเพิ่มเติมได้ โดยเพิ่ม PopupMenuItem
ในชั้นเรียน Menu
และเพิ่มฟังก์ชันพิเศษ
อัปเดต _MenuOptions
ด้วยตัวเลือกเมนูเพิ่มเติมโดยการเพิ่มค่าการแจงนับ javascriptChannel
และเพิ่มการใช้งานลงในคลาส Menu
ดังนี้
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel, // Add this option
}
class Menu extends StatefulWidget {
const Menu({required this.controller, super.key});
final WebViewController controller;
@override
State<Menu> createState() => _MenuState();
}
class _MenuState extends State<Menu> {
@override
Widget build(BuildContext context) {
return PopupMenuButton<_MenuOptions>(
onSelected: (value) async {
switch (value) {
case _MenuOptions.navigationDelegate:
await widget.controller
.loadRequest(Uri.parse('https://youtube.com'));
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
));
case _MenuOptions.javascriptChannel: // Add from here
await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
if (req.status == 200) {
let response = JSON.parse(req.responseText);
SnackBar.postMessage("IP Address: " + response.ip);
} else {
SnackBar.postMessage("Error: " + req.status);
}
}
req.send();'''); // To here.
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.userAgent,
child: Text('Show user-agent'),
),
const PopupMenuItem<_MenuOptions>( // Add from here
value: _MenuOptions.javascriptChannel,
child: Text('Lookup IP Address'),
), // To here.
],
);
}
}
JavaScript นี้จะทำงานเมื่อผู้ใช้เลือกตัวเลือกเมนูตัวอย่างช่อง JavaScript
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
if (req.status == 200) {
SnackBar.postMessage(req.responseText);
} else {
SnackBar.postMessage("Error: " + req.status);
}
}
req.send();
โค้ดนี้จะส่งคำขอ GET
ไปยัง API ที่อยู่ IP สาธารณะ โดยส่งคืนที่อยู่ IP ของอุปกรณ์ ผลลัพธ์นี้แสดงใน SnackBar
โดยการเรียกใช้ postMessage
ใน JavascriptChannel
ของ SnackBar
11. การจัดการคุกกี้
แอปของคุณจัดการคุกกี้ใน WebView
ได้โดยใช้คลาส CookieManager
ในขั้นตอนนี้ คุณจะแสดงรายการคุกกี้ ล้างรายการคุกกี้ ลบคุกกี้ และตั้งค่าคุกกี้ใหม่ เพิ่มรายการลงใน _MenuOptions
สำหรับกรณีการใช้งานคุกกี้แต่ละกรณีดังนี้
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
// Add from here ...
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// ... to here.
}
การเปลี่ยนแปลงที่เหลือในขั้นตอนนี้จะมุ่งเน้นที่คลาส Menu
ซึ่งรวมถึงการแปลงคลาส Menu
จากไม่เก็บสถานะเป็นเก็บสถานะ การเปลี่ยนแปลงนี้มีความสำคัญเนื่องจาก Menu
ต้องเป็นเจ้าของ CookieManager
และสถานะที่เปลี่ยนแปลงได้ในวิดเจ็ตแบบไม่เก็บสถานะเป็นชุดค่าผสมที่ไม่เหมาะสม
เพิ่ม CookieManager ลงในคลาส State ที่ได้ดังต่อไปนี้:
lib/src/menu.dart
class Menu extends StatefulWidget {
const Menu({required this.controller, super.key});
final WebViewController controller;
@override
State<Menu> createState() => _MenuState();
}
class _MenuState extends State<Menu> {
final cookieManager = WebViewCookieManager(); // Add this line
@override
Widget build(BuildContext context) {
// ...
คลาส _MenuState
จะมีรหัสที่เพิ่มไว้ในคลาส Menu
ก่อนหน้านี้ พร้อมกับ CookieManager
ที่เพิ่มเข้ามาใหม่ ในชุดส่วนถัดไป คุณจะเพิ่มฟังก์ชันตัวช่วยใน _MenuState
ซึ่งจะเรียกใช้โดยรายการในเมนูที่ยังไม่ได้เพิ่ม
ดูรายการคุกกี้ทั้งหมด
และจะใช้ JavaScript เพื่อดูรายการคุกกี้ทั้งหมด เพื่อให้บรรลุเป้าหมายนี้ ให้เพิ่มเมธอด Help ในตอนท้ายของคลาส _MenuState
ที่เรียกว่า _onListCookies
เมื่อใช้เมธอด runJavaScriptReturningResult
เมธอด Help ของคุณจะเรียกใช้ document.cookie
ในบริบท JavaScript และแสดงผลรายการคุกกี้ทั้งหมด
เพิ่มข้อมูลต่อไปนี้ในชั้นเรียน _MenuState
:
lib/src/menu.dart
Future<void> _onListCookies(WebViewController controller) async {
final String cookies = await controller
.runJavaScriptReturningResult('document.cookie') as String;
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(cookies.isNotEmpty ? cookies : 'There are no cookies.'),
),
);
}
ล้างคุกกี้ทั้งหมด
หากต้องการล้างคุกกี้ทั้งหมดใน WebView ให้ใช้เมธอด clearCookies
ของคลาส CookieManager
เมธอดจะแสดง Future<bool>
ที่เปลี่ยนเป็น true
หาก CookieManager
ล้างคุกกี้ และ false
หากไม่มีคุกกี้ให้ล้าง
เพิ่มข้อมูลต่อไปนี้ในชั้นเรียน _MenuState
:
lib/src/menu.dart
Future<void> _onClearCookies() async {
final hadCookies = await cookieManager.clearCookies();
String message = 'There were cookies. Now, they are gone!';
if (!hadCookies) {
message = 'There were no cookies to clear.';
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
),
);
}
เพิ่มคุกกี้
การเพิ่มคุกกี้สามารถทำได้โดยเรียกใช้ JavaScript API ที่ใช้เพิ่มคุกกี้ลงในเอกสาร JavaScript มีการบันทึกข้อมูลเชิงลึกเกี่ยวกับ MDN
เพิ่มข้อมูลต่อไปนี้ในชั้นเรียน _MenuState
:
lib/src/menu.dart
Future<void> _onAddCookie(WebViewController controller) async {
await controller.runJavaScript('''var date = new Date();
date.setTime(date.getTime()+(30*24*60*60*1000));
document.cookie = "FirstName=John; expires=" + date.toGMTString();''');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Custom cookie added.'),
),
);
}
การตั้งค่าคุกกี้ด้วย CookieManager
นอกจากนี้ คุกกี้ยังตั้งค่าโดยใช้ CookieManager ได้ด้วยดังต่อไปนี้
เพิ่มข้อมูลต่อไปนี้ในชั้นเรียน _MenuState
:
lib/src/menu.dart
Future<void> _onSetCookie(WebViewController controller) async {
await cookieManager.setCookie(
const WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev'),
);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Custom cookie is set.'),
),
);
}
นำคุกกี้ออก
การนำคุกกี้ออกเกี่ยวข้องกับการเพิ่มคุกกี้โดยมีการกำหนดวันที่หมดอายุไว้ในอดีต
เพิ่มข้อมูลต่อไปนี้ในชั้นเรียน _MenuState
:
lib/src/menu.dart
Future<void> _onRemoveCookie(WebViewController controller) async {
await controller.runJavaScript(
'document.cookie="FirstName=John; expires=Thu, 01 Jan 1970 00:00:00 UTC" ');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Custom cookie removed.'),
),
);
}
การเพิ่มรายการในเมนู CookieManager
แค่เพิ่มตัวเลือกเมนู แล้วต่อสายเข้ากับวิธีการของ Helper ที่คุณเพิ่งเพิ่มลงไป อัปเดตชั้นเรียน _MenuState
ดังนี้
lib/src/menu.dart
class _MenuState extends State<Menu> {
final cookieManager = WebViewCookieManager();
@override
Widget build(BuildContext context) {
return PopupMenuButton<_MenuOptions>(
onSelected: (value) async {
switch (value) {
case _MenuOptions.navigationDelegate:
await widget.controller
.loadRequest(Uri.parse('https://youtube.com'));
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
));
case _MenuOptions.javascriptChannel:
await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
if (req.status == 200) {
let response = JSON.parse(req.responseText);
SnackBar.postMessage("IP Address: " + response.ip);
} else {
SnackBar.postMessage("Error: " + req.status);
}
}
req.send();''');
case _MenuOptions.clearCookies: // Add from here
await _onClearCookies();
case _MenuOptions.listCookies:
await _onListCookies(widget.controller);
case _MenuOptions.addCookie:
await _onAddCookie(widget.controller);
case _MenuOptions.setCookie:
await _onSetCookie(widget.controller);
case _MenuOptions.removeCookie:
await _onRemoveCookie(widget.controller); // To here.
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.userAgent,
child: Text('Show user-agent'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.javascriptChannel,
child: Text('Lookup IP Address'),
),
const PopupMenuItem<_MenuOptions>( // Add from here
value: _MenuOptions.clearCookies,
child: Text('Clear cookies'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.listCookies,
child: Text('List cookies'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.addCookie,
child: Text('Add cookie'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.setCookie,
child: Text('Set cookie'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.removeCookie,
child: Text('Remove cookie'),
), // To here.
],
);
}
การใช้ CookieManager
หากต้องการใช้ฟังก์ชันทั้งหมดที่คุณเพิ่งเพิ่มลงในแอป ให้ลองทำตามขั้นตอนต่อไปนี้
- เลือกแสดงรายการคุกกี้ ระบบควรแสดงรายการคุกกี้ Google Analytics ที่ตั้งค่าโดย flutter.dev
- เลือกล้างคุกกี้ ระบบจะรายงานว่าได้ล้างคุกกี้แล้ว
- เลือกล้างคุกกี้อีกครั้ง ระบบควรรายงานว่าไม่มีคุกกี้ที่สามารถล้างได้
- เลือกแสดงรายการคุกกี้ ระบบควรรายงานว่าไม่มีคุกกี้
- เลือกเพิ่มคุกกี้ ระบบควรรายงานคุกกี้ว่าเพิ่มแล้ว
- เลือก Set cookie ระบบจะรายงานคุกกี้ตามที่ตั้งค่าไว้
- เลือกแสดงรายการคุกกี้ แล้วเลือกนำคุกกี้ออก
12. โหลดเนื้อหา ไฟล์ และสตริง HTML ของ Flutter ใน WebView
แอปของคุณโหลดไฟล์ HTML โดยใช้วิธีต่างๆ และแสดงใน WebView ได้ ในขั้นตอนนี้ คุณจะได้โหลดเนื้อหา Flutter ที่ระบุไว้ในไฟล์ pubspec.yaml
โหลดไฟล์ที่อยู่ในเส้นทางที่ระบุและโหลดหน้าเว็บโดยใช้สตริง HTML
หากคุณต้องการโหลดไฟล์ที่อยู่บนเส้นทางที่ระบุ คุณจะต้องเพิ่ม path_provider
ลงใน pubspec.yaml
นี่คือปลั๊กอิน Flutter สำหรับค้นหาสถานที่ที่ใช้กันโดยทั่วไปในระบบไฟล์
เรียกใช้คำสั่งต่อไปนี้ในบรรทัดคำสั่ง
$ flutter pub add path_provider
สำหรับการโหลดเนื้อหา เราต้องระบุเส้นทางไปยังเนื้อหาใน pubspec.yaml
ใน pubspec.yaml
ให้เพิ่มบรรทัดต่อไปนี้
pubspec.yaml
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# Add from here
assets:
- assets/www/index.html
- assets/www/styles/style.css
# ... to here.
หากต้องการเพิ่มชิ้นงานลงในโปรเจ็กต์ ให้ทำตามขั้นตอนต่อไปนี้
- สร้างไดเรกทอรีใหม่ด้วยชื่อ
assets
ในโฟลเดอร์รูทของโปรเจ็กต์ - สร้างไดเรกทอรีใหม่ด้วยชื่อ
www
ในโฟลเดอร์assets
- สร้างไดเรกทอรีใหม่ด้วยชื่อ
styles
ในโฟลเดอร์www
- สร้างไฟล์ใหม่ด้วยชื่อ
index.html
ในโฟลเดอร์www
- สร้างไฟล์ใหม่ด้วยชื่อ
style.css
ในโฟลเดอร์styles
คัดลอกและวางโค้ดต่อไปนี้ในไฟล์ index.html
<!DOCTYPE html>
<!-- Copyright 2013 The Flutter Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<html lang="en">
<head>
<title>Load file or HTML string example</title>
<link rel="stylesheet" href="styles/style.css" />
</head>
<body>
<h1>Local demo page</h1>
<p>
This is an example page used to demonstrate how to load a local file or HTML
string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
webview</a> plugin.
</p>
</body>
</html>
สำหรับ style.css ให้ใช้บรรทัดต่อไปนี้ในการตั้งค่ารูปแบบส่วนหัว HTML:
h1 {
color: blue;
}
เมื่อตั้งค่าชิ้นงานและพร้อมใช้งานแล้ว คุณใช้วิธีที่จำเป็นในการโหลดและแสดงเนื้อหา ไฟล์ หรือสตริง HTML ของ Flutter ได้
โหลดเนื้อหา Flutter
สำหรับการโหลดชิ้นงานที่เพิ่งสร้างขึ้น เพียงแค่เรียกใช้เมธอด loadFlutterAsset
โดยใช้ WebViewController
แล้วระบุเส้นทางไปยังชิ้นงานเป็นพารามิเตอร์ เพิ่มเมธอดต่อไปนี้ต่อท้ายโค้ดของคุณ:
lib/src/menu.dart
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
โหลดไฟล์ในเครื่อง
สำหรับการโหลดไฟล์ในอุปกรณ์ คุณสามารถเพิ่มเมธอดที่จะใช้เมธอด loadFile
อีกครั้งโดยใช้ WebViewController
ซึ่งจะนำ String
ที่มีเส้นทางไปยังไฟล์นั้นออก
คุณต้องสร้างไฟล์ที่มีโค้ด HTML ก่อน เพียงเพิ่มโค้ด HTML เป็นสตริงที่ด้านบนของโค้ดในไฟล์ menu.dart
ใต้การนำเข้า
import 'dart:io'; // Add this line,
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart'; // And this one.
import 'package:webview_flutter/webview_flutter.dart';
// Add from here ...
const String kExamplePage = '''
<!DOCTYPE html>
<html lang="en">
<head>
<title>Load file or HTML string example</title>
</head>
<body>
<h1>Local demo page</h1>
<p>
This is an example page used to demonstrate how to load a local file or HTML
string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
webview</a> plugin.
</p>
</body>
</html>
''';
// ... to here.
หากต้องการสร้าง File
และเขียนสตริง HTML ลงในไฟล์ คุณจะต้องเพิ่มเมธอด 2 วิธี _onLoadLocalFileExample
จะโหลดไฟล์โดยระบุเส้นทางเป็นสตริง ซึ่งแสดงผลโดยเมธอด _prepareLocalFile()
เพิ่มเมธอดต่อไปนี้ลงในโค้ดของคุณ:
Future<void> _onLoadLocalFileExample(
WebViewController controller, BuildContext context) async {
final String pathToIndex = await _prepareLocalFile();
await controller.loadFile(pathToIndex);
}
static Future<String> _prepareLocalFile() async {
final String tmpDir = (await getTemporaryDirectory()).path;
final File indexFile = File('$tmpDir/www/index.html');
await Directory('$tmpDir/www').create(recursive: true);
await indexFile.writeAsString(kExamplePage);
return indexFile.path;
}
โหลดสตริง HTML
การแสดงหน้าเว็บโดยการระบุสตริง HTML เป็นสิ่งที่ค่อนข้างตรงไปตรงมา WebViewController
มีเมธอดที่คุณสามารถใช้ชื่อว่า loadHtmlString
ซึ่งคุณสามารถกำหนดสตริง HTML เป็นอาร์กิวเมนต์ได้ จากนั้น WebView
จะแสดงหน้า HTML ที่ให้ไว้ เพิ่มเมธอดต่อไปนี้ลงในโค้ดของคุณ
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
Future<void> _onLoadLocalFileExample(
WebViewController controller, BuildContext context) async {
final String pathToIndex = await _prepareLocalFile();
await controller.loadFile(pathToIndex);
}
static Future<String> _prepareLocalFile() async {
final String tmpDir = (await getTemporaryDirectory()).path;
final File indexFile = File('$tmpDir/www/index.html');
await Directory('$tmpDir/www').create(recursive: true);
await indexFile.writeAsString(kExamplePage);
return indexFile.path;
}
// Add here ...
Future<void> _onLoadHtmlStringExample(
WebViewController controller, BuildContext context) async {
await controller.loadHtmlString(kExamplePage);
}
// ... to here.
เพิ่มรายการในเมนู
เมื่อตั้งค่าชิ้นงานและพร้อมใช้งานแล้ว รวมถึงมีวิธีอัปเดตเมนูที่มีฟังก์ชันทั้งหมด เพิ่มรายการต่อไปนี้ใน enum _MenuOptions
:
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// Add from here ...
loadFlutterAsset,
loadLocalFile,
loadHtmlString,
// ... to here.
}
เมื่ออัปเดต enum แล้ว คุณจะเพิ่มตัวเลือกเมนูและต่อเข้ากับเมธอด Helper ที่เพิ่งเพิ่มได้ อัปเดตชั้นเรียน _MenuState
ดังนี้
lib/src/menu.dart
class _MenuState extends State<Menu> {
final cookieManager = WebViewCookieManager();
@override
Widget build(BuildContext context) {
return PopupMenuButton<_MenuOptions>(
onSelected: (value) async {
switch (value) {
case _MenuOptions.navigationDelegate:
await widget.controller
.loadRequest(Uri.parse('https://youtube.com'));
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
));
case _MenuOptions.javascriptChannel:
await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
if (req.status == 200) {
let response = JSON.parse(req.responseText);
SnackBar.postMessage("IP Address: " + response.ip);
} else {
SnackBar.postMessage("Error: " + req.status);
}
}
req.send();''');
case _MenuOptions.clearCookies:
await _onClearCookies();
case _MenuOptions.listCookies:
await _onListCookies(widget.controller);
case _MenuOptions.addCookie:
await _onAddCookie(widget.controller);
case _MenuOptions.setCookie:
await _onSetCookie(widget.controller);
case _MenuOptions.removeCookie:
await _onRemoveCookie(widget.controller);
case _MenuOptions.loadFlutterAsset: // Add from here
if (!mounted) return;
await _onLoadFlutterAssetExample(widget.controller, context);
case _MenuOptions.loadLocalFile:
if (!mounted) return;
await _onLoadLocalFileExample(widget.controller, context);
case _MenuOptions.loadHtmlString:
if (!mounted) return;
await _onLoadHtmlStringExample(widget.controller, context);
// To here.
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.userAgent,
child: Text('Show user-agent'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.javascriptChannel,
child: Text('Lookup IP Address'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.clearCookies,
child: Text('Clear cookies'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.listCookies,
child: Text('List cookies'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.addCookie,
child: Text('Add cookie'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.setCookie,
child: Text('Set cookie'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.removeCookie,
child: Text('Remove cookie'),
),
const PopupMenuItem<_MenuOptions>( // Add from here
value: _MenuOptions.loadFlutterAsset,
child: Text('Load Flutter Asset'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.loadHtmlString,
child: Text('Load HTML string'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.loadLocalFile,
child: Text('Load local file'),
), // To here.
],
);
}
การทดสอบเนื้อหา ไฟล์ และสตริง HTML
หากต้องการทดสอบว่าโค้ดทำงานที่คุณเพิ่งติดตั้งไปหรือไม่ คุณสามารถเรียกใช้โค้ดบนอุปกรณ์และคลิกรายการเมนูที่เพิ่มเข้ามาใหม่ สังเกตวิธีที่ _onLoadFlutterAssetExample
ใช้ style.css
ที่เราเพิ่มเข้าไปเพื่อเปลี่ยนส่วนหัวของไฟล์ HTML เป็นสีน้ำเงิน
13. เสร็จเรียบร้อย
ขอแสดงความยินดี! คุณทำ Codelab เสร็จสมบูรณ์แล้ว คุณดูโค้ดที่สมบูรณ์สำหรับ Codelab ได้ในที่เก็บ Codelab
ลองดูข้อมูลเพิ่มเติมที่ Flutter Codelab