#冲刺创作新星#[六]第一个hap应用 原创 精华
作者:王石,胡瑞涛
上节回顾
在 [四]移植开源库节我们学习了在OpenHarmony下如何移植编译运行一个开源库,接下来我们来熟悉下hap应用的结构框架和如何自己开发一个简单的hap程序。
简介
HAP文件是在OpenHarmony系统下编译生成的可执行文件。HAP 包是由代码、资源、第三方库以及应用配置文件打包生成的模块包,主要分为两种类型:entry 和 feature。
entry:应用的主模块,作为 OpenHarmony 应用的入口,提供了应用的基础功能。
feature:应用的动态特性模块,作为应用能力的扩展,可以根据用户的需求和设备的类型进行选择性安装。
OpenHarmony 用户应用程序包可以只包含一个基础的 entry 包,也可以包含一个基础的 entry 包和一个或多个功能型的 feature 包。
工具
- DevEco Studio 3.0 Beta4
- HUAWEI DevEco Studio For OpenHarmony是基于IntelliJ IDEA Community开源版本打造,面向OpenHarmony全场景多设备的一站式集成开发环境(IDE),DevEco Studio 3.0支持在HarmonyOS 3.0 Beta版上开发应用及服务,并已适配ArkUI声明式编程范式、ArkCompiler方舟编译,同时提供低代码开发、双向预览、全新构建工具、模拟器、调试调优、信息中心等功能,为开发者提供工程模板创建、开发、编译、调试、发布等E2E的OpenHarmony应用/服务开发。
- 下载链接: HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者
-
安装步骤: 下载与安装软件-快速开始-HUAWEI DevEco Studio For OpenHarmony使用指南-工具-HarmonyOS应用开发 | HarmonyOS
注:建议在选项界面都勾选DevEco Studio,Add “bin” folder to the PATH,Add “Open Folder as Project”
-
配置开发环境:配置开发环境-快速开始-HUAWEI DevEco Studio For OpenHarmony使用指南-工具-HarmonyOS应用开发 | HarmonyOS
ETS
-
基于TS扩展的声明式开发范式的方舟开发框架是一套开发极简、高性能、跨设备应用的UI开发框架,支持开发者高效的构建跨设备应用UI界面。
-
基于TS扩展的声明式开发范式提供了一系列基础组件,这些组件以声明方式进行组合和扩展来描述应用程序的UI界面,并且还提供了基本的数据绑定和事件处理机制,帮助开发者实现应用交互逻辑。
@Entry //用@Entry装饰的自定义组件用作页面的默认入口组件,也可以理解为页面的根节点。 一个页面有且仅能有一个@Entry,只有被@Entry修饰的组件或者其子组件,才会在页面上显示。 @Component //@Component装饰的struct表示该结构体具有组件化能力,能够成为一个独立的组件,这种类型的组件也称为自定义组件,在build方法里描述UI结构。 struct Hello { //在声明式UI中,所有的页面都是由组件构成。组件的数据结构为struct @State myText: string = 'World' build() { //build函数用于定义组件的声明式UI描述,在build方法中以声明式方式进行组合自定义组件或系统内置组件。 Column() { //Column:沿垂直方向布局的容器。 Text('Hello') //Text:显示一段文本的组件。 .fontSize(30) Text(this.myText) .fontSize(32) Divider() //Divider:提供分隔器组件,分隔不同内容块/内容元素。 Button() { //Button:按钮组件,可快速创建不同样式的按钮,通常用于响应用户的点击操作。 Text('Click me') .fontColor(Color.Red) }.onClick(() => { this.myText = 'UI' }) .width(500) .height(200) } } }
基本概念
-
装饰器:方舟开发框架定义了一些具有特殊含义的装饰器,用于装饰类、结构、方法和变量。装饰器就是某一种修饰,给被装饰的对象赋予某一种能力,比如@Entry就是页面入口的能力,@Component就是组件化能力。
-
自定义组件:可重用的UI单元,可以与其他组件组合,如@Component装饰的struct Hello。
-
UI描述:声明性描述UI结构,例如build()方法中的代码块。
-
内置组件:框架中默认内置的基本组件和布局组件,开发者可以直接调用,仅用于解释UI描述规范。如Column、Text、Divider、Button等。
-
属性方法:用于配置组件属性,如fontSize()、width()、height()、color()等。
-
事件方法:在事件方法的回调中添加组件响应逻辑。例如,为Button组件添加onClick方法,在onClick方法的回调中添加点击响应逻辑。
应用
1. 创建
-
在DevEco Studio的欢迎页,选择Create Project开始创建一个新工程。
-
根据工程创建向导,在OpenHarmony页签,选择“Empty Ability”模板,点击Next。
- 点击Next,各个参数保持默认值即可,点击Finish。
关于各个参数的详细介绍,请参考创建OpenHarmony工程-工程管理-HUAWEI DevEco Studio For OpenHarmony使用指南-工具-HarmonyOS应用开发 | HarmonyOS
-
Project name:工程的名称,可以自定义。
-
Project type:工程的类型,标识该工程是一个传统方式的需要安装的应用(Application)或原子化服务(Atomic service),默认类型为Application。
-
Bundle name:软件包名称,默认情况下,应用ID也会使用该名称,应用发布时,应用ID需要唯一。如果“Project type”选择了Atomic service,则Bundle name的后缀名必须是.hmservice。
-
Save location:工程文件本地存储路径。
-
**Compile SDK:**编译的SDK版本。
-
Enable Super Visual:选择开发模式,部分模板支持低代码开发,可选择打开该开关。
-
**Language:**开发语言。
-
Compatible SDK:兼容的SDK最低版本。
-
Device type:该工程模板支持的设备类型。
-
**Show in service center:**是否在服务中心露出。
-
工程创建完成后,DevEco Studio会自动进行工程的同步,同步成功如下图所示。
-
将搭载OpenHarmony标准系统的开发板与电脑连接。
- 点击File > Project Structure > Project > Signing Configs界面勾选“Automatically generate signature”,等待自动签名完成即可,点击“OK”。如下图所示:
- 为了保证OpenHarmony应用的完整性和来源可靠,在应用构建时需要对应用进行签名。经过签名的应用才能在真机设备上安装、运行、和调试。如果没有配置签名,会报错:hvigor WARN: Will skip sign ‘hap’,Invalid signingConfig is configured for ‘default’ product.
2. 目录结构
-
新项目的目录结构
entry为应用的主模块,类似与Android Studio的app模块,一个APP中,对于同一设备类型必须有且只有一个entry类型的HAP,可独立安装运行。
-
使用previewer查看程序的预览图
点击View > Tool Windows > Project > Previewer 如下图所示:
- 方法一:
- 方法二:
- 成功预览后会生成.preview结构:
-
使用build编译程序
点击Build > Rebuild 进行编译; Build > Build Hap(s) /App(s) >Build Hap(s) 生成hap文件
- hap文件的生成路径:entry/build/outpus/default
-
目录结构中文件分类如下:
.ets结尾的eTS(extended TypeScript)文件,用于描述UI布局、样式、事件交互和页面逻辑。
各个文件夹和文件的作用:
- app.ets文件用于全局应用逻辑和应用生命周期管理。
- pages目录用于存放所有组件页面。
- common目录用于存放公共代码文件,比如:自定义组件和公共方法。
说明:
- 资源目录resources文件夹位于src/main下,此目录下资源文件的详细规范以及子目录结构规范参看资源文件的分类。
- 页面支持导入TypeScript和JavaScript文件。
3. 基础组件应用
-
如上所说,基于TS扩展的声明式开发范式提供了一系列基础组件,如:基础组件,容器组件,媒体组件,绘制组件和画布组件等,本节我们主要使用容器组件。
-
list列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据,例如图片和文本。
-
listitem用来展示列表具体item,宽度默认充满List组件,必须配合List来使用。
-
text可以显示一段文本。
编写一个简单的ui界面,其homepage为:
import ConfigData from '../../Utils/ConfigData'; import {SubEntryComponent} from '../../component/subEntryComponent'; @Entry @Component struct HomePage { @State message: string = 'The Test' build() { Column(){ GridContainer({columns:12, sizeType: SizeType.Auto, gutter: vp2px(1) === 2 ? '12vp' : '0vp', margin: vp2px(1) === 2 ? '24vp' : '0vp'}) { Row({}) { Column() {} .width(ConfigData.WH_100_100) .height(ConfigData.WH_100_100) .useSizeType({ xs: { span: 0, offset: 0 }, sm: { span: 0, offset: 0 }, md: { span: 0, offset: 0 }, lg: { span: 2, offset: 0 } }); Column() { Text(this.message) .fontSize($r("app.float.font_30")) .lineHeight($r("app.float.lineHeight_41")) .fontWeight(FontWeight.Bold) .fontFamily('HarmonyHeiTi') .textAlign(TextAlign.Start) .width(ConfigData.WH_100_100) .padding({left: $r('app.float.distance_26'), top: $r('app.float.distance_12'), bottom: $r('app.float.distance_17')}) Column({space:'16vp'}){ List(){ ListItem(){ SubEntryComponent({targetPage:"pages/test1",title:$r("app.string.test1")}) } .padding({top: $r('app.float.distance_2'), bottom: $r('app.float.distance_2')}) ListItem(){ SubEntryComponent({targetPage:"pages/test2",title:$r("app.string.test2")}) } .padding({top: $r('app.float.distance_2'), bottom: $r('app.float.distance_2')}) ListItem(){ SubEntryComponent({targetPage:"pages/test3",title:$r("app.string.test3")}) } .padding({top: $r('app.float.distance_2'), bottom: $r('app.float.distance_2')}) ListItem(){ SubEntryComponent({targetPage:"pages/test4",title:$r("app.string.test4")}) } .padding({top: $r('app.float.distance_2'), bottom: $r('app.float.distance_2')}) } } .width(ConfigData.WH_100_100) } .padding({left: $r('app.float.distance_24'), right: $r('app.float.distance_24')}) .width(ConfigData.WH_100_100) .height(ConfigData.WH_100_100) .useSizeType({ xs: { span: 12, offset: 0 }, sm: { span: 12, offset: 0 }, md: { span: 12, offset: 0 }, lg: { span: 8, offset: 2 } }); Column() {} .width(ConfigData.WH_100_100) .height(ConfigData.WH_100_100) .useSizeType({ xs: { span: 0, offset: 12 }, sm: { span: 0, offset: 12 }, md: { span: 0, offset: 12 }, lg: { span: 2, offset: 10 } }) } .width(ConfigData.WH_100_100) .height(ConfigData.WH_100_100); } .width(ConfigData.WH_100_100) .height(ConfigData.WH_100_100); } .backgroundColor($r("sys.color.ohos_id_color_sub_background")) .width(ConfigData.WH_100_100) .height(ConfigData.WH_100_100); } }
-
调用的subEntryComponent为:
import ConfigData from '../Utils/ConfigData'; @Component export struct SubEntryComponent{ private targetPage: string; private title:string | Resource; @State isTouched:boolean = false; private date: any = null; private deviceId: any = null; build() { Navigator({target: this.targetPage}){ Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems:ItemAlign.Center }) { Row() { Text(this.title) .fontSize($r('app.float.font_30')) .lineHeight($r('app.float.wh_value_32')) .fontColor($r('app.color.font_color_182431')) .fontWeight(FontWeight.Medium) .margin({left: $r('app.float.distance_8')}) .textAlign(TextAlign.Start) .height($r('app.float.wh_value_32')); } Image(('app.media.ic_settings_arrow')) .width($r('app.float.wh_value_12')) .height($r('app.float.wh_value_100')) .margin({right: $r('app.float.distance_8')}); } .height(ConfigData.WH_100_100) .width(ConfigData.WH_100_100) .borderRadius($r('app.float.radius_12')) .linearGradient(this.isTouched ? { angle: 90, direction: GradientDirection.Right, colors: [[$r("app.color.DCEAF9"), 0.0], [$r("app.color.FAFAFA"), 1.0]] } : { angle: 90, direction: GradientDirection.Right, colors: [[$r("sys.color.ohos_id_color_foreground_contrary"), 1], [$r("sys.color.ohos_id_color_foreground_contrary"), 1]]}) .onTouch((event: TouchEvent) => { if (event.type === TouchType.Down) { this.isTouched = true; } if (event.type === TouchType.Up) { this.isTouched = false; } }) } .params( {date: this.date, deviceId: this.deviceId} ) .padding($r('app.float.distance_4')) .height($r('app.float.wh_value_100')) .borderRadius($r('app.float.radius_24')) .backgroundColor($r("sys.color.ohos_id_color_foreground_contrary")); } }
-
预览图:
4. 界面跳转
在实现界面跳转时一般需要用到targetPage或者router:url方法。
-
targetpage方法
targetpage:"{页面路径}"
-
router方法
定义: const 'NAME' = '路径'; 实现: Router.push({ uri: PAGE_URI_TEST_NAME });
-
js标签配置
开发框架需要应用的config.json中配置相关的js标签,其中包含了实例名称、页面路由、视图窗口配置信息。pages定义每个页面入口组件的路由信息,每个页面由页面路径和页面名组成,页面的文件名就是页面名。比如:
说明:
pages列表中第一个页面为应用的首页入口。
页面文件名不能使用组件名称,比如:Text.ets、Button.ets等。
每个页面文件中必须包含页面入口组件(@Entry装饰)。
- 预览图:
5. api组件
-
在基于TS扩展的声明式开发范式中有众多的API参考,初学者可以使用这些组件练习,以此来加深对ets的熟悉。如:
// 提供在给定范围内选择评分的组件 @Entry @Component struct RatingExample { @State rating: number = 1 @State indicator: boolean = false build() { Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { Text('current score is ' + this.rating).fontSize(20) Rating({ rating: this.rating, indicator: this.indicator }) .stars(5) .stepSize(0.5) .onChange((value: number) => { this.rating = value }) }.width(350).height(200).padding(35) } }
-
预览图:
6. 自定义组件
组件的成员变量可以通过两种方式初始化:
-
本地初始化,如:
@State counter: Counter = new Counter() ts
-
在构造组件时通过构造参数初始化,如:
MyComponent({counter: $myCounter})
如可以使用CustomDialogController类显示自定义弹窗
-
导入对象
dialogController : CustomDialogController = new CustomDialogController(value:{builder: CustomDialog, cancel?: () => void, autoCancel?: boolean})
-
使用open()和close()对其进行开关显示。
open(): void
显示自定义弹窗内容,若已显示,则不生效。
close(): void
关闭显示的自定义弹窗,若已关闭,则不生效。
-
示例:
class ClassA { public a:number constructor(a: number) { this.a = a } } @Entry @Component struct Parent { @State parentState: ClassA = new ClassA(1) build() { Column() { Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { CompA({ aState: new ClassA(2), aLink: $parentState }) } Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { CompA({ aLink: $parentState }) } Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { CompA({ aState: new ClassA(3), aLink: $parentState }) } } } } @Component struct CompA { @State aState: any = false @Link aLink: ClassA build() { Column() { CompB({ bLink: $aLink, bProp: this.aState }) CompB({ bLink: $aState, bProp: false }) } } } @Component struct CompB { @Link bLink: ClassA @Prop bProp: boolean build() { Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text(JSON.stringify(this.bLink.a)).fontSize(30) Text(JSON.stringify(this.bProp)).fontSize(30).fontColor(Color.Red) }.margin(10) } }
hap是鸿蒙应用必须的一环