混合开发概述与优势
一、混合开发的定义
混合开发是一种融合了原生开发和 Web 开发优势的移动应用开发方式。通常利用一种框架或平台创建应用程序,结合原生应用的功能特性(如访问设备摄像头、相册、GPS、蓝牙等),同时使用 Web 技术(HTML5、CSS 和 JavaScript)编写大部分应用代码。
二、混合开发优势
(一)跨平台能力
可以编写一次代码,部署到多个平台(如 iOS/Android/HarmonyOS),大大节省开发和维护成本。
(二)快速迭代
混合应用主要使用 Web 技术,可像网页一样快速更新和迭代,无需用户手动更新应用。
(三)用户体验
虽性能可能不如完全原生应用,但随着技术发展,许多混合开发框架能提供接近原生应用的用户体验。
(四)成本效益
对于预算有限的项目或初创公司,混合开发减少了为每个平台单独开发应用的需要,是成本效益较高的选择。
三、创建 Web 页面步骤
(一)创建页面导航栏
- 创建 Web 页面
import { promptAction } from '@kit.ArkUI'
@Builder
function WebViewBuilder() {
WebView()
}
@Component
struct WebView {
src: ResourceStr = ''
@Consume pageStack: NavPathStack
aboutToAppear(): void {
const params = this.pageStack.getParamByName('WebView')[0] as string
if (params) {
promptAction.showToast({
message: params
})
}
// 1. 生产使用
// this.src = 'resource://rawfile/index.html#' + params
// 2. 测试使用
// this.src = 'http://192.168.xx.xxx:5173/#' + params
}
build() {
NavDestination() {
Column() {
// 后续放置 web 相关内容
Text('测试用的页面')
.fontSize(50)
}
}
.hideTitleBar(true)
}
}
- 设置点击跳转
.onClick(() => {
this.pageStack.pushPathByName('WebView', 'profile')
})
- 路由表配置参考
{
"name": "WebView",
"pageSourceFile": "src/main/ets/views/WebView.ets",
"buildFunction": "WebViewBuilder"
}
- 设置加载进度条。 progress 组件
// 网页是否在加载中
@State loading: boolean = true
// 网页加载的进度
@State progress: number = 0
// 导航条下方
// 堆叠组件
Stack({ alignContent: Alignment.Top }){
// 如果加载中, 则显示进度条插件
if (this.loading) {
Progress({ type: ProgressType.Linear, value: this.progress, total: 100 })
.style({ strokeWidth: 2, enableSmoothEffect: true })
.color($r('[basic].color.red'))
.zIndex(1)
}
}
.width('100%')
.layoutWeight(1)
- 设置 Web 组件加载网页,以京东作为测试页面。
// 其他略
aboutToAppear(): void {
const params = this.pageStack.getParamByName('WebView')[0] as string
promptAction.showToast({
message: params
})
// 测试
this.src = 'https://m.suning.com'
}
- 添加 web 组件,并设置地址和控制器。
// 网页是否在加载中
@State loading: boolean = true
// 网页加载的进度
@State progress: number = 0
// 页面视图控制器
controller = new webview.WebviewController()
// 导航条下方
// 堆叠组件
Stack({ alignContent: Alignment.Top }){
// 如果加载中, 则显示进度条插件
if (this.loading) {
Progress({ type: ProgressType.Linear, value: this.progress, total: 100 })
.style({ strokeWidth: 2, enableSmoothEffect: true })
.color($r('[basic].color.red'))
.zIndex(1)
}
// web组件: 用于加载在线网页
Web({ src: this.src, controller: this.controller })
}
.width('100%')
.layoutWeight(1)
- 设置进度条显示 / 隐藏(带动画)和加载进度。
onPageBegin: 开始加载网页时触发
onPageEnd: 网页加载完成时触发
onProgressChange: 网页加载进度变化时触发该回调
链接
// web组件: 用于加载在线网页
Web({ src: this.src, controller: this.controller })
.onProgressChange((data) => { // 网页加载进度变化时触发该回调
// 1. 进度条
console.log('mk-logger', JSON.stringify(data)) // 新的加载进度,取值范围为0到100的整数
if (data) {
// 1.1 记录加载进度
this.progress = data.newProgress
// 1.2 如果加载进度完成
if (data.newProgress === 100) {
// 1.3 动画让进度条消失
animateTo({ duration: 300, delay: 300 }, () => {
this.loading = false
})
}
}
})
.onPageBegin(() => { // 开始加载网页时触发
this.progress = 0
this.loading = true
console.log('mk-logger', 'onPageBegin')
})
.onPageEnd(() => { // 网页加载完成时触发
console.log('mk-logger', 'onPageEnd')
})
(二)导航栏相关操作处理
- 导航下拉菜单 - 刷新。 传送门
.onClick(() => this.controller.refresh())
@Builder
MenuBuilder() {
Menu() {
MenuItem({ content: '首页' })
.onClick(() => router.back({ url: 'pages/Index', params: { index: 0 } }))
MenuItem({ content: '分类' })
.onClick(() => router.back({ url: 'pages/Index', params: { index: 1 } }))
MenuItem({ content: '购物袋' })
.onClick(() => router.back({ url: 'pages/Index', params: { index: 2 } }))
MenuItem({ content: '我的' })
.onClick(() => router.back({ url: 'pages/Index', params: { index: 3 } }))
MenuItem({ content: '刷新一下' })
.onClick(() => this.controller.refresh())
}
.width(100)
.fontColor($r('[basic].color.text'))
.font({ size: 14 })
.radius(4)
}
- 导航条标题设置。
网页标题切换时,更新页面标题
// 网页document标题更改时触发该回调
.onTitleReceive((data) => {
console.log('mk-logger', 'onTitleReceive')
this.title = data?.title || ''
})
- 返回和关闭操作。
(一)WebView 中的返回逻辑
在 WebView 中,若当前页面之前有页面,则在控制器内返回;若没有,则通过原生侧路由返回。(二)关闭处理
能够关闭鸿蒙页面。(三)点击导航栏返回
- 如果 Web 组件有历史记录,Web 组件返回上一个 Web 网页。
- 如果 Web 组件没有历史记录,返回上一鸿蒙页面。
(四)点击系统级返回按钮(onBackPressed)
事件、getbackforwardentries、页面生命周期
accessBackward
当前页面是否可后退,即当前页面是否有返回历史记录。
/**
* 回到web容器的上一个页面
*/
webBack() {
if (this.controller.accessBackward()) {
this.controller.backward()
} else {
this.pageStack.pop()
}
}
/**
* 回到上一个页面
*/
webClose() {
this.pageStack.pop()
}
完整代码
import { promptAction } from '@kit.ArkUI'
import { emitter } from '@kit.BasicServicesKit'
import { EmitterKey } from 'basic'
import { webview } from '@kit.ArkWeb'
@Builder
function WebViewBuilder() {
WebView()
}
@Component
struct WebView {
src: ResourceStr = ''
@Consume
pageStack: NavPathStack
// 当前网页的标题
@State title: string = '默认标题'
// 从本地存储中获取顶部安全距离
@StorageProp('safeTop') safeTop: number = 0
// 网页是否在加载中
@State loading: boolean = true
// 网页加载的进度
@State progress: number = 0
// 页面视图控制器
controller = new webview.WebviewController()
aboutToAppear(): void {
const params = this.pageStack.getParamByName('WebView')[0] as string
promptAction.showToast({
message: params
})
this.src = 'https://jd.com'
}
/**
* 回到web容器的上一个页面
*/
webBack() {
if (this.controller.accessBackward()) {
this.controller.backward()
} else {
this.pageStack.pop()
}
}
/**
* 回到上一个页面
*/
webClose() {
this.pageStack.pop()
}
toIndex(index: number) {
this.pageStack.clear()
emitter.emit(EmitterKey.CHANGE_TAB, {
data: {
index
}
})
}
@Builder
MenuBuilder() {
Menu() {
MenuItem({ content: '首页' })
.onClick(() => {
this.toIndex(0)
})
MenuItem({ content: '分类' })
.onClick(() => {
this.toIndex(1)
})
MenuItem({ content: '购物袋' })
.onClick(() => {
this.toIndex(2)
})
MenuItem({ content: '我的' })
.onClick(() => {
this.toIndex(3)
})
MenuItem({ content: '刷新一下' })
.onClick(() => {
this.controller.refresh()// 刷新
})
}
.width(100)
.fontColor($r('[basic].color.text'))
.font({ size: 14 })
.radius(4)
}
build() {
NavDestination() {
Column() {
// 使用网页容器组件
// 导航条
Row() {
Row() {
// 返回(网页)
Image($r("[basic].media.ic_public_left"))
.iconStyle()
.onClick(() => {
this.webBack()
})
// 关闭(鸿蒙页面)
Image($r('[basic].media.ic_public_close'))
.iconStyle()
.onClick(() => {
this.webClose()
})
}
.width(100)
Text(this.title)
.fontSize(16)
.fontWeight(500)
.fontColor($r('[basic].color.black'))
.layoutWeight(1)
.maxLines(1)
.textAlign(TextAlign.Center)
.textOverflow({ overflow: TextOverflow.MARQUEE })
Row() {
Blank()
Image($r('[basic].media.ic_public_more'))
.iconStyle()
.bindMenu(this.MenuBuilder)
}
.width(100)
}
.height(50 + this.safeTop)
.backgroundColor($r('[basic].color.white'))
.padding({ top: this.safeTop })
// 后续放置 进度条+页面
// 堆叠组件
Stack({ alignContent: Alignment.Top }){
// 如果加载中, 则显示进度条插件
if (this.loading) {
Progress({ type: ProgressType.Linear, value: this.progress, total: 100 })
.style({ strokeWidth: 2, enableSmoothEffect: true })
.color($r('[basic].color.red'))
.zIndex(1)
}
// web组件: 用于加载在线网页
Web({ src: this.src, controller: this.controller })
.onProgressChange((data) => { // 网页加载进度变化时触发该回调
// 1. 进度条
console.log('mk-logger', JSON.stringify(data)) // 新的加载进度,取值范围为0到100的整数
if (data) {
// 1.1 记录加载进度
this.progress = data.newProgress
// 1.2 如果加载进度完成
if (data.newProgress === 100) {
// 1.3 动画让进度条消失
animateTo({ duration: 300, delay: 300 }, () => {
this.loading = false
})
}
}
})
.onPageBegin(() => { // 开始加载网页时触发
this.progress = 0
this.loading = true
console.log('mk-logger', 'onPageBegin')
})
.onPageEnd(() => { // 网页加载完成时触发
console.log('mk-logger', 'onPageEnd')
})
// 网页document标题更改时触发该回调
.onTitleReceive((data) => {
console.log('mk-logger', 'onTitleReceive')
this.title = data?.title || ''
})
}
.width('100%')
.layoutWeight(1)
}
}
.hideTitleBar(true)
}
}
@Extend(Image)
function iconStyle() {
.width(24)
.aspectRatio(1)
.fillColor($r('[basic].color.text'))
.margin(13)
}
结语
在当今移动应用开发领域,混合开发为开发者提供了一种高效且灵活的解决方案。通过融合原生开发和 Web 开发的优势,我们能够在跨平台能力、快速迭代、用户体验和成本效益等方面取得显著的成果。
在本文中,我们深入探讨了鸿蒙混合开发中创建 Web 页面的详细步骤以及相关操作处理。从创建页面导航栏到设置加载进度条,再到处理导航栏的各种操作,每一个环节都展示了混合开发的强大功能和灵活性。
随着技术的不断进步,混合开发将继续在移动应用开发中发挥重要作用。无论是对于经验丰富的开发者还是初入行业的新手,掌握混合开发技术都将为他们的项目带来更多的可能性和竞争力。
希望本文能够为你在鸿蒙混合开发的旅程中提供有价值的参考和指导,让你能够更加轻松地创建出功能强大、用户体验优秀的移动应用。
版权归原作者 小李cc 所有, 如有侵权,请联系我们删除。