2020 Google开发者大会重磅开幕 了解详情

InAppWebView:Flutter中WebView的真正力量

2020 年 7 月 25 日

InAppWebView:Flutter中WebView的真正力量

flutter_inappwebview是什么?它是一个Flutter插件,让你可以把WebView小部件加入到Flutter应用中,从而使用headless WebView或In-App Browser。与其他WebView插件相比,它的功能非常丰富:有很多事件、方法和选项可以用来控制WebView。此外,flutter_inappwebview的每个特性几乎都有文档记录。


本文最初发布于 Flutter 社区,经原作者授权由 InfoQ 中文站翻译并分享。



Flutter InAppWebView


flutter_inappwebview 是什么?它是一个 Flutter 插件,让你可以把 WebView 小部件 加入到 Flutter 应用中,从而使用 headless WebViewIn-App Browser


那么, webview_flutter (官方 Flutter 插件)和 flutter_webview_plugin 有什么区别呢?


与其他 WebView 插件相比,它的功能 非常丰富 :有很多 事件方法选项 可以用来控制 WebView。此外,前者没有提供很好的 API 文档,或者至少是文档不完整。相比之下, flutter_inappwebview 的每个特性几乎都有文档记录(可 在pub.dev上查阅API参考)。


在本文中,我将展示人们在 官方flutter_inappwebview存储库(问题部分)和 StackOverflow 上问到的 InAppWebView 小部件的主要类和一些示例。


主要类概览


该插件主要提供了以下类:


  • InAppWebView:一个Flutter小部件,用于添加整合到Flutter部件树的内联原生WebView。

  • ContextMenu:该类表示WebView的快捷菜单。

  • HeadlessInAppWebView:该类表示处于headless模式的WebView。它可以用来在后台运行WebView,而无需将 InAppWebView 附加到部件树中。

  • InAppBrowser:使用原生WebView的In-App Browser。

  • ChromeSafariBrowser:使用 Chrome Custom Tabs(Android)和 SFSafariViewController(iOS)的In-App Browser。

  • InAppLocalhostServer:该类让你可以创建一个简单的服务器: http://localhost:[port]/ . ,默认 port8080

  • CookieManager:这个类实现了一个单例对象(共享实例),管理WebView实例使用的cookie。

  • HttpAuthCredentialDatabase:该类实现一个管理共享HTTP身份验证凭据缓存的单例对象(共享实例)。

  • WebStorageManager:该类生成一个管理Web存储(供WebView实例使用)的单例对象(共享实例)。


在本文中,我将重点展示 InAppWebView 小部件,这是使用/请求最多的小部件。


添加 InAppWebView 小部件


在应用中添加 InAppWebView 小部件非常简单。它只是一个和任何其他小部件一样的 Flutter 小部件: InAppWebView(initialUrl: 'https://github.com/flutter')


注意 :要在 iOS 上使用它,你需要在应用的 Info.plist 文件中添加一个布尔型属性来选择嵌入的视图预览,键为 io.flutter.embedded_views_preview ,值为 YES


这个小部件有一组初始化属性,可以用于初始化 WebView:


  • initialUrl :初始加载的URL。

  • initialOptions :将会使用的初始WebView选项。

  • gestureRecognizers :指定WebView应该使用的划屏手势。

  • initialData :初始加载的InAppWebViewInitialData,如HTTP字符串。

  • initialFile :初始加载的资产文件(请查看“ 加载assets文件夹下的文件”一节)。

  • initialHeaders :初始使用的头信息。

  • contextMenu :包含自定义选项的快捷菜单。


WebView 可用选项的列表很长,例如,你可以使用 javascriptEnabled 选项启用/禁用 JavaScript,或者使用 cacheEnabled 选项启用/禁用缓存。所有选项的完整列表请查看 这里


使用 InAppWebViewController 控制 WebView


要控制 WebView,则可以使用 InAppWebViewController 类。当 WebView 准备就绪时, onWebViewCreated 回调会返回该控制器。


