写点什么

教不会你算我输系列 | 手把手教你 HarmonyOS 应用开发

作者:百度Geek说
  • 2024-02-27
    上海
  • 本文字数:9755 字

    阅读完需:约 32 分钟

教不会你算我输系列 | 手把手教你HarmonyOS应用开发

作者 | 小狮子


导读


鸿蒙系统(HarmonyOS)是华为推出的一款面向万物互联的全场景分布式操作系统。鸿蒙支持手机、平板、智能穿戴、智慧屏和车机等多种终端设备,发展史如下:



全文 9755 字,预计阅读时间 32 分钟。

01 HarmonyOS 开发简介

1.1 技术概念

在万物智联时代重要机遇期,鸿蒙系统结合移动生态发展的趋势,提出了三大技术理念:一次开发,多端部署;可分可合,自由流转;统一生态,原生智能。


1.1.1 一次开发,多端部署


一次开发,多端部署:指的是一个工程,一次开发上架,多端按需部署。目的是为了支撑开发者高效的开发多种终端设备上的应用。为了实现这一目的,鸿蒙系统提供了几个核心能力,包括多端开发环境,多端开发能力及多端分发机制。

1.1.2 可分可合,自由流转

元服务是鸿蒙系统提供的一种全新的应用形态,具有独立入口,用户可以通过点击、碰一碰,扫一扫等方式直接触发,无需显式安装,由程序框架后台静默安装使用,可为用户提供便捷服务。



可分可合:在开发态,开发者通过业务解耦,把不同的业务拆分为多个模块。在部署态,开发者可以将一个或多个模块自由组合,打包成一个 App Pack 统一上架。在分发运行态,每个 HAP 都可以单独分发满足用户单一使用场景,也可以多个 HAP 组合分发满足用户更加复杂的使用场景。



自由流转:传统应用只能在单个设备内运行,当用户有多个设备,且要完成多个任务时,则要在多个设备间来回切换。因此应用能够在设备之间流转,不间断给用户提供服务的能力就变得非常重要。鸿蒙系统提供了自由流转的能力,使得开发者可以方便的开发出跨越多个设备的应用,用户也能够方便的使用这些功能。

1.1.3 统一生态,原生智能

统一生态:移动操作系统和桌面操作系统的跨平台应用开发框架不尽相同,从渲染方式的角度可以归纳为 WebView 染原、原生渲染和自渲染这三类,鸿蒙系统对应的提供系统 WebView、ArkUI 框架和 XComponent 能力来支撑三种类型的跨平台框架的接入。



原生智能:鸿蒙系统内置强大的 AI 能力,面相鸿蒙生态应用的开发,通过不同层次的 AI 能力开放,满足开发者的不同开发场景下的诉求,降低应用的开发门槛,帮助开发者快速实现应用智能化。

1.2 开发套件


鸿蒙开发套件包含设计、开发、测试、运维套件以及 OS 开放能力集:


HarmonyOS Design:鸿蒙视觉设计套件


ArkTS:鸿蒙编程语言


ArkUI:鸿蒙声明式 UI 框架


ArkCompoler:鸿蒙方舟编译器


DevEco Studio:鸿蒙开发 IDE


DevEco Testing:鸿蒙测试套件


AppGallery Connect:鸿蒙应用发布套件

1.2.1 开发语言 - ArkTS

ArkTS 基于 TypeScript(简称 TS)的超集,扩展名为.ets,继承了 TS 的所有特性(NEXT 版本有阉割)。引入了静态类型,扩展了声明式 UI、状态管理等能力,让开发者以更简洁、更自然的方式开发高性能应用。

1.2.2 ArkUI 框架


ArkUI 框架提供开发者两种开发方式:基于 ArkTS 的声明式开发范式和基于 JS 扩展的类 Web 开发范式。声明式开发范式更加简洁,类 Web 开发范式对 Web 及前端开发者更友好。


(1)ArkTS 的声明式开发范式:


采用声明式开发范式进行应用开发,相同场景下,对比类 Web 开发范式代码更为精简,并且在性能内存方面进一步优化提升。另外 ArkUI 框架还提供了 API 扩展机制,通过此种机制进行封装风格统一的 JS 接口。包括状态管理、布局、组件化、装饰器、动效、事件交互、绘制能力、混合开发、跨平台


  • 状态管理



声明式开发范式的核心思想是数据驱动 UI 变化,通过提供的状态进行数据管理,这里状态管理指的是,管理数据发生变化时,UI 组件更新的范围。


@State:组件拥有的状态属性,当 @State 装饰的变量更改时,组件会重新渲染更新 UI。


