我正在参加「掘金·启航计划」

前言

Flutter与H5的结合

Flutter是Google开发的一个跨平台的UI开发框架,同时也可以使用Dart编写本地代码,可以兼容各种移动端、Web和桌面端平台。H5是一种基于HTMLCSSJavascript的Web技术,广泛用于构建Web应用程序和网页。

在实际开发中,经常需要将FlutterH5进行结合,以展现更加丰富的应用场景。其中,Flutter中内嵌H5页面并且实现与之通信,可以使应用更加交互性,体验性更好,同时也能极大的扩展应用的功能。

为什么要使用Flutter内嵌H5页面?

Flutter内嵌H5页面有以下几个优势:

  • 可以快速实现Web应用中的某些功能,而无需将整个应用都重写为Flutter
  • 可以出色地展示H5页面,以便将Web内容移植到App中;
  • 可以通过H5页面与Flutter进行交互,获得更加流畅、快捷的用户体验。

综上所述,内嵌H5页面是非常有必要的。

实现方式

Flutter中的WebView组件介绍

Flutter中,可以使用flutter_webview_plugin库实现内嵌H5页面的功能。这个库是Flutter的一个用于Web视图插件的第三方库,依赖于AndroidiOS的原生Web View

安装该库:

dependencies:
flutter_webview_plugin: ^0.4.0+1

这个库提供了两种方式加载Web视图:

  • WebviewScaffold:显示一个工具栏,并在屏幕上方显示一个带有Web视图的Scaffold。
  • Webview:直接在Widget树中包含一个Web视图。

下面是一个简单的Webview使用示例:

import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
class MyWebView extends StatefulWidget {
final String title;
final String url;
MyWebView({
@required this.title,
@required this.url,
});
@override
_MyWebViewState createState() => _MyWebViewState();
}
class _MyWebViewState extends State<MyWebView> {
final flutterWebViewPlugin = FlutterWebviewPlugin();
@override
void initState() {
super.initState();
}
@override
void dispose() {
flutterWebViewPlugin.dispose(); // 组件销毁时需要释放
super.dispose();
}
@override
Widget build(BuildContext context) {
return WebviewScaffold(
appBar: AppBar(
title: Text(widget.title),
),
url: widget.url,
initialChild: Center(
child: CircularProgressIndicator(),
),
);
}
}

这里就生成了一个简单的带AppbarWebview视图。

WebView与H5页面通信方式的比较

在Flutter中,有两种方式实现Flutter与H5的通信:

  • JavascriptChannel:通过Javascript通信。
  • MethodChannel:通过建立Binder通道和Android原生代码通信。

JavascriptChannel在需求短时较为直观、实现相对简单,MethodChannel在处理较复杂的逻辑时,更加清晰明了。

Flutter与H5交互的核心技术WebViewJavascriptChannel

Flutter中,内嵌H5页面并且实现通信最为常见的方式就是使用WebViewJavascriptChannel了。

关于WebViewJavascriptChannel,以下是相关的介绍和使用示例。

WebViewJavascriptChannel接口定义:

abstract class WebViewJavascriptChannel {
void postMessage(String message);
}

Flutter中,需要创建一个WebviewJavascriptChannel对象,并将其提供给WebView组件的构造器,其将在H5中调用。

下面是一个简单的使用WebViewJavascriptChannel的示例:

import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
class MyWebView extends StatefulWidget {
final String title;
final String url;
MyWebView({
@required this.title,
@required this.url,
});
@override
_MyWebViewState createState() => _MyWebViewState();
}
class _MyWebViewState extends State<MyWebView> {
final flutterWebViewPlugin = FlutterWebviewPlugin();
TextEditingController controller = TextEditingController();
@override
void initState() {
super.initState();
flutterWebViewPlugin.onStateChanged.listen((WebViewStateChanged state) {
print("onStateChanged: ${state.type} ${state.url}");
});
flutterWebViewPlugin.onUrlChanged.listen((String url) {
print("onUrlChanged: $url");
setState(() {
controller.text = url;
});
});
flutterWebViewPlugin.onDestroy.listen((_) {
print("onDestroy");
});
}
@override
void dispose() {
flutterWebViewPlugin.dispose(); // 组件销毁时需要释放
super.dispose();
}
void onPageFinished(BuildContext context) {
final channel = MethodChannel('webViewJSChannel');
channel.setMethodCallHandler((MethodCall call) async {
if (call.method == 'webToFlu') {
String para = call.arguments;
//处理接收的参数
print('MethodChannel接收到的内容:' + para);
//回传数据到H5
flutterWebViewPlugin.evalJavascript(
"jsFunction('${para}', '${DateTime.now().toString()}')");
}
});
channel.invokeMethod('fluToWeb', {'msg': 'Flutter start!'});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
TextField(
controller: controller,
readOnly: true,
decoration: InputDecoration(
hintText: 'current url',
contentPadding: EdgeInsets.all(10.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
),
),
),
Expanded(
child: WebviewScaffold(
url: widget.url,
hidden: true,
initialChild: Container(
color: Colors.white,
child: Center(
child: CircularProgressIndicator(),
),
),
withJavascript: true,
hiddenPage: true,
appBar: AppBar(
title: Text(widget.title),
),
javascriptChannels: <JavascriptChannel>[
JavascriptChannel(
name: 'FlutterEvent',
onMessageReceived: (JavascriptMessage msg) {
print(msg.message);
})
].toSet(),
onPageFinished: onPageFinished,
withZoom: false,
),
),
],
),
);
}
}