通过它,你可以控制 WebView 或者访问它的属性,比如使用 getUrl 方法访问当前的 URL。还有其他方法,比如 loadUrl 加载一个新的 URL, postUrl 使用 POST 方法加载包含自定义数据的指定 URL, evaluateJavascript 对传入 WebView 的 JavaScript 代码求值并取得求值结果, takeScreenshot 获取 WebView 视窗的截图(PNG 格式), getCertificate 获取顶级主页的 SSL 证书 或者在没有证书时返回 null 。你可以使用的所有方法的完整列表非常长,感兴趣的话,请点击 这里


InAppWebView 事件


小部件 InAppWebView 提供了各种事件。下面列出了其中的一部分:


  • onLoadStart :当WebView开始加载某个URL时触发该事件;

  • onLoadStop :当WebView完成一个URL的加载时触发该事件;

  • onLoadHttpError :当WebView的主页收到HTTP错误时触发该事件;

  • onConsoleMessage :当WebView收到一条JavaScript控制台消息(如 console.logconsole.error )时触发该事件;

  • shouldOverrideUrlLoading :当一个URL将要在当前的WebView中加载时为宿主应用提供获取控制权的机会;

  • onDownloadStart :当WebView识别到一个可下载的文件时触发该事件;

  • onReceivedHttpAuthRequest :当WebView收到一个HTTP身份验证请求时触发该事件;默认行为是取消请求;

  • onReceivedServerTrustAuthRequest :当WebView需要执行服务器信任认证(证书验证)时触发该事件;

  • onPrint :当在JavaScript端调用 window.print() 时触发该事件;

  • onCreateWindow :当 InAppWebView 请求宿主应用程序创建一个新窗口时触发该事件,例如当尝试用 target="_blank" 打开链接,或者当JavaScript端调用 window.open() 时。


还有 许多许多 !你可以查看 API 参考来了解更多细节。至于 WebView 的选项和方法,完整的清单非常长,感兴趣的话,请点击 这里


InAppWebView 简单示例


下面是一个简单的示例,展示了一个 InAppWebView 小部件、它的当前 URL 和 3 个按钮:一个返回,一个向前,另一个重新加载当前页面。



InAppWebView 示例


完整代码如下:


import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
InAppWebViewController _webViewController;
String url = "";
double progress = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('InAppWebView Example'),
),
body: Container(
child: Column(children: <Widget>[
Container(
padding: EdgeInsets.all(20.0),
child: Text(
"CURRENT URL\n${(url.length > 50) ? url.substring(0, 50) + "..." : url}"),
),
Container(
padding: EdgeInsets.all(10.0),
child: progress < 1.0
? LinearProgressIndicator(value: progress)
: Container()),
Expanded(
child: Container(
margin: const EdgeInsets.all(10.0),
decoration:
BoxDecoration(border: Border.all(color: Colors.blueAccent)),
child: InAppWebView(
initialUrl: "https://flutter.dev/",
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
)
),
onWebViewCreated: (InAppWebViewController controller) {
_webViewController = controller;
},
onLoadStart: (InAppWebViewController controller, String url) {
setState(() {
this.url = url;
});
},
onLoadStop: (InAppWebViewController controller, String url) async {
setState(() {
this.url = url;
});
},
onProgressChanged: (InAppWebViewController controller, int progress) {
setState(() {
this.progress = progress / 100;
});
},
),
),
),
ButtonBar(
alignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Icon(Icons.arrow_back),
onPressed: () {
if (_webViewController != null) {
_webViewController.goBack();
}
},
),
RaisedButton(
child: Icon(Icons.arrow_forward),
onPressed: () {
if (_webViewController != null) {
_webViewController.goForward();
}
},
),
RaisedButton(
child: Icon(Icons.refresh),
onPressed: () {
if (_webViewController != null) {
_webViewController.reload();
}
},
),
],
),
])),
),
);
}
}

复制代码


JavaScript 处理程序(通道)


你可以与 JavaScript 端通信,反之亦然。要添加一个 JavaScript 处理程序,可以使用 _webViewController.addJavaScriptHandler 方法,其中定义了 JavaScript 端调用时会激活的 handlerNamecallbackcallback 可以返回要在 JavaScript 端发送的数据。


反之,在 JavaScript 端,要执行回调处理程序并向 Flutter 发送数据,需要使用 window.flutter_inappwebview.callHandler(handlerName ,…args) 方法,其中 handlerName 是一个字符串,表示调用的处理程序的名称, args 是可以发送到 Flutter 端的可选参数。