@Link:组件依赖于其父组件拥有的某些状态属性,当任何一个组件中的数据更新时,另一个组件的状态都会更新,父子组件重新渲染。


@Prop:类似 @Link,但子组件所做的更改不会同步到父组件上,属于单向传递。


@Provide:作为数据的提供方,可以更新其子节点的数据,并触发页面渲染。


@Consume:在感知到 @Provide 数据更新后,会触发当前自定义组件的重新渲染。


@StorageLink :组件通过使用 @StorageLink(key)装饰的状态变量,与 AppStorage 建立双向数据绑定,key 为 AppStorage 中的属性键值。当创建包含 @StorageLink 的状态变量的组件时,该状态变量的值将使用 AppStorage 中的值进行初始化。在 UI 组件中对 @StorageLink 的状态变量所做的更改将同步到 AppStorage ,并从 AppStorage 同步到任何其他绑定实例中。


@StorageProp :组件通过使用 @StorageProp(key)装饰的状态变量,将与 AppStorage 建立单向数据绑定,key 标识 AppStorage 中的属性键值。当创建包含 @StoageProp 的状态变量的组件时,该状态变量的值将使用 AppStorage 中的值进行初始化。AppStorage 中的属性值的更改会导致绑定的 UI 组件进行状态更新。


  • 布局


用特定的组件或者属性来管理用户页面所放置 UI 组件的大小和位置。ArkUI 框架支持多种布局方式,如弹性布局、列表、宫格、栅格布局等。


  • 组件化


组件是 ArkUI 框架中的基础显示单元,一切 UI 显示的内容都是组件。ArkUI 框架提供多种开箱即用的 UI 组件,如文本显示 Text、Button 等,并提供了向多种设备形态的多态 UI 能力。开发者可以直接使用预置组件或者自定义自己的组件。


  • 装饰器


自定义组件的场景中,通常会遇到要动态传入不同的 UI 元素的情况,为了满足该场景 ArkUI 框架同时提供了动态构建 UI 元素的能力。


@Builder:可通过 @Builder 装器进行描述,该装器可以修饰函数,此函数可以在 build() 函数之外声明,并在 build() 函数中或其他 @Builder 修的函数中使用,在一个自定义组件内快速生成多个布局内容。


@Style:声明式范式为了避免开发者对重复样式的设置,通过 @Styles 装器可以将多条样式设置提炼成一个方法,直接在组件声明的位置使用。


@Extend:@Extend 装饰器可以将新的属性函数添加到内置组件上,如 Text、Column、Button 等,快速扩展原生组件。


  • 动效


与传统开发方式不同,ArkUI 动画是由数据变化驱动动画启动,而不再是直接控制动画的播放。


1、属性动画:组件的某些通用属性变化时,可以通过属性动画实现渐变效果,比如设置组件 size。


2、显式动画:全局 animateTo 显式动画接口,指定由于闭包代码导致的状态变化插入过渡动效,比如组件的显示与隐藏。


3、转场动画:转场动画包括间转场、组件内过渡转场和共享元素转场三种,比如页面跳转。


  • 事件交互


ArkUI 框架提供了很多交互事件,这些事件提供了不同的信息用于处理相关程序交互逻辑,大致分位两类:


1、UI 组件事件:比如 TextInput 输入框产生的 onEditChange 输入文本变更事件,List 列表组件产生的 onScrollIndex 列表滚动事件。


2、交互事件:点击事件,拖拽事件,焦点事件,触摸事件,按键事件,鼠标事件,手势事件等


  • 绘制能力


ArkUI 框架提供两种 2D 自定义绘制能力。一种是通过图形组合的方式,利用布局、绝对定位和各种图形进行组合实现;另种是通过绘制 API 在 Canvas 画布上进行绘制。


  • 混合开发


ArkUI 框架提供了 XComponent 组件,支持加载应用动态库、NAPI 跨语言调用,进行 C++绘制能力的开发。


  • 跨平台


鸿蒙生态构建了 ArkUI 跨平台框架的核心设施,将相应的能力扩展到 iOS 和 Android 平台上。开发者可以通过一份代码,结合相应的工具链,同时生成多个 OS 平台的应用工程,并可编译出相应的应用程序,在相应的平台运行。


(2)类 Web 开发范式:



类 Web 范式的整体接口采用与传统 Web 开发相似的设计理念,采用 HML、CSS 与 JS 三种类型的文件进行页面开发,开发者可以基于此范式方便地进行 UI 构建,同时提供数据绑定机制,支持通过 JS 进行数据更新,进而更新 UI。

02 第一个鸿蒙版 Hello World