上面的示例中,在页面加载完成之后,我们首先通过回调方法onPageStartedFlutter中创建了一个MethodChannel,声明了FlutterH5推送方法的名称和需要传递的数据。随后在H5中,接收到Flutter传递的初始化参数后,就可以通过window.Flutter.postMessage()来向Flutter发送数据。当Flutter接收到H5的消息之后,就可以通过MethodChannel写入到Flutter侧的方法,进行处理。

下面是一个Web页面示例:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>test channel</title>
</head>
<body>
<button id='btn'>Click me to send message to Flutter</button>
<p>Tips: 点击按钮即可通过JS打印Hello World并通过MethodChannel发送到Flutter。</p>
<script>
var channel = null;
window.addEventListener("load", function() {
channel = new WebViewJavascriptChannel(bridge);
});
function bridge(msg) {
document.getElementById('log').innerHTML = msg;
var para = "Hello World";
channel.postMessage(JSON.stringify({'type':'channelTest', 'content': para}));
}
function jsFunction(para1, para2) {
console.log('接收到的内容:' + para1);
console.log('当前时间:' + para2);
}
document.getElementById('btn').addEventListener('click', function(e) {
console.log('clicked!');
console.log(channel);
bridge('msg from h5');
}, false);
</script>
</body>
</html>

这个页面中,我们声明了通信的JavaSript Channel,并通过按钮点击事件完成数据的发送,同时在jsFunction()方法中,通过flutterWebViewPlugin.evalJavascript()方法向H5页面回传数据。

实际应用

在Flutter中实现微信、支付宝等第三方授权登陆

在开发中,经常需要使用到微信、支付宝等第三方授权登陆的功能,我们可以在Flutter中内嵌H5页面,通过H5页面来完成授权登陆操作,然后回传数据到Flutter中进行后续的操作。

以下是一份简单的微信授权登陆的示例代码:

import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
class WechatAuthorizationPage extends StatefulWidget {
@override
_WechatAuthorizationPageState createState() =>
_WechatAuthorizationPageState();
}
class _WechatAuthorizationPageState extends State<WechatAuthorizationPage> {
final flutterWebViewPlugin = FlutterWebviewPlugin();
String _url =
"https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxe15b267e25e3f502&redirect_uri=https%3A%2F%2Fs.jianyi-ai.com%2Fapi%2Fselfdiagnose%2Fv1%2Fwechat%2Fcallback&response_type=code&scope=snsapi_userinfo&state=state&connect_redirect=1#wechat_redirect";
@override
void initState() {
super.initState();
flutterWebViewPlugin.onStateChanged.listen((WebViewStateChanged state) {
print("onStateChanged: ${state.type} ${state.url}");
});
flutterWebViewPlugin.onUrlChanged.listen((String url) {
print("onUrlChanged: $url");
});
flutterWebViewPlugin.onDestroy.listen((_) {
print("onDestroy");
});
}
@override
void dispose() {
flutterWebViewPlugin.dispose(); // 组件销毁时需要释放
super.dispose();
}
void onPageFinished(BuildContext context) {
final channel = MethodChannel('webViewJSChannel');
channel.setMethodCallHandler((MethodCall call) async {
if (call.method == 'webToFlu') {
Map para = json.decode(call.arguments);
print("onPageFinished-para=$para");
if (para.containsKey('code')) {
// do something with the code
} else {
// do something with the error
}
}
});
flutterWebViewPlugin.evalJavascript(
'setTimeout(function () { Flutter.postMessage(JSON.stringify({type:"pageLoaded", content:{}})); }, 1000)');
channel.invokeMethod('fluToWeb', {'msg': 'Flutter start!'});
}
void onStateChanged(BuildContext context, WebViewStateChanged state) {
print(
'[INFO]onStateChanged: ${state.type} ${state.url} ${state.canGoBack} ${state.canGoForward}');
if (state.type == WebViewState.finishLoad) {
flutterWebViewPlugin.evalJavascript(
'setTimeout(function () { Flutter.postMessage(JSON.stringify({type:"pageLoaded", content:{}})); }, 1000)');
}
if (state.type == WebViewState.abortLoad) {
// _progressBarVisibility = false;
}
if (state.type == WebViewState.startLoad) {
// _progressBarVisibility = true;
}
}
@override
Widget build(BuildContext context) {
return Container(
child: WebviewScaffold(
url: _url,
hidden: true,
initialChild: Container(
color: Colors.white,
child: Center(
child: CircularProgressIndicator(),
),
),
withJavascript: true,
hiddenPage: true,
appBar: AppBar(
title: Text('微信授权登陆'),
),
javascriptChannels: <JavascriptChannel>[
JavascriptChannel(
name: 'FlutterEvent',
onMessageReceived: (JavascriptMessage msg) {
print(msg.message);
})
].toSet(),
onPageFinished: onPageFinished,
withZoom: false,
//allowFileAccess: true,
userAgent:
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/37.0.0.0 Mobile Safari/537.36",
),
);
}
}