为了正确地调用 window.flutter_inappwebview.callHandler(handlerName <String>, ...args) ,你需要等待并监听 JavaScript 事件 flutterInAppWebViewPlatformReady 。平台(Android 或 iOS)一准备好处理 callHandler 方法,就会分派这个事件。


下面是个例子:


import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
InAppWebViewController _webViewController;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('InAppWebView Example'),
),
body: Container(
child: Column(children: <Widget>[
Expanded(
child:InAppWebView(
initialData: InAppWebViewInitialData(
data: """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
</head>
<body>
<h1>JavaScript Handlers (Channels) TEST</h1>
<script>
window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
window.flutter_inappwebview.callHandler('handlerFoo')
.then(function(result) {
// print to the console the data coming
// from the Flutter side.
console.log(JSON.stringify(result));
window.flutter_inappwebview
.callHandler('handlerFooWithArgs', 1, true, ['bar', 5], {foo: 'baz'}, result);
});
});
</script>
</body>
</html>
"""
),
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
)
),
onWebViewCreated: (InAppWebViewController controller) {
_webViewController = controller;
_webViewController.addJavaScriptHandler(handlerName:'handlerFoo', callback: (args) {
// return data to JavaScript side!
return {
'bar': 'bar_value', 'baz': 'baz_value'
};
});
_webViewController.addJavaScriptHandler(handlerName: 'handlerFooWithArgs', callback: (args) {
print(args);
// it will print: [1, true, [bar, 5], {foo: baz}, {bar: bar_value, baz: baz_value}]
});
},
onConsoleMessage: (controller, consoleMessage) {
print(consoleMessage);
// it will print: {message: {"bar":"bar_value","baz":"baz_value"}, messageLevel: 1}
},
),
),
])),
),
);
}
}

复制代码


InAppWebView 中的 WebRTC


目前, WebRTC 只在 Android 上获得了支持,因为,很遗憾,iOS 上的 WKWebView 并没有实现所有的 WebRTC API(你可以跟踪下这个问题: #200)。


我将使用 https://appr.tc/展示一个示例,以测试 WebRTC 的特性。这是一个基于 WebRTC( https://github.com/webrtc/apprtc)的视频聊天演示应用。


要请求相机和麦克风的权限,可以使用 permission_handler插件。另外,你需要将 WebView 选项 mediaPlaybackRequiresUserGesture 设置为 false ,以便自动播放 HTML5 音频和视频。


此外,在 Android 上,你需要实现 androidOnPermissionRequest 事件(它是 Android 特有的事件),当 WebView 请求访问特定资源的权限时会触发该事件(这是 Android 原生的 WebChromeClient.onPermissionRequest事件)。在本例中,该事件用于为 WebRTC API 授予权限。另外,你需要在 AndroidManifest.xml 中添加这些权限:


<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />

复制代码



WebRTC 示例


下面是完整的代码:


import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:permission_handler/permission_handler.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await Permission.camera.request();
await Permission.microphone.request();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: InAppWebViewPage()
);
}
}
class InAppWebViewPage extends StatefulWidget {
@override
_InAppWebViewPageState createState() => new _InAppWebViewPageState();
}
class _InAppWebViewPageState extends State<InAppWebViewPage> {
InAppWebViewController _webViewController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("InAppWebView")
),
body: Container(
child: Column(children: <Widget>[
Expanded(
child: Container(
child: InAppWebView(
initialUrl: "https://appr.tc/r/704328056",
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
mediaPlaybackRequiresUserGesture: false,
debuggingEnabled: true,
),
),
onWebViewCreated: (InAppWebViewController controller) {
_webViewController = controller;
},
androidOnPermissionRequest: (InAppWebViewController controller, String origin, List<String> resources) async {
return PermissionRequestResponse(resources: resources, action: PermissionRequestResponseAction.GRANT);
}
),
),
),
]))
);
}
}

复制代码


如何在 InAppWebView 中支持文件下载?


InAppWebView 可以识别 Android 和 iOS 平台上的可下载文件。要识别可下载的文件,你需要设置 useOnDownloadStart: true 选项,然后监听 onDownloadStart 事件。在 Android 上,你需要在 AndroidManifest.xml 文件中添加写权限:


<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

复制代码


然后需要使用 permission_handler插件请求权限。而要高效地下载文件,可以使用 flutter_downloader插件。下面是使用 http://ovh.net/files/(特别是 http://ovh.net/files/1Mio.dat)测试下载的完整示例:


import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:permission_handler/permission_handler.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await Permission.camera.request();
await Permission.microphone.request();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: InAppWebViewPage()
);
}
}
class InAppWebViewPage extends StatefulWidget {
@override
_InAppWebViewPageState createState() => new _InAppWebViewPageState();
}
class _InAppWebViewPageState extends State<InAppWebViewPage> {
InAppWebViewController _webViewController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("InAppWebView")
),
body: Container(
child: Column(children: <Widget>[
Expanded(
child: Container(
child: InAppWebView(
initialUrl: "https://appr.tc/r/704328056",
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
mediaPlaybackRequiresUserGesture: false,
debuggingEnabled: true,
),
),
onWebViewCreated: (InAppWebViewController controller) {
_webViewController = controller;
},
androidOnPermissionRequest: (InAppWebViewController controller, String origin, List<String> resources) async {
return PermissionRequestResponse(resources: resources, action: PermissionRequestResponseAction.GRANT);
}
),
),
),
]))
);
}
}

复制代码


如你所见,我还使用 path_provider插件获得保存文件的文件夹。


允许自签名 SSL 证书


要允许自签名 SSL 证书,你可以使用 onReceivedServerTrustAuthRequest 事件,并在简单地返回后继续处理该请求:


onReceivedServerTrustAuthRequest: (controller, challenge) async {
return ServerTrustAuthResponse(action: ServerTrustAuthResponseAction.PROCEED);
},

复制代码


如何管理使用 target=”_blank”或“window.open”打开的弹出窗口?


在处理用户点击 target="_blank" 链接或通过 JavaScript 代码 window.open 打开的窗口时 ,可以使用 onCreateWindow 事件。在 Android 上,要启用这个事件,需要将 supportMultipleWindows 选项设置为 true 。另外,为了能够使用 JavaScript,你需要将 javaScriptCanOpenWindowsAutomatically 设置为 true


如果你想处理这些请求,那么你应该让该事件返回 true ,否则,该事件的默认实现不会做任何事情,并返回 false


CreateWindowRequest 表示一个导航请求,其中包含一个 windowId ,可以用来创建一个新的 InAppWebView 实例。本地代码使用这个 windowId 来映射请求和 WebView 从而处理该请求。


另外, CreateWindowRequest 包含请求的 url (在 Android 上,如果弹出窗口是用 JavaScript 代码 window.open 打开的窗口,那么它会是 null )。但是,如果你需要维护 JavaScript 对象引用 Window (使用 window.open 方法创建),比如调用 window.close 方法,那么你应该使用 windowId 创建新的 WebView,而不是使用该 url


下面是一个简单的例子,当用户点击链接时显示一个 AlertDialog 对话框:


import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: InAppWebViewPage()
);
}
}
class InAppWebViewPage extends StatefulWidget {
@override
_InAppWebViewPageState createState() => new _InAppWebViewPageState();
}
class _InAppWebViewPageState extends State<InAppWebViewPage> {
InAppWebViewController _webViewController;
InAppWebViewController _webViewPopupController;
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('InAppWebView Example'),
),
body: SafeArea(
child: Container(
child: InAppWebView(
initialData: InAppWebViewInitialData(
data: """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Flutter InAppWebView</title>
</head>
<body>
<a style="margin: 50px; background: #333; color: #fff; font-weight: bold; font-size: 20px; padding: 15px; display: block;"
href="https://github.com/flutter"
target="_blank">
Click here to open https://github.com/flutter in a popup!
</a>
</body>
</html>
"""
),
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
// set this to true if you are using window.open to open a new window with JavaScript
javaScriptCanOpenWindowsAutomatically: true
),
android: AndroidInAppWebViewOptions(
// on Android you need to set supportMultipleWindows to true,
// otherwise the onCreateWindow event won't be called
supportMultipleWindows: true
)
),
onWebViewCreated: (InAppWebViewController controller) {
_webViewController = controller;
},
onCreateWindow: (controller, createWindowRequest) async {
print("onCreateWindow");
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Container(
width: MediaQuery.of(context).size.width,
height: 400,
child: InAppWebView(
// Setting the windowId property is important here!
windowId: createWindowRequest.windowId,
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
),
),
onWebViewCreated: (InAppWebViewController controller) {
_webViewPopupController = controller;
},
onLoadStart: (InAppWebViewController controller, String url) {
print("onLoadStart popup $url");
},
onLoadStop: (InAppWebViewController controller, String url) {
print("onLoadStop popup $url");
},
),
),
);
},
);
return true;
},
),
),
),
),
);
}
}

