目录
- 装饰器
- @Entry(入口)
- @Component(组件)
- @Builder(构建)
- @State(状态)
- @Prop(属性)
- @Preview(预览)
- Previewer
- Inspector
- 结构体
- struct
- build
- 自定义组件
- 自定义 Custom 组件
- export(导出) & import(导入)
- Page(页面)
- 生命周期
- aboutToAppear
- 数据
- Array(数组/集合)
- Map(映射)
- 容器(字典顺序)
- Grid(网格容器)
- List(列表)
- margin(外边距) & padding(内边距)
- 外边距
- 内边距
- Row(行) & Column(列)
- RelativeContainer(相对布局容器)
- Scroll(滑动布局)
- Swiper(轮播图)
- 组件(字典顺序)
- Blank()
- Image(图片)
- 图片的填充模式
- Text(文本)
- 组件:左上角对齐
- 文字:左对齐
- WebView
- 架构
- MVVM
- 三层架构设计
- Project 工程结构
- .hvigor/
- .idea/
- AppScope/
- resources/
- app.json5
- entry/
- .preview/
- src/
- main/
- ets/
- resources/
- base/
- en_US/
- zh_CN/
- rawfile/
- module.json5
- ohosTest/
- test/
- .gitignore(module目录)
- build-profile.json5(module目录)
- hvigorfile.ts(module目录)
- obfuscation-rules.txt(module目录)
- oh-package.json5(module目录)
- hvigor/
- oh_modules/
- .gitignore
- build_profile.json5(根目录)
- code-linter.json5
- hvigorfile.ts
- local.properties
- oh-package.json5
- oh-package-lock.json5
- 参考
装饰器
@Entry(入口)
@Entry 装饰的 @Component 将作为 UI 页面的入口
在单个 UI 页面中,最多可以使用@Entry 装饰一个自定义组件
Index {}
struct
@Component(组件)
自定义组件,@Component 功能更加多样,有自己的生命周期,还能支持预览效果
- @Component 装饰了 struct 关键字声明的类 Index
- Index 被 @Component 装饰后具备组件化的能力,通过实现 build 方法描述 UI
- @Component 装饰的 struct 类必须添加 @Component 装饰器,开发工具有提示
Index {build() {}
}
struct
@Builder(构建)
自定义构建函数,@Builder 更加轻量,能满足基础的组件封装,性能更好,但是不支持预览。
- 注意:如需更好的性能,建议采用 @Builder 方式进行组件定义封装
CustomBuilderXXX(paramxx: string) {Row() {Image($r(paramxx)).width(20).height(20)}.width('100%').height(64).alignItems(VerticalAlign.Center)}
@State(状态)
文本信息由 @State 装饰器装饰的状态变量 message 驱动
: string = 'HarmonyOS 速记';
message
@Prop(属性)
@Prop 装饰器,用于从父组件接收数据
- 注意:加入 @Prop 后,Previewer 会失效
@Preview(预览)
Previewer
- Previewer 可以直接预览 @Entry 装饰的整个页面
也可以预览由 @Preview 装饰的单独组件- 预览 @Entry 装饰的整个页面时,需要选中 @Entry 所在的文件,Previewer 才能顺利打开
- 将 Previewer 调整至 ComponentMode,便可以单独预览组件视图
- 如果修改的是文本内容,则需要手动保存(即 ctrl+s)后,Previewer 才会更新
如果修改的是相关属性,则不需要手动保存,Previewer 也会实时更新- 注意:此时的 Inspector 是不可用状态
Inspector
开启 Previewer 工具栏的 Inspector 工具,可以观察到当前组件树,并与 Previewer 交互
结构体
struct
定义组件结构体
- @Component 装饰的 struct 类必须添加 @Component 装饰器,开发工具有提示
- @Component 装饰的 struct 类必须重写 build 函数,开发工具有提示
struct Index {}
build
组件通过 build 函数用于描述 UI
- @Component 装饰的 struct 类必须重写 build 函数,开发工具有提示
build() {}
自定义组件
Custom {build() {}
}
struct
自定义 Custom 组件
// 用于组件预览
// 定义组件
struct Custom { // 组件名build() {Image($r('app.media.banner_pic1')) // 图片.width('100%') // 宽度.padding({ // 内边距left: 16,top: 10,right: 16,bottom: 10}).borderRadius(16) // 圆角.objectFit(ImageFit.Contain) // 缩放模式}
}
使用 Previewer 查看效果
export(导出) & import(导入)
独立定义的 Model/View,需要提供给外部使用,则需要 export 关键字修饰
在需要使用到文件,通过 import { XXX } from ‘…/model/XXX’; 导入后使用
// export 导出
export class BannerClass {id: string = '';imageSrc: ResourceStr = '';url: string = '';constructor(id: string, imageSrc: ResourceStr,url: string) {this.id = id;this.imageSrc = imageSrc;this.url = url;}
}import {BannerClass} from '../model/BannerClass';
@Component
struct Banner {@State banner: BannerClass;// ...
}
Page(页面)
生命周期
aboutToAppear
aboutToAppear 函数在创建自定义组件的新实例后,且在执行其 build 函数之前执行
数据
Array(数组/集合)
// 方式一:
list: Array<XXXClass> = [new XXXClass('01', 'zhangsan'),new XXXClass('02', 'lisi')
];// 方式二:(推荐)
list: XXXClass[] = [{id: '01', name: 'zhangsan'},{id: '02', name: 'lisi'}
];export class XXXClass {id: string = '';name: string = '';constructor(id: string, name: string) {this.id = id;this.name = name;}
}
Map(映射)
定义映射关系,即<key, value>,可以使用 Record 类型来进行映射,也可以使用 Map 进行映射,通过 TypeMapIcon[key] 的方式来获取 value
// Record 类型
const TypeMapIcon: Record<string, string> = {'指南': 'app.media.ic_guide','准备': 'app.media.ic_prepare','学习与获取证书': 'app.media.ic_medals','视频教程': 'app.media.ic_video',
}
容器(字典顺序)
Grid(网格容器)
网格容器,由“行”和“列”分割的单元格所组成,其中容器内各条目对应一个 GridItem 组件
如果仅设置行、列数量与占比中的一个,则网格单元将按照设置的方向排列
设置单行显示,则可以横向滑动
网格布局具有较强的页面均分能力,子组件占比控制能力,是一种重要自适应布局
Grid 是与 GridItem 搭配使用的
- 通过 ForEach 创建 GridItem,
- 在 GridItem 中加入自定义的 item 布局
- 自定义 item 布局中通过 @Prop 装饰器来接收外部传入的数据源
List(列表)
List 是与 ListItem 搭配使用的
- 通过 ForEach 创建 ListItem,
- 在 ListItem 中加入自定义的 item 布局
- 自定义 item 布局中通过 @Prop 装饰器来接收外部传入的数据源
// List 列表布局List({ space: 12 }) { // item 列间隔ForEach(this.tutorialList,(item: ArticleClass, index: number) => {ListItem(){TutorialItem({tutorialItem: item})}},(item: ArticleClass, index: number) => item.id)}.padding({left: 16,right: 16}).scrollBar(BarState.Off).divider({ // item 分割线样式strokeWidth: 2,startMargin: 38,endMargin: 0,color: Color.Orange})
margin(外边距) & padding(内边距)
外边距
Text(this.message)// .margin(12).margin({left: 20,top: 20,right: 20,bottom: 20})
内边距
Text(this.message)// .padding(12).padding({left: 20,top: 20,right: 20,bottom: 20})
Row(行) & Column(列)
Row() {Column() {Text(this.message).fontSize(20).fontWeight(FontWeight.Bold)}.width('100%')}.height('100%')
RelativeContainer(相对布局容器)
alignRules 在 RelativeContainer 中设置对齐规则(位置:上中下、左中右)
- top、center、bottom 上中下
- left、middle、right 左中右
注:alignRules 属性在 Row & Column 容器中无效
// 水平、竖直居中RelativeContainer() {Text(this.message).fontSize(20).fontWeight(FontWeight.Bold).alignRules({ // 对齐规则center: {anchor: '__container__',align: VerticalAlign.Center},middle: {anchor: '__container__',align: HorizontalAlign.Center}})}.width('100%').height('100%')
函数 alignRules 声明
alignRules(value: AlignRuleOption): T;
参数 AlignRuleOption 源码
declare interface AlignRuleOption {top?: { // 上anchor: string;align: VerticalAlign;};center?: { // 中anchor: string;align: VerticalAlign;};bottom?: { // 下anchor: string;align: VerticalAlign;};left?: { // 左anchor: string;align: HorizontalAlign;};middle?: { // 中anchor: string;align: HorizontalAlign;};right?: { // 右anchor: string;align: HorizontalAlign;};bias?: Bias;
}
结论
top、center、bottom、left、middle、right
- 对应着 设置子控件的基线,即以子控件的哪个位置作为对齐的基准点
VerticalAlign#Top、Center、Bottom & HorizontalAlign#Start、Center、End
- 这些属性才是对应着 设置子控件相对于父布局的对齐规则,但需要配合上面的基准来使用才会得到想要的正确效果
Scroll(滑动布局)
Scroll 组件需要注意下面两种情况:
- 列表的数据数量过多时,将导致内容会超过界面
- 列表的数据数量不足时,将导致Scroll组件滚动时出现空白区域
- 解决方法:所以此处要在最外层包裹一个Scroll组件
- constraintSize 属性,用于设置约束大小
- 设置 minHeight 为 100%,可以解决当内容条数不足时,Scroll组件滚动时会出现空白区域的错误效果
- edgeEffect 属性,用于设置边缘滑动效果
- EdgeEffect.None 表示设置为没有效果
- EdgeEffect.Fade 表示设置为淡入淡出效果
- EdgeEffect.Spring 表示设置为弹性物理动效。该效果滑动到边缘后可以根据初始速度或通过触摸事件继续滑动一段距离,松手后回弹
- scrollable 属性,用于设置滚动方向
- ScrollDirection.Vertical 表示允许纵向滚动
- ScrollDirectionHorizontal 表示允许横向滚动
- scrollBar 属性,用于设置以何种方式显示滚动条
- BarState.On 一直显示
- BarState.Off 不显示
- BarState.Auto 滑动时显示
Scroll() {Column() {...}...}.backgroundColor('#F1F3F5').align(Alignment.TopStart).constraintSize({ // 解决内容不足时,会出现空白区域的现象minHeight: '100%' }).edgeEffect(EdgeEffect.Spring) // EdgeEffect.Spring 弹性效果
Swiper(轮播图)
使用Swiper构建轮播图
Index { message: string = 'HarmonyOS 速记';build() {Column() {// TitleText(this.message).padding({left: 16,top: 10,right: 16,bottom: 10}).width('100%').textAlign(TextAlign.Start).fontWeight(FontWeight.Bold)// BannerBanner().margin({left: 16,right: 16})}.height('100%').width('100%').backgroundColor('#F1F3F5')}
}
struct Banner {// Banner 数据源 banners: Array<BannerBean> = [new BannerBean('pic0', $r('app.media.banner_pic0'),'https://developer.huawei.com/consumer/cn/training/course/video/C101718352529709527'),new BannerBean('pic1', $r('app.media.banner_pic1'),'https://developer.huawei.com/consumer/cn/'),new BannerBean('pic2', $r('app.media.banner_pic2'),'https://developer.huawei.com/consumer/cn/deveco-studio/'),new BannerBean('pic3', $r('app.media.banner_pic3'),'https://developer.huawei.com/consumer/cn/arkts/'),new BannerBean('pic4', $r('app.media.banner_pic4'),'https://developer.huawei.com/consumer/cn/arkui/'),new BannerBean('pic5', $r('app.media.banner_pic5'),'https://developer.huawei.com/consumer/cn/sdk')];build() {Swiper() { // 轮播图ForEach(this.banners, // 数据源(item: BannerBean, index: number) => { // 用于生成 item 组件Image(item.imageSrc).width('100%').borderRadius( 10 ) // 设置图片的圆角,不是 Banner 的圆角,所以感觉有点怪异.objectFit(ImageFit.Contain)},(item: BannerBean, index: number) => item.id // 用于 item 增量更新,所以需要 id)}.autoPlay(true) // 开启自动播放.loop(true) // 开启轮训// .interval(1000) // 时间间隔// .indicator(true) // 使用默认的指示器.indicator( // 配置指示器new DotIndicator().color('#1a000000').selectedColor('#0A59F7'))}
}/*** Banner 结构体*/
class BannerBean {id: string = '';imageSrc: ResourceStr = '';url: string = '';constructor(id: string, imageSrc: ResourceStr, url: string) {this.id = id;this.imageSrc = imageSrc;this.url = url;}
}
struct
组件(字典顺序)
Blank()
该组件可以自动填充主轴方向的空余空间
Row() {Text(this.navBarItem.order)...Text(this.navBarItem.title)...Blank() // 通过 Blank 组件,来自动填充主轴方向的剩余空间Image($r('app.media.ic_arrow')).width(12).height(24)}
Image(图片)
用于显示图片
Image($r('app.media.banner_pic1')) // 设置图片资源.width('100%') // 宽度.padding({ // 内边距left: 16,top: 10,right: 16,bottom: 10}).borderRadius(16) // 圆角.objectFit(ImageFit.Contain) // 缩放模式
方法一
通过 $r(‘app.media. 文件名字’) 将 media 文件夹下的本地图片读取到 Image 组件
Image($r('app.media.image_path'))
方法二
定义 ResourceStr 变量,值为 media 文件夹下的本地图片路径,通过 $r(上面定义的 ResourceStr 变量) 将 media 文件夹下的本地图片读取到 Image 组件
private path: ResourceStr = 'app.media.image_path';
Image($r(path))
方法三
- 通过 rawfile 目录中配置的 JSON 文件来配置本地图片的路径,这种情况时图片的路径为 string 类型、非 ResourceStr 类型,所以需要定义 string 类型、非 ResourceStr 类型 的变量来接收 JSON 中定义的图片路径,然后直接通过 Image 组件加载图片即可。(具体解析代码详见当前页面内容 Project 工程结构#rawfile)
- 由于 json 文件解析限制,当前不支持 Previewer 预览器模式,应选择模拟器或真机进行调试。
图片的填充模式
.objectFit(ImageFit.Contain)
设置图片的填充模式
- Contain 模式,即保持宽高比进行缩小或者放大,使得图片完全显示在显示边界内
- Cover 模式,即保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界。
Text(文本)
组件:左上角对齐
Text(this.message) // 默认宽度 wrap_content.id('HelloWorld').fontSize(20).fontWeight(FontWeight.Bold).fontStyle(FontStyle.Italic).lineHeight(55).alignRules({ // 对齐规则:左上角(top、left) 为 对其基准点top: { // 上边缘 为 对其基准点anchor: '__container__',align: VerticalAlign.Top // 上对齐},left: { // 左边缘 为 对其基准点anchor: '__container__',align: HorizontalAlign.Start // 左对齐}})
文字:左对齐
Text(this.message).width('100%') // 设置宽度 match_parent.textAlign(TextAlign.Start) // 设置文字朝向 居左
WebView
加载网络资源
- 创建ArkTS侧代码。在 ets/pages/ 中创建 CourseLearning.ets 文件,新增基础的代码框架
- 添加网络权限 ohos.permission.INTERNET
- 创建 webviewController,开发者后续可以通过该Controller控制Web组件加载的界面
加载本地资源
- 将本地 Web 资源包 course_learning 目录放入 resources/rawfile/ 目录下
- 创建ArkTS侧代码。在 ets/pages/ 中创建 CourseLearning.ets 文件,新增基础的代码框架
- 创建 webviewController,开发者后续可以通过该Controller控制Web组件加载的界面
- 加载本地界面,修改 Web 组件的 src 属性,使用 $rawfile 加载刚刚放入 rawfile 目录下的 course_learning 资源。
- 注意:需要在真机或者模拟器上运行代码,Previewer 预览器暂不支持 Web 组件的预览效果
import { webview } from '@kit.ArkWeb'
struct CourseLearning {private webviewController: webview.WebviewController = new webview.WebviewController();build() {Column() {// 加载网络 Web 资源Web({ src: 'http://www.huawei.com', controller: this.webviewController }).domStorageAccess(true)// 加载本地 Web 资源// Web({ src: $rawfile('course_learning/index.html'), controller: this.webviewController })// .domStorageAccess(true)}}
}
架构
MVVM
ArkUI 采取 MVVM = Model + View + ViewModel 模式,其中 状态管理模块 起到的就是ViewModel 的作用,将数据(Model)与视图(View)绑定在一起,数据更新 会直接 更新视图。
- Model 为我们定义的数据结构和数据来源,通过 @State 等装饰器来装饰对应的数据,就提供了响应式能力,Model 数据的变化就能够触发 UI 的更新。
- MVVM 的目录组织方式,一般适用于单个模块内文件组织
三层架构设计
为了更好地适配复杂应用的开发,建议采用三层架构的方式对整个应用的功能进行模块化,实现高内聚、低耦合开发。
三层工程结构如下:
- commons(公共能力层):用于存放公共基础能力集合(如工具库、公共配置等)。commons层可编译成一个或多个HAR包或HSP包,只可以被products和features依赖,不可以反向依赖。
- features(基础特性层):用于存放基础特性集合(如应用中相对独立的各个功能的UI及业务逻辑实现等)。各个feature高内聚、低耦合、可定制,供产品灵活部署。不需要单独部署的feature通常编译为HAR包或HSP包,供products或其它feature使用。需要单独部署的feature通常编译为Feature类型的HAP包,和products下Entry类型的HAP包进行组合部署。features层可以横向调用及依赖common层,同时可以被products层不同设备形态的HAP所依赖,但是不能反向依赖products层。
- products(产品定制层):用于针对不同设备形态进行功能和特性集成。products层各个子目录各自编译为一个Entry类型的HAP包,作为应用主入口。products层不可以横向调用。
三层架构工程目录迁移
Project 工程结构
.hvigor/
生成文件
.idea/
生成文件
AppScope/
resources/
base/element/
- 放置 base 资源文件
- string.json
base/media/
- 放置 base 图片资源
- app_icon.png
app.json5
app 基础信息配置文件
// app.json5
{"app": {"bundleName": "com.villen.hm", // 包名"vendor": "example", // 提供商"versionCode": 1000000, // version code"versionName": "1.0.0", // 版本号"icon": "$media:app_icon", // 应用图标"label": "$string:app_name" // 应用名称}
}
entry/
.preview/
- 生成文件,类似 android 中的 build 目录
src/
entry/src/
main/
entry/src/main/
ets/
entry/src/main/ets/
源代码
resources/
entry/src/main/resources/
资源
base/
entry/src/main/resources/base/
base 资源
element/ -> 资源文件
- color.json
- string.json
media/ -> 资源图片
profile/ -> 资源图片
- backup_config.json -> 描述 备份相关配置
- main_pages.json -> 描述 路由信息 或定义 窗口相关配置
en_US/
entry/src/main/resources/en_US/
英文资源
- element/ -> 资源文件
- string.json
zh_CN/
entry/src/main/resources/zh_CN/
中文资源
- element/ -> 资源文件
- string.json
rawfile/
entry/src/main/resources/rawfile/
该目录用于存放 json 数据资源、Web 资源等本地资源文件。
- 该目录中的资源文件会被直接打包进应用,不经过编译,也不会被赋予资源文件ID。通过指定文件路径和文件名引用。
- 由于 json 文件解析限制,当前不支持 Previewer 预览器模式,应选择模拟器或真机进行调试。
- 读取 json 文件内容,需要使用到 ResourceManager 的 getRawfileContent 方法,从 rawfile 目录中读取对应的 JSON 文件
- 转换内容需要两个步骤:
- 将获取的buffer内容转换为字符串
- 由于 ResourceManager 获取到的是 ArrayBufferLike 类型的内容,所以需要将对应的内容转换为字符串,并将字符串解析为对应的数据结构。
- 考虑到其他的文件也会使用这个公共方法,可以新建一个 util 文件夹,并创建一个 BufferUtil 文件,实现这个字符串转换方法。
- 将字符串转换为页面数据结构
- 该功能可以用于临时 mock 数据
// BufferUtil.ets
import { util } from '@kit.ArkTS';/*** buffer 转 string* 用于从 rawfile 中读取 JSON 数据** @param buffer* @returns*/
export function bufferToString(buffer: ArrayBufferLike): string {let textDecoder = util.TextDecoder.create('utf-8', {ignoreBOM: true});let resultPut = textDecoder.decodeWithStream(new Uint8Array(buffer), {stream: true});return resultPut;
}
module.json5
entry/src/main/module.json5
清单文件,类似 android 中的 AndroidManifest.xml
- 配置页面
- 权限申请
// 权限申请,位于 module 节点下"requestPermissions": [{"name": "ohos.permission.INTERNET","reason": "$string:apply_for_network"}]
ohosTest/
测试
test/
测试
.gitignore(module目录)
忽略文件
build-profile.json5(module目录)
hvigorfile.ts(module目录)
obfuscation-rules.txt(module目录)
oh-package.json5(module目录)
配置依赖项,配置完后,会提示需要安装依赖,可以通过 Run “ohpm install” 执行安装过程。
"dependencies": {"@ohos/learning": "file:../../features/learning","@ohos/map": "file:../../features/map","@ohos/quickstart": "file:../../features/quickstart"}
hvigor/
hvigor-config.json5
oh_modules/
生成文件
.gitignore
忽略文件
build_profile.json5(根目录)
code-linter.json5
hvigorfile.ts
local.properties
oh-package.json5
oh-package-lock.json5
生成文件
参考
HarmonyOS应用开发快速入门