2.1 开发环境搭建



  • 安装 NodeJS

  • 打开 DevEco Studio,如果已经安装 NodeJS 会自动载入路径



△注意:最新 LTS 版本为 18,但鸿蒙要求 v14.I9.1~v17.0.0,官方 IDE 为 v16.19.1。


  • 手动安装 NodeJS:


// 安装nvm,nvm可以管理多版本NodeJS。npm install -g n
// 安装v16.19.1nvm install v16.19.1
// 切换使用指定的版本nodenvm use v16.19.1
// 彻底切换版本nvm alias default v16.19.1
复制代码


  • 安装 Ohpm(Open Harmony Pack Manager)鸿蒙包管理系统,安装第三库

  • 安装 Harmony SDK


注意的是如果不小心关闭了,也不需要恐慌,因为再次打开也回不到开始的配置页面了。


此时可以打开 DevEco Studio -> Help -> Diagnose Development Environment 重新配置。


2.2 创建第一个鸿蒙应用

Create Project -> Application -> Empty Ability



Next



Mode:ArkTS 的声明式开发范式与类 Web 开发范式,官方主推 Stage 声明式开发范式。

2.3 项目结构(有一点点像 Android Studio!!)


上部左侧为工程目录,上部中间为代码区,上部右侧为预览区,下部为调试区。

2.3.1 工程目录

  • AppScope -> app.json5:应用配置信息,包含报名、应用名、版本等



  • entry 为该工程下的 Module 应用包

  • entryability -> EntryAbility.ts 为该 Module 的启动入口



  • entry -> pages 存放页面文件



  • entry -> resources 存放资源文件

  • entry -> module.json5 配置页面信息,可以用与 router 跳转

  • entry -> ohosTest 为测试模块

2.3.2 预览区

鸿蒙提供实时的页面预览,且 Reload 模式可以实时更新 UI,终于可以不用 Command+R 了👍🏻。

2.4 运行

鸿蒙提供 Previewer 预览模式、Local Emulator 本地模拟器、Remote Emulator 远程模拟器、Remote Device 远程真机、Local Device 本地真机等模式运行。


实测 Mate30 运行鸿蒙应用屏幕刷新有问题,非常卡,可以使用 scrcpy 投屏来解决显示问题。Mate60 正常。

2.4.1 真机运行

  • 遥遥领先连续点击 "系统版本号" 开启开发者模式。

  • 系统 -> 更新,开启 abd 调试。

  • 插入数据线,选择 "传输文件"。

  • 证书配置:File > Project Structure > Project > Signing Configs 界面,勾选“Automatically generate signature”。

03 小试牛刀 - 度加首页

3.1 底部 Tab

使用 Tabs 组件创建底部 tabbar,子 page 分别为 CreatePage、ClassPage、MinePage。