复制代码



onCreateWindow 事件弹出窗口示例


处理 whatsapp:、fb:、tel:、mailto:等平台 URL


一般来说,WebView 不知道如何处理 whatsapp:tel:fb: 协议/方案。要捕获使用这些自定义协议/方案发出的请求,可以使用 shouldOverrideUrlLoading 事件(需要通过 useShouldOverrideUrlLoading: true 选项启用它)。


这样你就可以取消对 WebView 的请求,取而代之打开 App,比如说使用 url_launcher 插件:


initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
useShouldOverrideUrlLoading: true
),
),
shouldOverrideUrlLoading: (controller, request) async {
var url = request.url;
var uri = Uri.parse(url);
if (!["http", "https", "file",
"chrome", "data", "javascript",
"about"].contains(uri.scheme)) {
if (await canLaunch(url)) {
// Launch the App
await launch(
url,
);
// and cancel the request
return ShouldOverrideUrlLoadingAction.CANCEL;
}
}
return ShouldOverrideUrlLoadingAction.ALLOW;
},

复制代码


处理 WebView Cookies


要处理 WebView 的 cookie,可以使用 CookieManager 类,它实现了一个单例对象(共享实例)。在 Android 上,它是使用 CookieManager类实现的。在 iOS 上,它则是使用了 WKHTTPCookieStore类。


下面是一个如何设置 cookie 的例子:


CookieManager _cookieManager = CookieManager.instance();
final expiresDate =
DateTime.now().add(Duration(days: 3)).millisecondsSinceEpoch;_cookieManager.setCookie(
url: "https://flutter.dev/",
name: "session",
value: "54th5hfdcfg34",
domain: ".flutter.dev",
expiresDate: expiresDate,
isSecure: true,
);

复制代码


自定义快捷菜单


你可以自定义 WebView 的快捷菜单,添加自定义菜单项,和/或隐藏默认的系统菜单项。对于每个自定义菜单项,你可以声明一个回调 action ,当用户单击时调用它。作为示例,我将添加一个名为 Special 的自定义菜单项,并定义一个回调动作,使用选中的文本向用户显示 JavaScript window.alert



自定义菜单项示例


下面是完整的代码:


import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: InAppWebViewPage()
);
}
}
class InAppWebViewPage extends StatefulWidget {
@override
_InAppWebViewPageState createState() => new _InAppWebViewPageState();
}
class _InAppWebViewPageState extends State<InAppWebViewPage> {
InAppWebViewController _webViewController;
ContextMenu contextMenu;
@override
void initState() {
super.initState();
contextMenu = ContextMenu(
menuItems: [
ContextMenuItem(androidId: 1, iosId: "1", title: "Special", action: () async {
print("Menu item Special clicked!");
var selectedText = await _webViewController.getSelectedText();
await _webViewController.clearFocus();
await _webViewController.evaluateJavascript(source: "window.alert('You have selected: $selectedText')");
})
],
options: ContextMenuOptions(
hideDefaultSystemContextMenuItems: false
),
onCreateContextMenu: (hitTestResult) async {
print("onCreateContextMenu");
print(hitTestResult.extra);
print(await _webViewController.getSelectedText());
},
onHideContextMenu: () {
print("onHideContextMenu");
},
onContextMenuActionItemClicked: (contextMenuItemClicked) async {
var id = (Platform.isAndroid) ? contextMenuItemClicked.androidId : contextMenuItemClicked.iosId;
print("onContextMenuActionItemClicked: " + id.toString() + " " + contextMenuItemClicked.title);
}
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("InAppWebView")
),
body: Container(
child: Column(children: <Widget>[
Expanded(
child: Container(
child: InAppWebView(
initialUrl: "https://github.com/flutter",
contextMenu: contextMenu,
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
),
),
onWebViewCreated: (InAppWebViewController controller) {
_webViewController = controller;
},
),
),
),
]))
);
}
}

