HarmonyOS实战开发-一次开发,多端部署-音乐专辑

2024-03-31 22:04

本文主要是介绍HarmonyOS实战开发-一次开发,多端部署-音乐专辑,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

简介

基于自适应和响应式布局,实现一次开发、多端部署音乐专辑页面。

相关概念

  • 一次开发,多端部署:一套代码工程,一次开发上架,多端按需部署。支撑开发者快速高效的开发支持多种终端设备形态的应用,实现对不同设备兼容的同时,提供跨设备的流转、迁移和协同的分布式体验。
  • 自适应布局:当外部容器大小发生变化时,元素可以根据相对关系自动变化以适应外部容器变化的布局能力。相对关系如占比、固定宽高比、显示优先级等。当前自适应布局能力有7种:拉伸能力、均分能力、占比能力、缩放能力、延伸能力、隐藏能力、折行能力。自适应布局能力可以实现界面显示随外部容器大小连续变化。
  • 响应式布局:当外部容器大小发生变化时,元素可以根据断点、栅格或特定的特征(如屏幕方向、窗口宽高等)自动变化以适应外部容器变化的布局能力。当前响应式布局能力有3种:断点、媒体查询、栅格布局。
  • GridRow:栅格容器组件,仅可以和栅格子组件(GridCol)在栅格布局场景中使用。
  • GridCol:栅格子组件,必须作为栅格容器组件(GridRow)的子组件使用。

环境搭建

软件要求

  • DevEco Studio版本:DevEco Studio 3.1 Release及以上版本。
  • OpenHarmony SDK版本:API version 9及以上版本。

硬件要求

  • 开发板类型:润和RK3568开发板。
  • OpenHarmony系统:3.2 Release及以上版本。

环境搭建

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:

  1. 获取OpenHarmony系统版本:标准系统解决方案(二进制)。以3.2 Release版本为例:

2.搭建烧录环境。

  1. 完成DevEco Device Tool的安装
  2. 完成RK3568开发板的烧录

3.搭建开发环境。

  1. 开始前请参考工具准备,完成DevEco Studio的安装和开发环境配置。
  2. 开发环境配置完成后,请参考使用工程向导创建工程(模板选择“Empty Ability”)。
  3. 工程创建完成后,选择使用真机进行调测。

代码结构解读

本篇Codelab只对核心代码进行讲解。

├──common/src/main/ets               // 公共能力层
│  ├──bean
│  │  └──MenuData.ets                // 菜单数据实体类
│  ├──constants
│  │  ├──BreakpointConstants.ets     // 断点常量类
│  │  ├──GridConstants.ets           // 栅格常量类
│  │  └──StyleConstants.ets          // 样式常量类
│  └──utils
│     └──BreakpointSystem.ets        // 断点工具类
├──features                          // 基础特性层
│  ├──content/src/main/ets           // 专辑封面和歌曲列表内容区
│  │  ├──components
│  │  │  ├──AlbumComponent.ets       // 自定义专辑封面组件
│  │  │  ├──AlbumCover.ets           // 支持多设备的自定义专辑封面组件
│  │  │  ├──Content.ets              // 自定义专辑封面和歌曲列表组件
│  │  │  └──PlayList.ets             // 自定义歌曲列表组件
│  │  ├──constants
│  │  │  └──ContentConstants.ets     // 内容区常量类
│  │  └──viewmodel
│  │     ├──SongDataSource.ets       // 懒加载数据源
│  │     └──SongListData.ets         // 歌曲列表数据类
│  ├──content/src/main/resources     // 资源文件目录
│  ├──header/src/main/ets            // 顶部标题栏
│  │  ├──components
│  │  │  └──Header.ets               // 自定义标题栏组件
│  │  └──constants
│  │     └──HeaderConstants.ets      // 标题栏常量类
│  ├──header/src/main/resources      // 资源文件目录
│  └──player/src/main/ets            // 底部播放控制区
│  │  ├──components
│  │  │  └──Player.ets               // 自定义底部播放控制区组件
│  │  └──constants
│  │     └──PlayerConstants.ets      // 播放控制区常量类
│  └──player/src/main/resources      // 资源文件目录
└──products                          // 产品定制层├──phone/src/main/ets             // 支持手机、平板│  ├──entryability│  │  └──EntryAbility.ts          // 程序入口类│  └──pages│     └──MainPage.ets             // 主界面└──phone/src/main/resources       // 资源文件目录

工程结构管理

在这个章节中,我们需要完成模块划分、梳理模块间依赖关系并设计代码结构,从而便于后续复杂项目的维护。参考上一章节页面设计,我们可以将页面分拆为多个组成部分:

  1. 标题栏
  2. 专辑封面
  3. 歌曲列表
  4. 播放控制区