import promptAction from '@ohos.promptAction'import { TabbarItem } from '../Models/TabbarItem'import { ClassPage } from './ClassPage'import { CreatePage } from './CreatePage'import { MinePage } from './MinePage'@Entry@Componentstruct Index {
// tabbar数据源 tabList: TabbarItem[] = [ { defaultIcon: $r("app.media.home"), activeIcon: $r("app.media.home_focus"), title:"创作" }, { defaultIcon: $r("app.media.class"), activeIcon: $r("app.media.class_focus"), title:"课堂" }, { defaultIcon: $r("app.media.my"), activeIcon: $r("app.media.my_focus"), title:"我的" }, ]
// 记录索引,index从0开始 @State tabActivityIndex: number = 0
// 展示tabbaritem @Builder TabbarBuilder(item: TabbarItem, index: number) { Column({space: 5}) { Image(this.tabActivityIndex == index ? item.activeIcon : item.defaultIcon) .width(24) .aspectRatio(1) Text(item.title) .fontSize(12) .fontColor(this.tabActivityIndex == index ? "#FFFFFF" :"#99FFFFFF") } }
build() { Column() { // Tabs Tabs({index: this.tabActivityIndex}) { ForEach(this.tabList,(item: TabbarItem, index: number) => { TabContent() { if (index == 0) { CreatePage() } else if (index == 1) { ClassPage() } else { MinePage() } } .tabBar(this.TabbarBuilder(item,index)) } ) } .barPosition(BarPosition.End) // tab放在底部 .onChange((index: number) => { this.tabActivityIndex = index promptAction.showToast({message:index.toString()}) }) } .height("100%") .width("100%") .backgroundColor("#16141F") .justifyContent(FlexAlign.Center) }}
复制代码

3.2 创作 page

创作页面分为 5 大区域:迎新年、开始创作、工具栏、热点活动 tab、热点互动内容。


import { ActivityComponent } from '../Components/ActivityComponent'import { HotComponent } from '../Components/HotComponent'
// 创作页面@Componentexport struct CreatePage {
// 活动下标 @State activitySelectedIndex: number = 0
build() { Column() {
// 迎新年行 Row() { Row(){ Text("🎉迎新年,瓜分万元流量券") // 新玩法!AI帮你制作专属贺岁视频🧧 .width("178") .fontColor("rgba(255,255,255,0.6)") .fontSize(14) Image($r("app.media.img_home_link_arrow")) .width(16) .aspectRatio(1) } .width(194) .padding({left: 10})
Row(){ Image($r("app.media.img_reward_star")) .width(16) .height(16) Text("签到") .width("40") .fontColor("rgba(255,255,255,0.6)") .fontSize(14) .textAlign(TextAlign.Center) } .width(66) } .height("50") .width("100%") .justifyContent(FlexAlign.SpaceBetween)
// 开始创作 Row({space: 10}){ // 开始创作 Stack() { Image($r("app.media.img_home_entrance")) .width("100%") .height("100%") .borderRadius(8) // 圆角 Image($r("app.media.jiandao1")) .width(30) .aspectRatio(1) .margin({bottom: 30}) Text("开始创作") .width(56) .height(20) .fontColor("rgba(255,255,255,1)") .fontSize(14) .margin({top: 30}) } .height("100%") .layoutWeight(1) // 告诉占满剩余 .borderRadius(8) // 圆角
// 草稿箱 Column(){ Text("2") .fontColor("#fff") .fontSize(20) .margin({top: 15}) Text("草稿箱") .fontColor("#fff") .fontSize(14) .margin({top: 10}) } .backgroundColor("#282438") .height("100%") .width(86) .borderRadius(8) // 圆角 } .height(86) .width("100%") .justifyContent(FlexAlign.SpaceBetween) .padding({left: 15, right: 15})
// AI提词器 Row() { Row() { Column() { Image($r("app.media.home_prompter")) .width(40) .height(28) Text("AI 提词") .fontColor("#fff") .fontSize(10) } .width(70) .height(70) .justifyContent(FlexAlign.Center) .margin({left: 15})
Column() { Image($r("app.media.home_ttv")) .width(40) .height(28) Text("AI 成片") .fontColor("#fff") .fontSize(10) } .width(70) .height(70) .justifyContent(FlexAlign.Center)
Column() { Image($r("app.media.home_digital_human")) .width(40) .height(28) Text("AI 数字人") .fontColor("#fff") .fontSize(10) } .width(70) .height(70) .justifyContent(FlexAlign.Center)
Column() { Image($r("app.media.home_subtitle")) .width(40) .height(28) Text("文字提取") .fontColor("#fff") .fontSize(10) } .width(70) .height(70) .justifyContent(FlexAlign.Center) .margin({right: 15})
} .height("100%") .width("100%") .backgroundColor("#282438") .borderRadius(8) .justifyContent(FlexAlign.SpaceBetween)
} .backgroundColor("#16141F") .height(76) .width("100%") .padding({left: 15, right: 15}) .margin({top: 15}) .borderRadius(8)
// 春节活动 Row() { Row() { Image($r("app.media.huodong1")) .borderRadius(8) } .height("100%") .width("100%") .backgroundColor("#282438") .justifyContent(FlexAlign.SpaceBetween) } .backgroundColor("#16141F") .height(76) .width("100%") .padding({left: 15, right: 15}) .margin({top: 15}) .borderRadius(8)
// 热点 Row() { Row() { Image(this.activitySelectedIndex == 0 ? $r("app.media.img_hot_tab_focus") : $r("app.media.img_hot_tab")) .width(45) .height(25) } .margin({left: 20}) .width(45) .height(54) .onClick( () => { this.activitySelectedIndex = 0 })
Text("活动") .fontSize(18) .fontColor(this.activitySelectedIndex == 0 ? "rgba(255,255,255,0.4)" : "rgba(255,255,255,1)") .margin({left: 20}) .onClick( () => { this.activitySelectedIndex = 1 }) .margin({left: 15, top: 2}) .height(54) } .margin({top: 5}) .height(54)
if (this.activitySelectedIndex == 0){ HotComponent() } else { ActivityComponent() } } .width("100%") .height("100%") .alignItems(HorizontalAlign.Start) }}
复制代码

3.3 热点 Component


