1. 前言
众所周知,在Android系统App开发中,我们往往会直接跳转到网页。比如微信给你发了一个链接,默认也是在App之内打开的。很多App就只使用一个WebView作为整体框架,这样开发的好处是,只要使用少量的代码即可完成交互。同样在Harmony系统开发中,使用Harmony的ArkUI框架开发应用的时候,官方为我们提供了一个web组件,提供给开发者使用,通过本文学习,我们将了解并学会如何使用HarmonyOS Web组件进行如下操作:
(1)在线网页加载
(2)本地离线网页加载
(3)Web组件常用属性设置
(4)客户端与网页之间的双向通信交互
2. Web组件介绍
Web是提供网页显示能力的组件,具体用法请参考 Web API。
2.1. 接口
Web(options: { src: string, controller?: WebController })
2.1.1. 参数
(1)src:是网页资源地址,可以是本地资源,也可以是网络资源
(2)controller:控制器,可以控制Web组件的各种行为,如网页前进、后退等
2.1.2. 加载在线网页
只需要给src指定引用的网页路径
Web({ src: 'https://www.baidu.com/', controller: this.controller })
2.1.3. 加载本地网页
首先在main/resources/rawfile目录下创建一个HTML文件,然后通过$rawfile引用本地网页资源
Web({ src: $rawfile('index.html'), controller: this.controller })
2.1.4. controller:
(1)forward:前进
(2)backward:后退
(3)refresh:刷新
(4)stop:停止
(5)clearHistory:清除历史
(6)runJavaScript:原生页面调用H5页面
(7)zoom(factor: number):设置网页缩放倍数,zoomAccess为true时才生效
2.1.5. 属性
(1)fileAccess:设置是否开启通过$rawfile(filepath/filename)访问应用中rawfile路径的文件, 默认启用
(2)javaScriptAccess:设置是否允许执行JavaScript脚本,默认允许执行
(3)imageAccess:设置是否允许自动加载图片资源,默认允许
(4)zoomAccess:设置是否支持手势进行缩放,默认允许执行缩放
(5)textZoomAtio:设置页面的文本缩放百分比,默认100,表示100%
2.1.6. 事件
(1)onConfirm事件,网页调用confirm()告警时触发此回调
onConfirm(callback: (event?: { url: string; message: string; result: JsResult }) => boolean)
参数:
url:当前显示弹窗所在网页的URL
message:弹窗中显示的信息
result:通知Web组件用户操作行为
返回值:
当回调返回true时,可以调用系统弹窗能力(包括确认和取消),如自定义弹窗AlertDialog.show;返回false时,触发默认弹窗
(2)onAlert
(3)onBeforeUnload
(4)onConsole
可以通过runJavaScript执行H5方法,H5页面处理完成之后调用confirm方法,原生页面会回调onConfirm事件,在这个回调事件中做一些逻辑处理,这样就完成了原生界面和H5页面的交互
2.2. Web和JavaScript交互
如果需要加载的网页在Web组件中运行JavaScript,则必须为Web组件启用JavaScript功能,默认情况下是允许JavaScript执行的
Web({ src:'https://www.baidu.com', controller:this.controller })
.javaScriptAccess(true)
2.2.1. Web和JavaScript交互
可以在Web组件中使用runJavaScript方法调用JS方法
this.controller.runJavaScript({
script: 'test()',
callback: (result: string)=> {
this.webResult = result;
}});
调用HTML文件中的test方法并将结果返回给Web组件
2.2.2. Web和JavaScript交互
可以使用registerJavaScriptProxy将Web组件中的JavaScript对象注入到window对象中,这样网页中的JS就可以直接调用该对象了
要想registerJavaScriptProxy方法生效,须调用refresh方法
原生调用:
@Entry
@Component
struct WebPage {
@State dataFromHtml: string = ''
controller: WebController = new WebController()
testObj = {
test: (data) => {
this.dataFromHtml = data
return 'ArkUI Web Component';
},
toString: () => {
console.log('Web Component toString');
}
}
build() {
Column() {
Text(this.dataFromHtml).fontSize(20)
Row() {
Button('Register JavaScript To Window').onClick(() => {
this.controller.registerJavaScriptProxy({
object: this.testObj,
name: 'objName',
methodList: ['test', 'toString'],
});
this.controller.refresh();
})
}
Web({ src: $rawfile('index.html'), controller: this.controller })
.javaScriptAccess(true)
}
}
}
Htnl代码:
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>
<button onclick="htmlTest()">调用Web组件里面的方法</button>
</body>
<script type="text/javascript">
function htmlTest() {
str = objName.test("param from Html");
}
</script>
</html>
2.2.3. 案例
@Entry
@Component
struct WebPage {
@State dataFromHtml: string = ''
controller: WebController = new WebController()
testObj = {
test: (data) => {
this.dataFromHtml = data
return 'ArkUI Web Component';
},
toString: () => {
console.log('Web Component toString');
}
}
build() {
Column() {
Text(this.dataFromHtml).fontSize(20)
Row() {
Button('Register JavaScript To Window').onClick(() => {
this.controller.registerJavaScriptProxy({
object: this.testObj,
name: 'objName',
methodList: ['test', 'toString'],
});
this.controller.refresh();
})
}
Web({ src: $rawfile('index.html'), controller: this.controller })
.javaScriptAccess(true)
}
}
}
3. 基础使用
3.1. 创建组件
在pages目录下创建WebComponent .ets的Page页面, 页面上放一个Web组件。在Web组件中通过src指定引用的网页路径,controller为组件的控制器,通过controller绑定Web组件,用于调用Web组件的方法。
import webVeb from '@ohos.web.webview';
@Entry
@Component
struct WebComponent {
webController: webVeb.WebviewController =
new webVeb.WebviewController();
build() {
Column() {
/**
* 组件创建时,
* (1)网络加载https://www.douban.com/
* (2)本地加载$rawfile('app/html/login.html')
*/
Web({ src: $rawfile('app/html/login.html'),
controller: this.webController })
}
}
}
3.2. 添加权限
使用Web组件,访问在线网页,需要给应用配置网络权限,访问在线网页时需添加网络权限:ohos.permission.INTERNET。
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "$string:app_name",
"usedScene": {
"abilities": [
"FormAbility"
],
"when": "inuse"
}
},
]
3.3. 设置样式和属性
Web组件的使用需要添加丰富的页面样式和功能属性。设置height、padding样式可为Web组件添加高和内边距,设置fileAccess属性可为Web组件添加文件访问权限,设置javaScriptAccess属性为true使Web组件具有执行JavaScript代码的能力。
import webVeb from '@ohos.web.webview';
@Entry
@Component
struct WebComponent {
webController: webVeb.WebviewController =
new webVeb.WebviewController();
fileAccess: boolean = true;
build() {
Column() {
Text('Hello world!')
.fontSize(20)
/**
* 组件创建时,
* (1)网络加载https://www.douban.com/
* (2)本地加载$rawfile('app/html/login.html')
*/
Web({ src: $rawfile('app/html/login.html'),
controller: this.webController })
// 设置高和内边距
.height(500)
.padding(20)
// 设置文件访问权限和脚本执行权限
.fileAccess(this.fileAccess)
.javaScriptAccess(true)
}
}
}
3.4. 添加事件和方法
为Web组件添加onProgressChange事件,该事件回调Web引擎加载页面的进度值。将该进度值赋值给Progress组件的value,控制Progress组件的状态。当进度为100%时隐藏Progress组件,Web页面加载完成。
import webVeb from '@ohos.web.webview';
@Entry
@Component
struct WebComponent {
webController: webVeb.WebviewController =
new webVeb.WebviewController();
fileAccess: boolean = true;
@State progress: number = 0
@State hideProgress: boolean = true
build() {
Column() {
Progress({ total: 100, value: this.progress })
.color('#ff5cea20')
.visibility(this.hideProgress
? Visibility.None : Visibility.Visible)
/**
* 组件创建时,
* (1)网络加载https://www.douban.com/
* (2)本地加载$rawfile('app/html/login.html')
*/
Web({ src: $rawfile('app/html/login.html'),
controller: this.webController })
// 设置文件访问权限和脚本执行权限
.fileAccess(this.fileAccess)
.javaScriptAccess(true)
.height('100%')
// 添加onProgressChange事件
.onProgressChange(e => {
this.progress = e.newProgress
// 当进度100%时,进度条消失
if (this.progress == 100) {
this.hideProgress = true
} else {
this.hideProgress = false
}
})
}.backgroundColor('0xFFFFFF')
}
}
3.5. 访问本地Html
实现了一个加载本地网页文件,然后Html网页中调用客户端的方法,进行了一个关闭页面和拉起系统相册的功能,下面开始讲下代码实现。
3.5.1. 本地html文件创建
在entry/src/main/resources/rawfile目录下,我们创建一个index.html文件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<script type="text/javascript" src="./static/js/init-rem.js"></script>
<style type="text/css">
body{
display: flex;
flex-direction: column;
}
.title {
color: black;
font-size: 0.34rem;
}
.btn-layout {
color: red;
font-size: 0.32rem;
margin-top: 0.5rem;
}
.btn-layout2 {
color: blue;
font-size: 0.32rem;
margin-top: 0.5rem;
}
</style>
</head>
<body>
<p class="title">这个是来自本地的html文件</p>
<button class="btn-layout" type="button"
onclick="window.jsBridge.closePage()">点击调用原生关闭页面
</button>
<button class="btn-layout2" type="button"
onclick="window.jsBridge.jumpSystemPicture()">
点击拉起原生系统相册
</button>
</body>
</html>
3.5.2. 本地html文件加载,JS对象注入,Html使用JS对象调用客户端方法
创建一个LocalWebPage页面,加载index.html 文件,如果需要进行网页跟客户端进行交互,我们需要设置往Html中注入一个JS对象,具体如下:
import webVeb from '@ohos.web.webview';
import common from '@ohos.app.ability.common';
import router from '@ohos.router';
@Entry
@Component
struct WebComponent {
webController: webVeb.WebviewController =
new webVeb.WebviewController();
fileAccess: boolean = true;
@State progress: number = 0
@State hideProgress: boolean = true
jsBridge = {
jumpSystemPicture() {
let context = getContext(this) as common.UIAbilityContext;
let want = {
"deviceId": "",
"bundleName": "",
"abilityName": "",
"uri": "",
"type": "image/*",
"action": "android.intent.action.GET_CONTENT",
"parameters":{},
"entities":[]
};
context.startAbility(want);
},
closePage() {
router.back()
}
}
build() {
Column() {
Progress({ total: 100, value: this.progress })
.color('#ff5cea20')
.visibility(this.hideProgress
? Visibility.None : Visibility.Visible)
/**
* 组件创建时,
* (1)网络加载https://www.douban.com/
* (2)本地加载$rawfile('app/html/login.html')
*/
Web({ src: $rawfile('app/index.html'),
controller: this.webController })
// 设置文件访问权限和脚本执行权限
.fileAccess(this.fileAccess)
.javaScriptAccess(true)
.height('100%')
// 添加onProgressChange事件
.onProgressChange(e => {
this.progress = e.newProgress
// 当进度100%时,进度条消失
if (this.progress == 100) {
this.hideProgress = true
} else {
this.hideProgress = false
}
})
//JS对象注入,Html使用JS对象调用客户端方法
.javaScriptAccess(true)
.javaScriptProxy({
object: this.jsBridge,
name: "jsBridge",
methodList: ["closePage","jumpSystemPicture"],
controller: this.webController
})
}.backgroundColor('0xFFFFFF')
}
}
这里我们定义了一个JS对象jsBridge ,定义了两个方法,jumpSystemPicture 和closePage,分别用于html 拉起系统相册和关闭客户端页面,然后使用Web的 javaScriptProxy方法进行JS对象注入设置,具体的配置如上述代码,需要配置对象名称,注入方法。
看下上文中Html 调用JS的代码,比如调用客户端的关闭页面方法,使用
window.jsBridge.closePage() 进行触发。
3.5.3. 客户端调用本地Html网页中的JS方法
在onPageEnd事件中添加runJavaScript方法。onPageEnd事件是网页加载完成时的回调,runJavaScript方法可以执行HTML中的JavaScript脚本。当页面加载完成时,触发onPageEnd事件,调用HTML文件中的test方法,在控制台打印信息。
import webVeb from '@ohos.web.webview';
import common from '@ohos.app.ability.common';
import router from '@ohos.router';
@Entry
@Component
struct WebComponent {
webController: webVeb.WebviewController =
new webVeb.WebviewController();
fileAccess: boolean = true;
@State progress: number = 0
@State hideProgress: boolean = true
jsBridge = {
jumpSystemPicture() {
let context = getContext(this) as common.UIAbilityContext;
let want = {
"deviceId": "",
"bundleName": "",
"abilityName": "",
"uri": "",
"type": "image/*",
"action": "android.intent.action.GET_CONTENT",
"parameters": {},
"entities": []
};
context.startAbility(want);
},
closePage() {
router.back()
}
}
build() {
Column() {
Progress({ total: 100, value: this.progress })
.color('#ff5cea20')
.visibility(this.hideProgress
? Visibility.None : Visibility.Visible)
/**
* 组件创建时,
* (1)网络加载https://www.douban.com/
* (2)本地加载$rawfile('app/html/login.html')
*/
Web({ src: $rawfile('app/index.html'),
controller: this.webController })
// 设置文件访问权限和脚本执行权限
.fileAccess(this.fileAccess)
.javaScriptAccess(true)
.height('100%')
// 添加onProgressChange事件
.onProgressChange(e => {
this.progress = e.newProgress
// 当进度100%时,进度条消失
if (this.progress == 100) {
this.hideProgress = true
} else {
this.hideProgress = false
}
})
//JS对象注入,Html使用JS对象调用客户端方法
.javaScriptAccess(true)
.javaScriptProxy({
object: this.jsBridge,
name: "jsBridge",
methodList: ["closePage", "jumpSystemPicture"],
controller: this.webController
})
.onPageEnd(e => {
// test()在index.html中定义
this.webController.runJavaScript('test()');
console.info('url: ', e.url);
})
}.backgroundColor('0xFFFFFF')
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<script type="text/javascript" src="./static/js/init-rem.js"></script>
<script type="text/javascript" src="./static/js/vconsole.min.js"></script>
<script type="text/javascript">
var vc = new VConsole()
</script>
<style type="text/css">
body {
display: flex;
flex-direction: column;
}
.title {
color: black;
font-size: 0.34rem;
}
.btn-layout {
color: red;
font-size: 0.32rem;
margin-top: 0.5rem;
}
.btn-layout2 {
color: blue;
font-size: 0.32rem;
margin-top: 0.5rem;
}
</style>
</head>
<body>
<p class="title">这个是来自本地的html文件</p>
<button class="btn-layout" type="button"
onclick="window.jsBridge.closePage()">
点击调用原生关闭页面
</button>
<button class="btn-layout2" type="button"
onclick="window.jsBridge.jumpSystemPicture()">
点击拉起原生系统相册
</button>
<script type="text/javascript">
function test() {
console.info('Ark Web Component');
}
</script>
</body>
</html>
3.6. Web组件H5层与应用层进行相互通讯
3.6.1. 鸿蒙应用向H5页面发送数据
在创建的Web页面中使用 runJavaScript() 方法可直接触发 H5 页面中的方法
Button("向H5层发送数据")
.onClick(() => {
this.webController
.runJavaScript(`sendH5Msg("${this.input}")`)
}).margin(10)
3.6.2. H5页面向鸿蒙应用发送数据
在 H5 页面中 执行 confirm() 弹窗,在 Web组件 中执行 onConfirm 事件并通过 Promise 方式返回脚本执行的结果。
返回结果为一个对象,其中url为当前web组件加载的页面地址,message为H5中传递返回的结果。
/**
* 给原生应用传递数据
*/
function toHmMsg() {
let obj = {
name: "张三", age: "20"
}
confirm(JSON.stringify(obj))
}
3.6.3. 完整代码
(1)Harmony代码
import webVeb from '@ohos.web.webview';
// 实现Web组件H5层与应用层进行相互通讯
@Entry
@Component
struct WebComponent4 {
webController: webVeb.WebviewController =
new webVeb.WebviewController();
@State content: string = ""
@State input: string = ""
build() {
// H5层
Column() {
Column() {
Text("H5层")
Web({ src: $rawfile('app/index4.html'),
controller: this.webController })
.onConfirm((e: any) => {
console.log("lvs>" + JSON.stringify(e))
this.content = e['message']
return true
})
}
.height('50%')
.width('100%')
.padding(10)
.margin(10)
.border({ width: 1 })
// 应用层
Column() {
Text("应用层")
TextInput({ placeholder: "请输入数据" })
.onChange((e => {
this.input = e
}))
Button("向H5层发送数据")
.onClick(() => {
this.webController
.runJavaScript(`sendH5Msg("${this.input}")`)
}).margin(10)
Row().width("100%").height(1)
.backgroundColor("#ddd").margin(10)
Text("接受H5传递的内容:"+this.content)
}
.height('50%')
.width('100%')
.padding(10)
.margin(10)
.border({ width: 1 })
}
.height('100%')
.padding(20)
.backgroundColor('0xFFFFFF')
}
}
(2)Html 代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<script type="text/javascript" src="./static/js/init-rem.js"></script>
<script type="text/javascript" src="./static/js/vconsole.min.js"></script>
<script type="text/javascript">
var vc = new VConsole()
</script>
<style type="text/css">
.divCon {
width: 100px;
height: 100px;
background: #ddd;
margin-top: 20px;
}
</style>
</head>
<body>
<script>
/**
* 给原生应用传递数据
*/
function toHmMsg() {
let obj = {
name: "张三", age: "20"
}
confirm(JSON.stringify(obj))
}
/**
* 接受原生发送数据
* @param msg
*/
function sendH5Msg(msg) {
document.getElementById("content_id").innerText = "你好" + msg
}
</script>
<div>
<button onclick="toHmMsg()">给原生应用传递数据</button>
<div id="content_id" class="divCon"></div>
</div>
</body>
</html>
上面方法只提到了调用网页中的JS方法,感兴趣的评论区留意讨论,一起学习,相互探讨,共同进步。有时间我再补充讲解下。