工程结构建议如下:

  • 定义common层(公共能力层): 用于存放项目的工具类、公共常量等。需编译成一个HAR包,只可以被products和features依赖,不可以反向依赖。
  • 定义features层(基础特性层): 用于存放相对独立的各个功能单元、自定义UI组件或业务实现逻辑,供产品灵活部署。不需要单独部署的feature通常编译为HAR包,供products或其它features使用。需要单独部署的feature通常编译为Feature类型的HAP包,和products下Entry类型的HAP包进行组合部署。features层可以横向调用及依赖common层,同时可以被products层不同设备形态的HAP所依赖,但是不能反向依赖products层。
  • 定义products层(产品定制层): 用于针对不同设备形态进行功能和特性集成。products层各个子目录分别编译为一个Entry类型的HAP包,作为应用的主入口,products层不可以横向调用。

本篇Codelab通过common层管理媒体查询工具类、栅格常量、公共常量等;按照页面分组,将标题栏定义为header基础特性,将专辑封面和歌曲列表定义为content基础特性,将播放控制区定义为player基础特性;定义phone产品目录支持手机、平板等设备,集成基础特性层的能力。products依赖features和common,features依赖common但不互相依赖,工程结构清晰且便于维护。

├──common               // 公共能力层
├──features             // 基础特性层
│  ├──content           // 专辑封面和歌曲列表内容区
│  ├──header            // 顶部标题栏
│  └──player            // 底部播放控制区
└──products             // 产品定制层└──phone             // 支持手机、平板

一次开发,多端部署实现方案

多标题栏

在这个章节中,我们需完成顶部标题栏的实现方案。不同设备的标题栏始终只显示“返回按钮”、“歌单”以及“更多按钮”,中间空白区域通过Blank组件填充,可实现自适应拉伸能力。效果如图所示:

// Header.ets
@Component
export struct Header {@StorageProp('fontSize') fontSize: number = 0;build() {Row() {Image($r('app.media.ic_back')).width($r('app.float.icon_width')).height($r('app.float.icon_height')).margin({ left: $r('app.float.icon_margin') })Text($r('app.string.play_list')).fontSize(this.fontSize + HeaderConstants.TITLE_FONT_SIZE_PLUS).fontWeight(HeaderConstants.TITLE_FONT_WEIGHT).fontColor($r('app.color.title_color')).opacity($r('app.float.title_opacity')).letterSpacing(HeaderConstants.LETTER_SPACING).padding({ left: $r('app.float.title_padding_left') })Blank()Image($r('app.media.ic_more')).width($r('app.float.icon_width')).height($r('app.float.icon_height')).margin({ right: $r('app.float.icon_margin') }).bindMenu(this.getMenu())}.width(StyleConstants.FULL_WIDTH).height($r('app.float.title_bar_height')).zIndex(HeaderConstants.Z_INDEX)}
}

专辑封面

在这个章节中,我们需完成专辑封面的实现方案。专辑封面由图片、歌单介绍及常用操作(“收藏”“下载”“评论”“分享”)三部分组成,这三部分的布局可以用栅格实现。

  • 在sm断点下,图片占4个栅格,歌单介绍占8个栅格,常用操作占12个栅格。
  • 在md断点下,图片、歌单介绍和常用操作分别占12个栅格。
  • 在lg断点下,图片、歌单介绍和常用操作分别占12个栅格。

// AlbumComponent.ets
@Component
export struct AlbumComponent {@StorageProp('coverMargin') coverMargin: number = 0;@StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm';...build() {Column() {GridRow() {GridCol({span: { sm: GridConstants.SPAN_FOUR, md: GridConstants.SPAN_TWELVE, lg: GridConstants.SPAN_TWELVE }}) {this.CoverImage()}GridCol({span: { sm: GridConstants.SPAN_EIGHT, md: GridConstants.SPAN_TWELVE, lg: GridConstants.SPAN_TWELVE }}) {this.CoverIntroduction()}GridCol({span: { sm: GridConstants.SPAN_TWELVE, md: GridConstants.SPAN_TWELVE, lg: GridConstants.SPAN_TWELVE }}) {this.CoverOptions()}.padding({top: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ? $r('app.float.option_margin') : 0,bottom: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ? $r('app.float.option_margin') : 0})}.padding({top: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ?$r('app.float.cover_padding_top_sm') : $r('app.float.cover_padding_top_other')})}.margin({left: this.coverMargin,right: this.coverMargin})}
}