import { hotContentItemList, hotContentItemList2, HotContentItemModel } from '../Models/HotContentItemModel'import { HotContentComponent } from './HotContentComponent'import { TabItemListComponent, TabItemModel } from './TabItemListComponent'// 热点@Componentexport struct HotComponent {
// 定义item的数据源 @State tabItemModelList: TabItemModel[] = [ new TabItemModel({title: "全网"}), new TabItemModel({title: "娱乐"}), new TabItemModel({title: "情感"}), new TabItemModel({title: "历史"}), new TabItemModel({title: "健康"}), new TabItemModel({title: "社会"}), new TabItemModel({title: "科技"}), new TabItemModel({title: "春节"}) ]
// 选中的item下标 @State selectedIndex: number = 0
// 热点内容列表 @State hotContentItemList: HotContentItemModel[] = hotContentItemList
// 热点内容列表2.为了模拟不同tab下的数据 @State hotContentItemList2: HotContentItemModel[] = hotContentItemList2
build() { Column() { Text("全网热点 每分钟更新") .fontColor("rgba(255,255,255,0.3)") .fontSize(12) .margin({left: 15, top: 0})
// 热点tab列表 TabItemListComponent({itemModelList: $tabItemModelList, selectedIndex: $selectedIndex})
if (this.selectedIndex == 0) { // 热点内容 HotContentComponent({itemList: this.hotContentItemList}) } else if (this.selectedIndex == 1) { // 热点内容 HotContentComponent({itemList: this.hotContentItemList2}) } else { // 热点内容 HotContentComponent({itemList: this.hotContentItemList}) } } .alignItems(HorizontalAlign.Start) .width("100%") .layoutWeight(1) .padding({bottom: 80}) }}
复制代码

3.4 活动 Component


import { createActivityListItemList, CreateActivityListItemModel } from '../Models/CreateActivityListItemModel'// 活动@Componentexport  struct ActivityComponent {
@State createActivityListItemList: CreateActivityListItemModel[] = createActivityListItemList
// 时间戳格式化 formatDateTime(timestamp: number) { let dateTime = new Date(timestamp * 1000) const year = dateTime.getFullYear(); const month = dateTime.getMonth() + 1 < 10 ? '0' + (dateTime.getMonth() + 1) : dateTime.getMonth() + 1; const date = dateTime.getDate() < 10 ? '0' + dateTime.getDate() : dateTime.getDate(); const hours = dateTime.getHours() < 10 ? '0' + dateTime.getHours() : dateTime.getHours(); const minutes = dateTime.getMinutes() < 10 ? '0' + dateTime.getMinutes() : dateTime.getMinutes(); const seconds = dateTime.getSeconds() < 10 ? '0' + dateTime.getSeconds() : dateTime.getSeconds(); return (month + '月' + date + '日'); }
build() { List() { ForEach(this.createActivityListItemList, (model: CreateActivityListItemModel, index: number) => { ListItem() { Row() { Image(model.pic) .width(114) .height(64) .margin({left: 15}) .borderRadius(8) Column() { Text(model.name) .fontColor(Color.White) .fontWeight(14) Text(this.formatDateTime(model.start_time) + "-" + this.formatDateTime(model.end_time)) .fontColor("rgba(255,255,255,0.3)") .fontWeight(12) .margin({top:10}) } .alignItems(HorizontalAlign.Start) .margin({left: 15}) } } .height(84) }) } .width("100%") .aspectRatio(1) }}
复制代码

04 基础图谱


——————————END——————————


参考资料:


[1]官方 Demo:


https://gitee.com/harmonyos/codelabs


[2]鸿蒙开发者网站:


https://developer.huawei.com/consumer/cn/


[3]鸿蒙开发者社区:


https://developer.huawei.com/consumer/cn/forum/


[4]《鸿蒙生态应用开发白皮书 V2.0》:https://developer.huawei.com/consumer/cn/doc/guidebook/harmonyecoapp-guidebook-0000001761818040


[5]鸿蒙开发者认证:https://developer.huawei.com/consumer/cn/training/dev-certification/a617e0d3bc144624864a04edb951f6c4



截止至 2023 年 12 月 31 日,已经有 50000 余开发者加入了鸿蒙开发行列,相信随着鸿蒙的发展迭代,未来队伍将会越来越壮大。


推荐阅读:


漫谈数据分布可视化分析

云上业务一键性能调优,应用程序性能诊断工具 Btune 上线

一文详解静态图和动态图中的自动求导机制

用户头像

百度Geek说

关注

百度官方技术账号 2021-01-22 加入

关注我们,带你了解更多百度技术干货。

评论

发布
暂无评论
教不会你算我输系列 | 手把手教你HarmonyOS应用开发_HarmonyOS_百度Geek说_InfoQ写作社区