赞
踩
在开发过程中很多时候都需要用WebView展示网页,在android中可以直接使用WebView控件加载网页,iOS也有WKWebView或UIWebView,那么在flutter中如何加载网页?
从以下问题入手:
flutter层不支持webview,加载网页的功能还需要借助原生控件来处理。
由于Webview是一个非常复杂的控件,flutter再重新实现一遍成本非常高,而各个平台都有很完善的WebView控件,故flutter团队提供了嵌入原生WebView的解决方案,flutter通过 PlatformView 使用原生控件。
通过pub.dev搜索以及对比网上文章,发现了几个比较受欢迎的flutter webview插件,
三个插件对比:
对比总结:
下文将使用flutter_inappwebview,进行使用简介,文档地址。
flutter_inappwebview主要功能清单如下:
可以看出,flutter_inappwebview支撑多种WebView的使用方式,下文以 InAppWebView 为例进行介绍,因为InAppWebView是Flutter组件可以嵌入Widget Tree,用法更灵活,更贴近实际需求。
Flutter 支持两种模式集成原生控制:虚拟显示模式 (Virtual displays) 和混合集成模式 (Hybrid composition) :
android.view.View
实例渲染为纹理,因此它不会嵌入到 Android Activity 的视图层次结构中。某些平台交互(例如键盘处理和辅助功能)可能无法正常工作。android.view.View
附加到视图层次结构中。因此,键盘处理和无障碍功能是开箱即用的。在 Android 10 之前,此模式可能会大大降低 Flutter UI 的帧吞吐量 (FPS)。根据具体情况来决定使用哪种模式。flutter webview在iOS平台仅支持Hybrid Composition 模式。
推荐使用Hybrid Composition 模式。
flutter项目配置文件pubspec.yaml中引入依赖:
dependencies:
flutter_inappwebview: ^5.3.2
android项目配置:
android {
compileSdkVersion 29
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
}
}
gradle相应版本:
gradle插件版本 :4.1.* 以及上
gradle版本 :5.6 及以上
注意事项:
flutter_inappwebview需要依赖androidx和swift。
widget中使用,并开启Hybrid Composition模式。
import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; class Browser extends StatefulWidget { const Browser(Key key, this.url, this.title) : super(key: key); final String url; final String title; @override _BrowserState createState() => _BrowserState(); } class _BrowserState extends State<Browser> { final GlobalKey webViewKey = GlobalKey(); InAppWebViewGroupOptions options = InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( useShouldOverrideUrlLoading: true, mediaPlaybackRequiresUserGesture: false, ), /// android 支持HybridComposition android: AndroidInAppWebViewOptions( useHybridComposition: true, ), ios: IOSInAppWebViewOptions( allowsInlineMediaPlayback: true, ), ); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: InAppWebView( key: webViewKey, initialUrlRequest: URLRequest(url: Uri.parse(widget.url)), initialOptions: options, ), ); } }
如果只加载网页不要复杂的定制,这样的配置已经可以啦~
InAppWebview实现了原生WebView(Android)和WKWebView(iOS)中绝大多数接口,将这些原生接口代理到了Flutter层,极大的提高了不同平台的使用效率。
接口组织方式分为两部分:
在使用某个接口功能时,需要先检测相应接口开关是否开启,开启有对应接口回调才会生效,一些常见的生命周期回调函数则不需要配置开关。
因为不同平台本身的差异,整体接口分为三类,通过InAppWebViewGroupOptions类进行配置管理:
接口配置实例:
InAppWebViewGroupOptions options = InAppWebViewGroupOptions( //夸平台配置 crossPlatform: InAppWebViewOptions( useShouldOverrideUrlLoading: true,//加载url拦截功能 useShouldInterceptAjaxRequest: true,//ajax请求拦截 useOnLoadResource: true,//资源加载回调 allowFileAccessFromFileURLs: true,//资源加载 mediaPlaybackRequiresUserGesture: false,//多媒体控制 ), //Android平台配置 android: AndroidInAppWebViewOptions( useHybridComposition: true,//支持HybridComposition useShouldInterceptRequest: true,//请求加载链接,可以用于实现Web离线包 ), //iOS平台配置 ios: IOSInAppWebViewOptions( allowsInlineMediaPlayback: true, ), ); // 使用options配置 InAppWebView( key: webViewKey, initialUrlRequest: URLRequest(url: Uri.parse("https://www.baidu.com/")), initialOptions: options,//配置options // ... )
接口回调实例:
InAppWebView( key: webViewKey, initialUrlRequest: URLRequest(url: Uri.parse("https://www.baidu.com/")), initialOptions: options,// 开关配置项 onWebViewCreated: (InAppWebViewController controller) { print("$TAG onWebViewCreated"); }, onLoadStart: (InAppWebViewController controller, Uri? url) { print("$TAG onLoadStart url:$url"); }, onLoadStop: (InAppWebViewController controller, Uri? url) { print("$TAG onLoadStop url:$url"); }, onLoadError: (InAppWebViewController controller, Uri? url, int code, String message) { print("$TAG onLoadError url:$url code:$code message:$message"); }, onLoadHttpError: (InAppWebViewController controller, Uri? url, int statusCode, String description) { print("$TAG onLoadHttpError url:$url statusCode:$statusCode description:$description"); }, onConsoleMessage: (InAppWebViewController controller, ConsoleMessage consoleMessage) { print("$TAG onConsoleMessage consoleMessage:$consoleMessage"); }, onProgressChanged: (InAppWebViewController controller, int progress) { print("$TAG onProgressChanged progress:$progress"); }, shouldOverrideUrlLoading: (InAppWebViewController controller, NavigationAction navigationAction) async { print("$TAG shouldOverrideUrlLoading navigationAction:$navigationAction"); return null; }, // 资源加载监听器 onLoadResource:(InAppWebViewController controller, LoadedResource resource) { print("$TAG onLoadResource resource:$resource"); }, // 滚动监听器 onScrollChanged: (InAppWebViewController controller, int x, int y) { print("$TAG onScrollChanged x:$x y:$y"); }, onLoadResourceCustomScheme:(InAppWebViewController controller, Uri url) async { print("$TAG onLoadResourceCustomScheme url:$url"); return null; }, onCreateWindow: (InAppWebViewController controller, CreateWindowAction createWindowAction) async { print("$TAG onCreateWindow"); return true; }, onCloseWindow: (InAppWebViewController controller) { print("$TAG onCloseWindow"); }, // 过量滚动监听器 onOverScrolled: (InAppWebViewController controller, int x, int y, bool clampedX, bool clampedY) async { print("$TAG onOverScrolled x:$x y:$y clampedX:$clampedX clampedY:$clampedY"); }, //Android特有功能,请求加载链接,可以拦截资源加载,并替换为本地Web离线包内的资源 androidShouldInterceptRequest: (InAppWebViewController controller, WebResourceRequest request) async { print("$TAG androidShouldInterceptRequest request:$request"); return null; }, //iOS特有功能 iosOnNavigationResponse: (InAppWebViewController controller, IOSWKNavigationResponse navigationResponse) async { return null; }, )
接口名称已经可以表示相应功能了,就不一一加注释了~
InAppWebview与Js通信有多种方式,具体文档地址。
js通信都是通过 InAppWebViewController 实现的,需要先通过onWebViewCreated方法获取InAppWebViewController 对象:
// InAppWebview中获取InAppWebViewController
onWebViewCreated: (InAppWebViewController controller) {
// ...
},
通过InAppWebViewController#addJavaScriptHandler添加js处理器,js的调用将会回调改方法。
// InAppWebview中获取InAppWebViewController
onWebViewCreated: (InAppWebViewController controller) {
// 注册一个JS处理方法,名称为myHandlerName
controller.addJavaScriptHandler(handlerName: 'myHandlerName', callback: (args) {
// 打印js方传递过来的参数
print(args);
// 返回给js方的结果
return {
'bar': 'bar_value', 'baz': 'baz_value'
};
});
},
js代码中:
window.flutter_inappwebview.callHandler('myHandlerName', {a:1,b:2})
flutter_inappwebview为InAppWebView框架自动注入的js对象,方便分发js调用到Flutter。
通过 InAppWebViewController.evaluateJavascript 调用js方法。在使用之前需要和js放约定好通讯协议:
onLoadStop: (controller, url) async {
// 直接调用js代码,并等待结果
final result = await controller.evaluateJavascript(source: """
window.handleFlutterInvoke("{a:1,b:2}");
""");
print("result")
},
其他方式参考上述文档~
Flutter的WebView功能是直接依赖不同平台的WebView实现的,已经知道是通过PlatformView实现的,那他们是怎么组合起来的呢?此处以Android平台为例分析下实现过程。
主要实现步骤:
Android原生实现PlatformView,内部通过getView方法返回具体要用到的Android View
Android原生实现PlatformViewFactory ,内部通过create方法创建前一步的PlatformView实例
注册前一步中的平台视图工厂类,并指定viewTypeId用于区分不同的平台视图。
FlutterActivity子类中:
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
flutterEngine
.platformViewsController
.registry
.registerViewFactory("<platform-view-type>", NativeViewFactory())
}
}
插件中注册:
class PlatformViewPlugin : FlutterPlugin {
override fun onAttachedToEngine(binding: FlutterPluginBinding) {
binding
.platformViewRegistry
.registerViewFactory("<platform-view-type>", NativeViewFactory())
}
override fun onDetachedFromEngine(binding: FlutterPluginBinding) {}
}
Flutter层自定义Widget整合Android和iOS层视图
class CustomFlutterWidget extends StatelessWidget { @override Widget build(BuildContext context) { final defaultTargetPlatform = ...; // Android if (defaultTargetPlatform == TargetPlatform.android) { if (useHybridComposition) {// 混合模式 return PlatformViewLink( viewType: 'viewTypeId',//唯一标识 // ... ); } else { return AndroidView(// 虚拟显示 viewType: 'viewTypeId',//唯一标识 // ... ); } } else if (defaultTargetPlatform == TargetPlatform.iOS) { // iOS return UiKitView(// 混合模式 viewType: 'viewTypeId',//唯一标识 // ... ); } return Text( '$defaultTargetPlatform is not yet supported by the flutter_inappwebview plugin'); } }
通过上述步骤就可以使用特定平台的原生View了,iOS也是同样方式实现细节略有差异~
整体类图还是比较清晰的,Flutter与平台层通信借助MethodChannel~
Flutter提供了多种通信渠道的实现方式,具体文档链接:
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; ... class _MyHomePageState extends State<MyHomePage> { static const platform = const MethodChannel('samples.flutter.io/testMethodChannel'); // 调用通信方法. Future<Null> _getBatteryLevel() async { try { final int result = await platform.invokeMethod('methodName', params); print(result); } on PlatformException catch (e) { } } }
import android.os.Bundle import io.flutter.app.FlutterActivity import io.flutter.plugin.common.MethodChannel import io.flutter.plugins.GeneratedPluginRegistrant class MainActivity() : FlutterActivity() { private val CHANNEL = "samples.flutter.io/testMethodChannel" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) GeneratedPluginRegistrant.registerWith(this) MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result -> if(call.methodName == 'methodName') { Log.d(TAG,"${call.arguments}") // TODO result.success(1) } } } }
Q1:flutter中是否有类似原生的WebView控件?
A1:Flutter没有类似WebView控件,借助平台层实现WebView功能。
Q2:flutter中如何使用WebView加载网页?
A2:借助现网提供的WebView插件即可实现网络加载,其中flutter_inappwebview插件非常优秀,推荐使用。
Q3:flutter的WebView如何与js通信的?
A3:InAppWebView已经实现了一套完整的js通信机制,如果用官方WebView插件,则需要自己实现一套JsBridge同时适用Android和iOS,成本稍高一点。
Q4:flutter中WebView是如何实现?
A4:Flutter本身不提供WebView功能,通过PlatformView去适用各个平台已有的WebView能力,降低了实现成功。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。