对于“收藏”、“下载”、“评论”、“分享”常用操作,通过设置容器组件的justifyContent属性为FlexAlign.SpaceEvenly,实现自适应均分能力。对于专辑图片,设置aspectRatio属性为1,Image组件宽高比固定为1:1,实现自适应缩放能力。

// AlbumComponent.ets
@Component
export struct AlbumComponent {...@BuilderCoverImage() {Stack({ alignContent: Alignment.BottomStart }) {...}.width(StyleConstants.FULL_WIDTH).height(StyleConstants.FULL_HEIGHT).aspectRatio(ContentConstants.ASPECT_RATIO_ALBUM_COVER)}...@BuilderCoverOptions() {Row() {ForEach(optionList, item => {Column({ space: ContentConstants.COVER_OPTION_SPACE }) {Image(item.image).height($r('app.float.option_image_size')).width($r('app.float.option_image_size'))Text(item.text).fontColor($r('app.color.album_name_introduction')).fontSize(this.fontSize - 1)}})}.height($r('app.float.option_area_height')).width(StyleConstants.FULL_WIDTH).justifyContent(FlexAlign.SpaceEvenly)}
}

歌曲列表

在这个章节中,我们需完成歌曲列表的实现方案。对于不同屏幕尺寸的设备,歌单列表的样式基本一致,但sm和md断点下的歌曲列表是单列显示,lg断点下的歌曲列表是双列显示。可以通过List组件的lanes属性实现这一效果。效果如图所示:

// PlayList.ets
@Component
export struct PlayList {@Consume coverHeight: number;@StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm';@StorageProp('fontSize') fontSize: number = 0;...build() {Column() {this.PlayAll()List() {LazyForEach(new SongDataSource(songList), (item: SongItem) => {ListItem() {Column() {this.SongItem(item.title, item.label, item.singer)Divider().strokeWidth(ContentConstants.DIVIDER_STROKE_WIDTH).color($r('app.color.black')).opacity($r('app.float.divider_opacity'))}.padding({left: $r('app.float.list_item_padding'),right: $r('app.float.list_item_padding')})}}, (item, index) => JSON.stringify(item) + index)}.width(StyleConstants.FULL_WIDTH).height(StyleConstants.FULL_HEIGHT).backgroundColor($r('app.color.white')).margin({ top: $r('app.float.list_area_margin_top') }).lanes(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ?ContentConstants.COL_TWO : ContentConstants.COL_ONE)}.padding({top: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ? 0 : $r('app.float.list_area_padding_top'),bottom: $r('app.float.list_area_padding_bottom')})}...}
}

播放控制区

在这个章节中,我们需完成底部播放控制区的实现方案。对于不同屏幕尺寸的设备,播放控制区显示的内容有差异,sm断点仅显示播放按钮,md断点显示上一首、播放和下一首按钮,lg断点显示收藏、上一首、播放、下一首、列表按钮,可以分别设置按钮的displayPriority属性,通过自适应隐藏能力实现。同时,歌曲信息与播放控制按钮之间通过Blank组件自动填充,实现自适应拉伸能力。效果如图所示:

// Player.ets
@Component
export struct Player {...build() {Row() {...Blank()Row() {Image($r('app.media.ic_favorite'))....displayPriority(PlayerConstants.DISPLAY_PRIORITY_ONE)Image($r('app.media.ic_previous'))....displayPriority(PlayerConstants.DISPLAY_PRIORITY_TWO)Image($r('app.media.ic_pause'))....displayPriority(PlayerConstants.DISPLAY_PRIORITY_THREE)Image($r('app.media.ic_next'))....displayPriority(PlayerConstants.DISPLAY_PRIORITY_TWO)Image($r('app.media.ic_music_list'))....displayPriority(PlayerConstants.DISPLAY_PRIORITY_ONE)}.layoutWeight(PlayerConstants.LAYOUT_WEIGHT_PLAYER_CONTROL).justifyContent(FlexAlign.End)}...}
}

特性集成

在这个章节中,我们将标题栏(header基础特性)、专辑内容(content基础特性)、播放控制区(player基础特性)集成到一起,作为应用的主入口。同时,后续项目如果新增更多的产品例如穿戴设备、智慧屏等,可定义新的products产品、自由集成不同的features基础特性,从而达成代码尽量复用、产品自由定制的目标。

// MainPage.ets
@Component
struct MainPage {...build() {Stack({ alignContent: Alignment.Top }) {Header()Content()Player()}...
}

解决系统能力差异的兼容性问题

前面章节主要介绍了如何解决页面适配、工程管理的问题,本章节主要介绍应用如何解决设备系统能力差异的兼容问题。

我们以一个简单例子说明:一个具有音频设备管理功能的应用,如何兼容地运行在有音频设备管理和无音频设备管理系统能力的设备上。

从开发到用户安装、运行,一般要经历几个阶段:应用分发和下载->应用安装->应用运行,要解决上面提到的兼容问题,一般有如下几种解决思路:

  1. 分发和下载:有音频设备管理设备的用户才能在应用市场可见该应用,供用户下载。
  2. 安装:有音频设备管理能力的设备才允许安装该应用。
  3. 运行:在运行阶段通过动态判断方式,在有音频设备管理设备上功能运行正常,在无音频设备管理设备上运行不发生Crash。

所以,对应的解决方案就有:

  1. 在分发阶段,上架的应用使用的系统能力是设备系统能力的子集。满足这个条件,用户才能在应用市场看到该应用。
  2. 在安装阶段,安装的应用调用的系统能力是设备系统能力的子集。满足这个条件,用户才能安装该应用。
  3. 通过canIUse接口判断是否支持某个系统能力。

本篇Codelab以音频设备管理为例,仅介绍canIUse接口判断的方式。点击右上角更多,如果当前系统支持音频设备管理能力,则显示“音频设备管理”菜单选项,否则不显示。

// Header.ets
@Component
export struct Header {...getMenu(): MenuData[] {let menuItem: MenuData = new MenuData();let menuDatas: MenuData[] = [];if (canIUse(HeaderConstants.SYSCAP_ETHERNET)) {menuItem.value = HeaderConstants.AUDIO_DEVICE_SERVICE;menuItem.action = () => {promptAction.showToast({message: HeaderConstants.AUDIO_DEVICE_SERVICE,duration: HeaderConstants.TOAST_DURATION});};menuDatas.push(menuItem);}return menuDatas;}
}

总结

您已经完成了本次Codelab的学习,并了解到以下知识点:

  1. 如何针对不同屏幕尺寸的设备完成一次开发,多端部署的方案设计。
  2. 如何按照合理的工程结构划分模块、组织代码。
  3. 通过自适应布局和响应式布局方案实现一次开发,多端部署。
  4. 通过运行时判断解决系统能力差异的兼容性问题。

为了帮助大家更深入有效的学习到鸿蒙开发知识点,小编特意给大家准备了一份全套最新版的HarmonyOS NEXT学习资源,获取完整版方式请点击→《HarmonyOS教学视频

HarmonyOS教学视频:语法ArkTS、TypeScript、ArkUI等.....视频教程

鸿蒙生态应用开发白皮书V2.0PDF:

获取完整版白皮书方式请点击→《鸿蒙生态应用开发白皮书V2.0PDF》

鸿蒙 (Harmony OS)开发学习手册

一、入门必看

  1. 应用开发导读(ArkTS)
  2. ……

二、HarmonyOS 概念

  1. 系统定义
  2. 技术架构
  3. 技术特性
  4. 系统安全
  5. ........

三、如何快速入门?《做鸿蒙应用开发到底学习些啥?》

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

四、开发基础知识

  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. ……

更多了解更多鸿蒙开发的相关知识可以参考:《鸿蒙 (Harmony OS)开发学习手册》

这篇关于HarmonyOS实战开发-一次开发,多端部署-音乐专辑的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

Pandas使用SQLite3实战

《Pandas使用SQLite3实战》本文主要介绍了Pandas使用SQLite3实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1 环境准备2 从 SQLite3VlfrWQzgt 读取数据到 DataFrame基础用法:读

tomcat多实例部署的项目实践

《tomcat多实例部署的项目实践》Tomcat多实例是指在一台设备上运行多个Tomcat服务,这些Tomcat相互独立,本文主要介绍了tomcat多实例部署的项目实践,具有一定的参考价值,感兴趣的可... 目录1.创建项目目录,测试文China编程件2js.创建实例的安装目录3.准备实例的配置文件4.编辑实例的

SpringBoot配置Ollama实现本地部署DeepSeek

《SpringBoot配置Ollama实现本地部署DeepSeek》本文主要介绍了在本地环境中使用Ollama配置DeepSeek模型,并在IntelliJIDEA中创建一个Sprin... 目录前言详细步骤一、本地配置DeepSeek二、SpringBoot项目调用本地DeepSeek前言随着人工智能技

Spring定时任务只执行一次的原因分析与解决方案

《Spring定时任务只执行一次的原因分析与解决方案》在使用Spring的@Scheduled定时任务时,你是否遇到过任务只执行一次,后续不再触发的情况?这种情况可能由多种原因导致,如未启用调度、线程... 目录1. 问题背景2. Spring定时任务的基本用法3. 为什么定时任务只执行一次?3.1 未启用