复制代码


总结


在本文中,我简单介绍了 flutter_inappwebview 插件,特别是 InAppWebView 小部件。该插件正在持续开发中(在撰写本文时,其最新版本是 4.0.0 )。如果想要了解它所有的特性,我建议查看 API 参考文档。如果有任何新的特性请求/bug 修复,则可以使用存储库的 Issue 部分。


下一篇文章将介绍如何使用这个插件实现一个功能齐全的浏览器。今天就到这里!我希望它为你的 Flutter 应用开辟了新的用例。欢迎关注Flutter社区Twitter


原文链接


InAppWebView: The Real Power of WebViews in Flutter


2020 年 7 月 25 日 08:00 3182
用户头像
蔡芳芳 InfoQ高级编辑

发布了 474 篇内容, 共 217.4 次阅读, 收获喜欢 1266 次。

关注

评论

发布
暂无评论
发现更多内容

Nacos 1.1.4 与微服务的实践经验记录

itfinally

Java 微服务 nacos

Boyer-Moore 算法

Kenn

算法 数组 Boyer-Moore

Phantomjs、Selenium之后浪Puppeteer

飞哥

职场“35岁现象”:焦虑 or 出路?是时候说出真相了!

狂师

职场 成长 软件测试 测试 软件开发

制作Unknown Pleasures效果图的3种方法

张云金_GISer

设计 T恤 GIS 地图

程序员陪娃漫画系列——上学路上

孙苏勇

程序员 生活 程序员人生 陪伴 漫画

Kafka系列第2篇:安装测试

z小赵

大数据 kafka 推荐 实时计算

太极宗师与华晨宇

伯薇

水平思考力 电视剧 综艺节目 歌手

为AndroidApk添加系统级签名

Howe

Java android

MyBatis核心功能介绍

Java收录阁

mybatis

Java并发编程系列——锁顺序

孙苏勇

Java Java并发 并发编程 多线程

为什么每个软件人都要懂点系统架构?

刘华Kenneth

架构 DevOps 高可用 敏捷 高并发

Spring中的测试类~简洁方便

程序员的时光

spring

Flutter引擎源码解读-Flutter是如何在iOS上运行起来的

稻子

flutter ios 移动应用 跨平台 dart

记录自有意义

彭宏豪95

人生 写作 感悟 记录

如何梳理画出牛逼的、高大上的架构图?

狂师

程序员 企业架构 开发者 软件测试 软件开发

JAVA中Base64加密与解密

Howe

Java base64 加密解密

动态规划问题的思路和技巧

Kenn

算法 动态规划

Kafka系列第1篇:Kafka是什么?它能干什么?

z小赵

大数据 kafka 推荐 实时计算

如何优雅的接收正在运行古董代码?

冰临深渊

项目管理 架构

Java新技术:文字块

范学雷

Java 编程语言

缓存的五种设计模式

Rayjun

缓存

Redis学习笔记(概述)

编程随想曲

redis

深入浅出逻辑组合电路(1)

顾洋琛

学习笔记 电子技术 大学生日常

20 大类,100+ 网络副业兼职平台汇总推荐

一尘观世界

程序员 自由职业 副业 赚钱

Kafka系列第4篇:消息发送时,网络“偷偷”帮忙做的那点事儿

z小赵

kafka 推荐 实时计算

KubeFATE: 用云原生技术赋能联邦学习(二)

亨利笔记

Kubernetes 云原生 k8s FATE KUBEFATE

​成功的人,都是 “狠角色”

非著名程序员

程序员 提升认知 成功学 自律

聊聊测试工程师的价值

鱼贩

软件测试 质量 测试工程师产出 测试的价值

周日福利来了

志学Python

Python 福利 python教程 python视频教程

游戏夜读 | 2020周记(4.3-4.10)

game1night

微服务治理平台化探索

微服务治理平台化探索

InAppWebView:Flutter中WebView的真正力量-InfoQ