在H5中与Flutter原生端交互:与Flutter全局变量通信、调用原生组件

H5页面中与Flutter原生端进行交互也是非常重要的。在H5中可以通过window调用FlutterAPI,以便获取Flutter全局的变量或者调用原生组件进行处理。

在这里我们介绍两个示例:

1.与Flutter全局变量通信

首先需要在Flutter中注册一个通信事件,例如我们可以创建一个名为globalEvent的事件,用于H5传递数据到Flutter全局变量。

import 'package:flutter/services.dart';
const MethodChannel _channel = MethodChannel('globalEvent');
void main() {
_channel.setMethodCallHandler((MethodCall call) async {
if(call.method == 'setGlobalVariable'){
// 在这里处理H5传递的数据
String data = call.arguments;
globalVariable = data;
}
});
}
// 声明一个全局变量
String globalVariable = '';

接着,在H5中通过window对象调用我们注册的事件globalEvent,将数据传递过来。

// 在H5中传递数据给Flutter
window.flutter_inject_global_variable = function(data) {
window.flutter_inject_global_variable_callback(data);
};
window.flutter_inject_global_variable_callback = function(data) {
window.flutter_inject_global_variable_callback = null;
}
// H5传递数据给Flutter全局变量
window.flutter_inject_global_variable('Hello Flutter!');

当H5调用了window.flutter_inject_global_variable方法时,Flutter会接收到一个名为setGlobalVariable的事件,并将H5传递的数据存储到全局变量globalVariable中。

  1. 调用原生组件

Flutter中可以通过MethodChannel调用原生组件,同时需要在原生端注册一个Flutter的通信事件,例如我们创建一个名为callNativeComponent的事件。

import 'package:flutter/services.dart';
const MethodChannel _channel = MethodChannel('callNativeComponent');
void main() {
_channel.setMethodCallHandler((MethodCall call) async {
if(call.method == 'showDialog'){
// 在这里调用原生组件
}
});
}
// 声明一个全局变量
String globalVariable = '';

接着,在H5中通过window对象调用callNativeComponent事件,使Flutter调用原生组件进行处理。

// H5中调用原生组件
window.call_native_component = function(type, data) {
var params = JSON.stringify({
"type": type,
"data": data
});
return window.flutter_call_native_component(params);
};
// 在H5中调用原生组件
window.call_native_component('show_dialog', {'content': 'Hello Native!'});

H5调用window.call_native_component方法时,Flutter接收到名为showDialog的事件并使用原生组件处理传递的数据。原生端同时需要实现MethodChannel的相应方法,例如showDialog方法。

总结与展望

在本文中,我们介绍了Flutter内嵌H5页面的实现方式和与前端通信的技术。通过使用Flutterflutter_webview_plugin插件,我们能够很方便地在Flutter应用中内嵌H5页面,并且通过JavaScript Channel机制实现与前端的通信。

在前端与Flutter应用之间实现无缝的交互可以带来很多的好处。例如,在Flutter应用中使用H5页面可以方便地引入第三方的页面和组件,并且可以快速构建复杂的组合界面。同时,基于Flutter强大的性能和Flutter Widget的可组合性,可以实现更加复杂和优雅的界面效果。

不过,在实际的开发过程中,还需要注意一些问题。例如,FlutterH5之间图形渲染的差异和性能问题,以及在调试方面可能遇到的困难。同时,由于FlutterH5都在不断发展中,未来仍然需要持续关注技术的变化和趋势。不过随着Flutter应用的普及和技术的不断进步,我们相信Flutter内嵌H5页面将在移动应用开发中发挥更加重要和广泛的作用。

总之,在实际的项目中,应根据具体情况选择使用Flutter内嵌H5页面的方案。在合适的场景下,该技术能够帮助我们更好地利用各类资源,构建更加高效和优秀的移动应用。

posted on   纯爱掌门人  阅读(16)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示