纯血鸿蒙APP实战开发——Navigation实现多设备适配案例

2024-04-29 11:12

本文主要是介绍纯血鸿蒙APP实战开发——Navigation实现多设备适配案例,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

介绍

在应用开发时,一个应用需要适配多终端的设备,使用Navigationmode属性来实现一套代码,多终端适配。

效果图预览

使用说明

  1. 将程序运行在折叠屏手机或者平板上观看适配效果。

实现思路

本例涉及的关键特性和实现方案如下:

1.分屏的使用

首先介绍的是本案例的关键特性Navigationmode属性,原先采用的是NavigationMode.Stack,导航栏与内容区独立显示,相当于两个页面。
现在采用当设备宽度>=600vp时,采用Split模式显示;设备宽度<600vp时,采用Stack模式显示。通过display.isFoldable()判断是否设备可折叠,如果可折叠
通过display.on(‘foldStatusChange’)来开启折叠设备折叠状态变化的监听,折叠时是Stack模式,半折叠和完全展开时采用Split模式。

源码参考EntryView.ets


if (display.isFoldable()) {this.regDisplayListener();
} else {if (this.screenW >= this.DEVICESIZE) {this.navigationMode = NavigationMode.Split;} else {this.navigationMode = NavigationMode.Stack;}
}/**
* 注册屏幕状态监听
* @returns {void}
*/
regDisplayListener(): void {this.changeNavigationMode(display.getFoldStatus());display.on('foldStatusChange', async (curFoldStatus: display.FoldStatus) => {// 同一个状态重复触发不做处理if (this.curFoldStatus === curFoldStatus) {return;}// 缓存当前折叠状态this.curFoldStatus = curFoldStatus;this.changeNavigationMode(this.curFoldStatus);})
}// 更改NavigationMode
changeNavigationMode(status: number): void {if (status === display.FoldStatus.FOLD_STATUS_FOLDED) {this.navigationMode = NavigationMode.Stack;} else {this.navigationMode = NavigationMode.Split;}
}
...
Navigation(this.pageStack) { ... }.backgroundColor($r('app.color.main_background_color')).hideTitleBar(true).navBarWidth($r('app.string.entry_half_size')).hideNavBar(this.isFullScreen).navDestination(this.pageMap).mode(this.navigationMode)
2.模块全屏的使用以及Bug解决

EntryViewNavigation中设置hideNavBar,其值设置为由@Provide装饰器装饰过的变量,默认值为false,作用是为了适配需要全屏的模块。
在对应模块的实现文件声明由@Consume装饰器装饰过的变量,更改变量的值就可以实现与后代组件双向同步的通信,从而实时确定是否需要
hideNavBar
源码参考:

MusicPlayerInfoComp.ets
EntryView.ets

 // EntryView.ets...@Provide('isFullScreen') isFullScreen: boolean = false;...Navigation(this.pageStack) { ... }.backgroundColor($r('app.color.main_background_color')).hideTitleBar(true).navBarWidth($r('app.string.entry_half_size')).hideNavBar(this.isFullScreen).navDestination(this.pageMap).mode(this.navigationMode)...// FunctionalScenes.etsif (this.isNeedClear) {DynamicsRouter.clear();}if (this.listData !== undefined) {// 点击瀑布流Item时,根据点击的模块信息,将页面放入路由栈DynamicsRouter.push(this.listData.routerInfo, this.listData.param);}// MusicPlayerInfoComp.ets...// 通知Navigation组件隐藏导航栏@Consume('isFullScreen') isFullScreen: boolean;...navigationAnimation(isFullScreen: boolean): void {animateTo({duration: 200,curve: Curve.EaseInOut,}, () => {this.isFullScreen = isFullScreen;})}
3.主页Navigation弹出路由栈

手机的Navigation采用Stack模式,手势右滑退出会自动pop路由栈,但是采用分栏可以直接点击跳转到下一模块,那么就需要在点击瀑布流的FlowItem的时刻clear上一个路由栈。
源码参考FunctionalScenes.ets。

  @BuildermethodPoints(listData: SceneModuleInfo) {....onClick(() => {// 平板采用点击切换案例,需要pop,手机则不需要,左滑时已pop。if (this.isNeedClear) {DynamicsRouter.clear();}if (this.listData !== undefined) {// 点击瀑布流Item时,根据点击的模块信息,将页面放入路由栈DynamicsRouter.push(this.listData.routerInfo, this.listData.param);}})}

FAQ

1.页面间共享组件实例模块的适配问题

页面间共享组件实例模块中也写了Navigation组件,想要展示的效果是Stack模式,但是半屏的平板的宽度也大于600,被系统自动认为采用Split模式。

页面间共享组件实例模块中还绑定了半模态,并未设置preferType(半模态页面的样式)。设备宽度小于600vp时,默认显示底部弹窗样式。
设备宽度在600-840vp间时,默认显示居中弹窗样式。设备宽度大于840vp时,默认显示跟手弹窗样式,跟手弹窗显示在bindSheet绑定的节点下方。平板宽度大于840vp,跟手弹窗显示在节点下方导致弹窗不可见。
所以通过设备宽度来设置preferType的样式。
源码参考:
ComponentSharedInPages.ets

TakeTaxiDetailPage.ets

  //ComponentSharedInPages.etsbuild() {Stack({alignContent: Alignment.Bottom}) {...// 应用主页用NavDestination承载,Navigation为空页面直接跳转到MainPage主页面Navigation(this.pageStackForComponentSharedPages) {}....mode(NavigationMode.Stack)}...}//TakeTaxiDetailPage.ets...aboutToAppear() {if (display.isFoldable()) {this.regDisplayListener();} else {if (this.screenW >= this.DEVICESIZE) {this.isCenter = true;} else {this.isCenter = false;}}}...build() {NavDestination() {...// 绑定上半模态页面,用于显示内容.bindSheet($$this.isShow, this.taxiContentBuilder(),{detents: TakeTaxiPageCommonConstants.SHEET_DETENTS,preferType: this.isCenter ? SheetType.CENTER : SheetType.POPUP,...})}...}
2.底部抽屉滑动效果模块的适配问题

底部抽屉滑动效果模块中写了一个Image组件,其资源是一个很大的地图图片,在分栏效果展示时Image图片资源会拦截Navigation导航栏的点击或者拖拽事件,可以采用Columnclip属性将超出Image的图片裁掉。

源码参考:Component.ets。

 build() {Column() {// 背景地图图片Image($r('app.media.map')).id("bg_img").height($r('app.integer.number_2000')).width($r('app.integer.number_2000')).translate({ x: this.offsetX, y: this.offsetY })// 以组件左上角为坐标原点进行移动.draggable(false) // 单指操作拖动背景地图}.width('100%').height('100%').clip(true) // 地图图片超出页面区域时裁剪掉...}
3.适配挖孔屏模块的适配问题

适配挖孔屏模块Image组件采用ImageFit.Cover填充图片,导致图片显示不完整,采用ImageFit.Fill,虽然图片变扁了,但是能完整显示,不影响具体功能。

源码参考:DiggingHoleScreen.ets。

Image($r('app.media.2048game')).objectFit(ImageFit.Fill).width('100%').height('100%')
4.左右拖动切换图片模块的适配问题

左右拖动切换图片模块主要功能要实时记录手势拖动的距离,以此来进行计算,所以宽度和高度要写固定数值,不能使用百分比。但是折叠屏手机折叠后会出现超出屏幕的情况,可采用缩小组件宽度的方式适配。

源码参考:
DragToSwitchPicturesView.ets

Constants.ets

integer.json

// DragToSwitchPicturesView.ets
@State dragRefOffset: number = 0; // 用来记录每次图标拖动的距离
@State imageWidth: number = 160; // 用来记录每次图标拖动完成后左侧Image的width宽度
@State leftImageWidth: number = 160; // 用来记录每次图标拖动时左侧Image的实时width宽度
@State rightImageWidth: number = 160; // 用来记录每次图标拖动时右侧Image的实时width宽度
...
PanGesture({ fingers: CONFIGURATION.PANGESTURE_FINGERS, distance: CONFIGURATION.PANGESTURE_DISTANCE }).onActionStart(() => {this.dragRefOffset = CONFIGURATION.INIT_VALUE; // 每次拖动开始时将图标拖动的距离初始化。})// TODO: 性能知识点: 该函数是系统高频回调函数,避免在函数中进行冗余或耗时操作,例如应该减少或避免在函数打印日志,会有较大的性能损耗。.onActionUpdate((event: GestureEvent) => {// 通过监听GestureEvent事件,实时监听图标拖动距离this.dragRefOffset = event.offsetX;this.leftImageWidth = this.imageWidth + this.dragRefOffset;this.rightImageWidth = CONFIGURATION.IMAGE_FULL_SIZE - this.leftImageWidth;if (this.leftImageWidth >= CONFIGURATION.LEFT_IMAGE_RIGHT_LIMIT_SIZE) { // 当leftImageWidth大于等于310vp时,设置左右Image为固定值,实现停止滑动效果。this.leftImageWidth = CONFIGURATION.LEFT_IMAGE_RIGHT_LIMIT_SIZE;this.rightImageWidth = CONFIGURATION.RIGHT_IMAGE_RIGHT_LIMIT_SIZE;} else if (this.leftImageWidth <= CONFIGURATION.LEFT_IMAGE_LEFT_LIMIT_SIZE) { // 当leftImageWidth小于等于30vp时,设置左右Image为固定值,实现停止滑动效果。this.leftImageWidth = CONFIGURATION.LEFT_IMAGE_LEFT_LIMIT_SIZE;this.rightImageWidth = CONFIGURATION.RIGHT_IMAGE_LEFT_LIMIT_SIZE;}}).onActionEnd((event: GestureEvent) => {if (this.leftImageWidth <= CONFIGURATION.LEFT_IMAGE_LEFT_LIMIT_SIZE) {this.leftImageWidth = CONFIGURATION.LEFT_IMAGE_LEFT_LIMIT_SIZE;this.rightImageWidth = CONFIGURATION.RIGHT_IMAGE_LEFT_LIMIT_SIZE;this.imageWidth = CONFIGURATION.LEFT_IMAGE_LEFT_LIMIT_SIZE;} else if (this.leftImageWidth >= CONFIGURATION.LEFT_IMAGE_RIGHT_LIMIT_SIZE) {this.leftImageWidth = CONFIGURATION.LEFT_IMAGE_RIGHT_LIMIT_SIZE;this.rightImageWidth = CONFIGURATION.RIGHT_IMAGE_RIGHT_LIMIT_SIZE;this.imageWidth = CONFIGURATION.LEFT_IMAGE_RIGHT_LIMIT_SIZE;} else {this.leftImageWidth = this.imageWidth + this.dragRefOffset; // 滑动结束时leftImageWidth等于左边原有Width+拖动距离。this.rightImageWidth = CONFIGURATION.IMAGE_FULL_SIZE - this.leftImageWidth; // 滑动结束时rightImageWidth等于340-leftImageWidth。this.imageWidth = this.leftImageWidth; // 滑动结束时ImageWidth等于leftImageWidth。}})
5.图片压缩模块的适配问题

图片压缩模块中Text组件的字号在折叠手机屏折叠状态下过大,文本会超出屏幕,可采取缩小字号适配。

源码参考:ImageCompression.ets

6.图片缩放模块的适配问题

图片缩放模块中Image组件的宽度和高度由窗口的宽度和高度决定。由于屏幕宽度大于600vp要分栏,会导致图片过大。所以要判断是否分栏,若分栏则windowWidth的宽度减半。

源码参考:ImageContentView.ets

   ...@State windowWidth: number = 0;@State windowHeight: number = 0;.../*** 获取应用主窗口的宽高*/aboutToAppear() {window.getLastWindow(getContext(this), (err: BusinessError, data: window.Window) => {let rect: window.Rect = data.getWindowProperties().windowRect;this.windowWidth = px2vp(rect.width);this.windowHeight = px2vp(rect.height);if (this.windowWidth > this.componentsWindowWidth) {this.windowWidth = this.windowWidth / 2;}data.on("windowSizeChange", (size: window.Size) => {this.windowWidth = px2vp(size.width);this.windowHeight = px2vp(size.height);if (this.windowWidth > this.componentsWindowWidth) {this.windowWidth = this.windowWidth / 2;}})})}...Image(this.image).width(this.windowWidth * this.imageScale.scaleValue).height(this.windowHeight * this.imageScale.scaleValue)...
7.元素超出List区域模块的适配问题

元素超出List区域模块中使用ListitemGroup组件实现卡片样式,在折叠屏中展开时并未布局满全屏,原因是设置ListItemGroupStyle.CARD时,必须配合ListItemListItemStyle.CARD使用。

源码参考:AboutMe.ets

ListItemGroup({ style: ListItemGroupStyle.CARD }) {ListItem({ style: ListItemStyle.CARD }) {...}.height($r("app.integer.itemoverflow_default_item_height")).toastOnClick($r("app.string.listitem_overflow_toast_no_edit"))ListItem({ style: ListItemStyle.CARD }) {...}.height($r("app.integer.itemoverflow_default_item_height")).toastOnClick($r("app.string.listitem_overflow_toast_no_edit"))
}
.divider({ strokeWidth: 1, color: $r('app.color.aboubtme_pageBcColor') })ListItemGroup({ style: ListItemGroupStyle.CARD }) {ListItem({ style: ListItemStyle.CARD }) {...}.height($r("app.integer.itemoverflow_default_item_height")).toastOnClick($r("app.string.listitem_overflow_toast_no_card"))
}...ListItemGroup({ style: ListItemGroupStyle.CARD }) {ListItem({ style: ListItemStyle.CARD }) {....toastOnClick($r("app.string.listitem_overflow_toast_no_favorite"))ListItem({ style: ListItemStyle.CARD }) {...}.height($r("app.integer.itemoverflow_default_item_height")).toastOnClick($r("app.string.listitem_overflow_toast_no_settings"))ListItem({ style: ListItemStyle.CARD }) {...}.height($r("app.integer.itemoverflow_default_item_height")).toastOnClick($r("app.string.listitem_overflow_toast_about"))
}...
8.听歌识曲水波纹特效模块的适配问题

听歌识曲水波纹特效模块中使用Column容器搭配margin进行布局,但是在不同设备中就不适配了。可以使用justifyContent属性设置子组件在垂直方向上的对齐格式,再搭配margin就可适配多种终端。

源码参考:WaterRipples.ets

Column() {Text($r('app.string.sound_hound')).fontColor(Color.White).fontSize(18).margin({ top: $r('app.integer.margin_large') })ButtonWithWaterRipples({ isListening: this.isListening })Text(this.isListening ? $r('app.string.is_listening') : $r('app.string.click_to_listen')).fontColor(Color.White).margin({ bottom: $r('app.integer.margin_large') })
}
.backgroundColor(Color.Black)
.justifyContent(FlexAlign.SpaceBetween)
.width("100%")
.height("100%")
9.模块资源命名重名

模块资源重复导致模块显示错误,修改资源命名,最好在新命名前面加上自己的模块名称。

{"name": "navigationparametertransferview_user_name","value": "用户姓名:"
}{"name": "aboubtme_pageBcColor","value": "#fff1f3f5"
}{"name": "customsafekeyboard_placeholder","value": "请输入密码"
}

参考资料

Navigation

clip

@Provide装饰器和@Consume装饰器:与后代组件双向同步

半模态转场

Image

Column

ListItemGroup

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:https://gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

这篇关于纯血鸿蒙APP实战开发——Navigation实现多设备适配案例的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/945924

相关文章

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

Python实现微信自动锁定工具

《Python实现微信自动锁定工具》在数字化办公时代,微信已成为职场沟通的重要工具,但临时离开时忘记锁屏可能导致敏感信息泄露,下面我们就来看看如何使用Python打造一个微信自动锁定工具吧... 目录引言:当微信隐私遇到自动化守护效果展示核心功能全景图技术亮点深度解析1. 无操作检测引擎2. 微信路径智能获

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

Python位移操作和位运算的实现示例

《Python位移操作和位运算的实现示例》本文主要介绍了Python位移操作和位运算的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 位移操作1.1 左移操作 (<<)1.2 右移操作 (>>)注意事项:2. 位运算2.1

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll

Java Stream流使用案例深入详解

《JavaStream流使用案例深入详解》:本文主要介绍JavaStream流使用案例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录前言1. Lambda1.1 语法1.2 没参数只有一条语句或者多条语句1.3 一个参数只有一条语句或者多

Spring Security自定义身份认证的实现方法

《SpringSecurity自定义身份认证的实现方法》:本文主要介绍SpringSecurity自定义身份认证的实现方法,下面对SpringSecurity的这三种自定义身份认证进行详细讲解,... 目录1.内存身份认证(1)创建配置类(2)验证内存身份认证2.JDBC身份认证(1)数据准备 (2)配置依