鸿蒙编程

506 阅读6分钟

鸿蒙编程

鸿蒙编程

一、布局

基础布局

  • Row - 横向布局
  • Column - 纵向布局

RowColumn 的布局方式称为线性布局

  • 线性布局中永远不会产生换行
  • 均不会出现滚动条
  • 横向排列的垂直居中,纵向排列的水平居中
  • 主轴 - 排列方向的轴
  • 侧轴 - 排列方向垂直的轴

堆叠布局

  • Stack - 在 Stack 内部后者永远会覆盖前者

弹性布局

  • Flex - 默认是不会进行换行的
  • Flex 方向设置是通过参数的,并非通过属性(参数能写啥具体还得参考文档)
Flex({
  wrap: FlexWrap.Wrap, 
  direction: FlexDirection.Row, 
  justifyContent: FlexAlign.SpaceEvenly, 
  alignItems: ItemAlign.Center
})

网格布局

  • Grid(网格布局) - 几行几列的布局,组件下只能放置 GridItem 组件
  • 可以控制列模版行模版 columnsTemplate() or rowsTemplate()

如果设置 1fr 1fr 表示,等分为两列,如果设置 1fr 2fr 表示左边一份,右边两份。 使用 columnsGap() 设置列和列的间距, rowsGap() 设置行和行的间距

  • GridRow(栅格布局) - 组件下放置 GridCol ( GridRow + GridCol )

可以使用参数 columns 设置每一行几列,超出会自动换行,columns 还可以设置不同设备具体显示几行几列gutter设置总体间距,如果设置单独的间距可使用子组件 padding margin 等去调整

相对布局

RelativeContainer - 容器的 id 固定为 __container__ 父容器,若相对兄弟元素需设置 id , 如下示例:

// 内部组件通过 alignRules 设置对齐方式
//  - 垂直方向对齐 top / bottom / center
//  - 水平方向对齐 left / right / middle
RelativeContainer() {
  Row(){}
    .width(100)
    .alignRules({
      top: { anchor: '__container__', align: VerticalAlign.Top}, 
      middle: { anchor: 'greenBox', align: HorizontalAlign.Center }
    })
}.width('100%').height('100%)

滚动条说明

Scroll - 滚动一般由用户的手指触发,我们也可以使用一个对象来控制滚动条 scroller

scroller: Scroller = new Scroller()
Scroll(this.scroller) {}
  .scrollBar(BarState.Off) // 是否显示滚动条
  .scrollable(ScrollDirection.Horizontal) // 横向滚动 
  
// 滚动方法
this.scroller.scrollEdge(Edge.Start)
this.scroller.scrollTo({
  xOffset: 220, 
  yOffset: 0, 
  animation: true
})

二、组件进阶

组件状态

当我们需要在组件中记录一些状态时,变量应该显示的在 struct 中声明,并注明类型

  • @State 修饰符 数据改变后响应式更新到页面上
  • @State 只能在自身或者第一层发生变化时产生UI更新,如果是第二层及以后的发生变化,只需重新 new 一个对象赋值就行

数据结构说明

  • 基本数据类型
    • string
    • number
    • boolean
    • null(空): null
    • undefined(未定义) :undefined
  • 引用数据类型
    • 接口 interface (属性不需要给初始值)
    • 类 class (属性是需要给初始值)
    • 数组: Array<类型> 或 类型[]
    • 函数: (参数类型) => 返回值的类型

注: interfaceclass 如何选择呢?

  • 如果只是进行类型的定义,使用 interface
  • 如果涉及状态的更新,使用 class

双向绑定

  • 数据发生变化,UI产生更新
  • UI产生交互,驱动数据变化

支持双向绑定的组件

当前 `$$` 支持基础类型变量,以及 `@State`、`@Link` 和 `@Prop` 装饰的变量

三、样式

语法(链式&枚举)

像素单位 px/vp/fp/lpx

使用虚拟像素,使元素在不同密度的设备上具有一致的视觉体量。官方定义

Q: 在不同屏幕物理分辨率下,如何实现等比例适配? A: 设置 lpx 基准值 - resources/base/profile/main-pages.json

// 添加 window 属性,设置 desigWidth,不设置也可以使用 lpx,默认 720
{
    "src": [...],
    "window": {
        "designWidth": 375
    }
}
// 然后在使用时,使用单位 lpx 如 .height('177lpx')
  • layoutWeight 占比: 占满主轴剩余空间的几份
  • aspectRatio 等比例: 宽高比

Image 和 资源 Resource

// 1.本地图片 自定义的 assets 目录 /开始查找
Image('/assets/ligong.png').width(100)
// 2.系统内置的资源图片(同时给宽高!!!)
Image($r('sys.media.ohos_ic_public_scan')).width(100).aspectRatio(1)
// 3.本地图片 预置设置资源图片 media(推荐使用)
Image($r('app.media.zipai')).width(100)
// 4.在线图片 网络地址
Image('https://gitlab.com/uploads/-/system/user/avatar/6415177/avatar.png?width=800').width(100)
// 5.下载的字体图标 (svg图标可以设置填充色)
Image($r('app.media.wechat')).width(100).fillColor('#ff00ff0f')
// 6.本地图片 预置设置资源图片 rawfile(一般放网页或视频文件,但也可以放图)
Image($rawfile('ligong.png')).width(100)

// 文字主题颜色
Text('Hello World!').fontSize(30).fontColor($r('app.color.forgive_color'))
// 国际化
Text($r('app.string.OldLi')).fontSize(30)

@Styles

开发中会出现大量代码在进行重复样式设置,@Styles 可以帮助样式服用

  • 仅支持 通用样式 和 通用事件
  • 不能进行参数传递
  • 不能进行导入导出

@Extend 复用

  • 必须明确样式添加给哪个组件
  • 使用 @Extend 装饰器修饰的函数只能是 全局
  • 可以进行组件 通用特定 样式声明
  • 函数可以传递参数,如果参数是状态变量,状态更新后会刷新UI,且参数可以是个函数
  • 不能进行导入导出

stateStyles 多态样式

ArkUI 提供以下五种状态:

  • focused: 获焦态
  • normal: 正常态(直接给组件添加就是 normal,但如果没有默认就无法恢复最初状态)
  • pressed: 按压态
  • disabled: 不可用态
  • selected: 选中态

使用较多的是 normalpressed 结合下的按压效果 enabled(true|false) 开启|禁用

// .stateStyles 如下使用示例:

TextInput({ placeholder: '请输入密码' })
  .stateStyles({
    focused: {
      .border({
        color: Color.Red,
        width: 1
      })
    }, 
    normal: {
     .border({
       width: 0
     })
    }
  })
  .type(InputType.Password)
  .showPasswordIcon(true)
  
Row()
  .stateStyles({
    pressed: {
      .backgroundColor(Color.Gray)
    },
    normal: {
      .backgroundColor(Color.White)
    }, 
    disabled: {
      .backgroundColor(Color.Pink)
    }
  })

四、界面渲染

条件渲染

在 ArkTS 中,我们要根据某个状态控制元素或组件显示隐藏,可采用条件渲染

  • if/else (创建销毁元素)
  • visibility 属性控制
  • 元素宽高 - 透明度 - 位置控制

循环渲染

  • ForEach
  • LazyForEach

下拉刷新

  • Refresh({ refreshing: $$是否刷新中, builder: 自定义刷新提示结构体? })
  • 内容是不限制的,可以是 List + ListItem 也可以是 Column
  • 刷新操作是在 onRefreshing 数据的更新,刷新状态的关闭

上拉加载

  • onScrollStart 监听开始滚动了 - 记录没有触底
  • onScrollStop 监听滚动停止了 - 是否触底(触底则加载数据)
  • onReachEnd 监听触底了(会触发2次) - 记录触底

鸿蒙进阶

一、状态管理

@Builder 自定义构建函数

如果不想在直接抽象组件,ArkUI 还提供了更轻量的UI元素复用机制 @Builder ,和组件的区别是:

  • 组件: 高阶函数(有自己的状态)
  • @Builder: 无状态函数

构建函数 参数传递规则

  • @Builder 修饰的函数内部,不允许改变参数值
  • 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递
  • 按引用传递时,ArkUI提供 $$ 作为引用传递参数的范式

@BuilderParam 传递UI

使用 @BuilderParam 的步骤

  • 前提: 需要出现 父子 组件的关系
  • 1.子组件声明 @BuilderParam getContent: () => void
  • 2.@BuilderParam 的参数可以不给初始值;放置组件出错则可(条件渲染、@Require、给默认值)等处理
  • 3.父组件传入的时候,它需要用 @Builder 修饰的函数又或者是一个箭头函数中包裹着

当我们的组件只有一个 @BuilderParam 的时候,此时可以使用 尾随闭包 的语法。 尾随闭包用空大括号就代表传递空内容,会替代默认内容。

二、状态共享

@Prop

父子单向 —— @Prop 装饰的变量可以和父组件建立单向的同步关系,适用于接收传递的数据,始终保持引用关系。 @Prop 装饰的变量是可变的,但是变化不会同步回其父组件。

子组件仍然可以改自己,更新UI,但不会通知父组件(单向),父组件改变后会覆盖子组件自己的值

@Link

父子双向 —— @Link 是双向的数据传递,只要使用 @Link 修饰了传递过来的数据,这时就是双向同步了,@Link 修饰不允许给初始值@Prop 是单向的

@Link 修饰符的要求: 你的父组件传值时传的必须是 @Link 或者 @State 修饰的数据. @Link 传递引用关系,共享同一份数据,@Prop 子组件会深拷贝一份(占用内存)

@Provide + @Consume

后代组件 —— 一个数据,后代多处会进行使用,不要层层传递,可使用共享 @Provide + @Consume

改别名:

  • 1.改提供方 —— 多处都需要改名,可统一修改
    • @Provide(提供的变量名) 原来的变量名: 类型 = 初始值
  • 2.改接收方 —— 只有少数需要更改
    • @Consume(提供的变量名) 新的变量名: 类型

@Watch

状态监听器 —— @Watch 可以用于主动检测数据变化,需要绑定一个函数,当数据变化时会触发这个函数。 @Watch('方法名') 观察数据变化的修饰符。

注意: @Watch 修饰符要写在 State Prop Link Provide 的修饰符下面,否则会有问题

  • 在第一次初始化的时候,@Watch 装饰的方法不会被调用

@Observed + @ObjectLink

使用 @Link 时,只有 @State 或者 @Link 修饰的数据才能用,如果一个数组内有多个对象,将对象传递给子组件的时候就没办法使用 @Link 了。 这时 ArkTS 支持 @Observed@ObjectLink 来实现这个,使用步骤:

  • 使用 @Observed 修饰这个 class 类,必须有类
  • 初始化数据: 数据确保是通过 @Observed 修饰的类 new 出来的
  • 通过 @objectLink 修饰传递的数据,可以直接修改被关联对象来更新UI

@Require 必传

4.0的编辑器中,如果子组件定义了 @Prop,那么父组件必须得传,不传则报错。 Next版本中,如果你想让父组件必须传递一个属性给你的 @Prop,作为强制性的约束条件,可以使用 @Require 修饰符

  • @Require 修饰符适用于作用在两个修饰符前面 @Prop@BuilderParam
  • 如果一个组件的属性要求必传也可以用 @Require

@Track class对象属性级更新

@Track 修饰符-只针对 class 对象中的某个属性的更新起作用,其余没修饰的属性不能进行UI展示。 @Track 装饰的属性变化时,只会触发该属性关联的UI更新。

Track 是性能优化的时候添加(假如只想根据对象中某个字段来更新或者渲染视图)
Track 要加就所有UI用到的属性都得加

三、应用状态

LocalStorage

LocalStorage 是页面级的UI状态存储,通过 Entry 装饰器接收的参数可以在页面内共享同一个 LocalStorage 实例。LocalStorage 也可以在 UIAbility 内,页面间共享状态。用法步骤:

  • 创建 LocalStorage 实例: const localStorage = new LocalStorage({key: value})
  • 导入共享对象,在需要使用的页面导入该对象,并传入 @Entry(localStorage)
  • 声明变量,用 @LocalStorageProp()@LocalStorageLink() 修饰进行接收

AppStorage

AppStorage 是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。- 注意它也是内存数据,不会写入磁盘

  • 第一种用法: 使用UI修饰符
    • 如果是初始化使用 AppStorage.setOrCreate(key, value)
    • 单向(组件内可变): @StorageProp('user')
    • 双向(全局均可变): @StorageLink('user')
  • 第二种用法: 使用API方法
    • 获取数据: AppStorage.get<ValueType>(key)
    • 覆盖数据: AppStorage.set<ValueType>(key, value)

PersistentStorage 状态持久化

用法 PersistentStorage.PersistProp('userName', 'lisi')

PersistentStorage 将选定的 AppStorage 属性保留在设备磁盘上。

限制条件

  • PersistentStorage 允许的类型和值有:
    • number, string, boolean, enum 等简单类型
    • 可以被 JSON.stringify()JSON.parse() 重构的对象。例如Date Map Set等内置类型则不支持,以及对象的属性方法不支持持久化。
  • PersistentStorage 不允许的类型和值有:
    • 不支持嵌套对象(对象数组,对象的属性是对象等)
    • 不支持 undefinednull

持久化是相对缓慢操作,写读磁盘是同步在UI线程中执行,最好是小于2KB数据
PersistentStorage只能在UI页面内使用,否则将无法持久化数据

Preferences 首选项

创建首选项 - 仓库的概念 - 应用可以有 N 个仓库,一个仓库中可以有 N 个 key

export class PreferencesClass {
  context: Context | null = null
  storeName: string = 'TOKEN_STORE_NAME'
  tokenKey: string = 'TOKEN_KEY'
  
  getStore() {
    return preferences.getPreferencesSync(this.context, {
      name: this.storeName
    })
  }
 
  async setToken(token: string) {
    this.getStore().putSync(this.tokenKey, token)
    await this.getStore.flush()
  }
  
  getToken() {
    return this.getStore().getSync(this.tokenKey, '') as string
  }
  
  async delToken() {
    this.getStore().deleteSync(this.tokenKey)
    await this.getStore().flush()
  }
 }

Environment 设备状态

image.png

// 使用
Environment.EnvProp('colorMode', Color.LIGHT)
// @StorageProp关联的环境参数可以在本地更改,但不能同步回AppStorage中
// 因为应用对环境变量参数是不可写的,只能在Environment中查询。
@StorageProp('colorMode') colorMode: ColorMode = ColorMode.LIGHT

四、网络管理

应用权限

ATM(AccessTokenManager)是HarmonyOS上基于AccessToken构建的统一的应用权限管理能力。根据授权方式不同,权限类型分为:

  • system_grant 系统授权 (配置后直接生效)
  • user_grant 用户授权 (向用户申请)

HTTP 请求

  • @ohos.net.http
  • axios 安装 ohpm install @ohos/axios

五、沉浸式

沉浸式 —— 全屏

实现的两种方案:

  • 使用 windowStage 的设置全屏的方式
  • 使用组件的安全区域扩展的方式
// 在 Ability 中获取主窗体,进行设置,如下给所有页面设置了沉浸式
windowStage.getMainWindowSync().setWindowLayoutFullScreen(true)

// 还可以获取安全区域高度,如下:
async aboutToAppear() {
  const win = await window.getLastWindow(getContext())
  // 防止全局没开启,指定页面开启沉浸式
  win.setWindowLayoutFullScreen(true)
  this.topSafeHeight = px2vp(win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height)
  this.bottomSafeHeight = px2vp(win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR).bottomRect.height)
}
// 扩展安全区域,expandSafeArea是一个按需的方式,哪个页面需要,直接自己设置
Image($r('app.media.handsome'))
  .width('100%')
  .height('50%')
  .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.Top])

沉浸式 —— 键盘避让

windowStage.loadContent('pages/Index', (err) => {
  // 加载页面后,开启键盘避让
  windowStage.getMainWindowSync()
    .getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE)
  });

六、路由

Navigation

Navigation使用指南

router

router 提供下列的几个方法

  • pushUrl - 压栈(最多页面栈为 32,到达32无法继续push)
  • replaceUrl - 替换页面栈
  • clear - 清空之前页面栈
  • back - 返回
  • getLength - 获取当前所有的路由长度
  • getParams - 获取参数
  • getState - 获取当前路由状态
  • router.RouterMode.Single 单例模式
  • showAlertBeforeBackPage - 返回阻断

模块间跳转

一个项目可能会有很多模块,HarmonyOS 提供了三层架构设计理念,主要包括三个层次: 产品定制层,基础特性层和公共能力层,为开发者构建了一个清晰、高效、可扩展的设计架构。

  • hap - 产品定制(入口具备ability-具备窗口能力)
  • hsp - 基础特性(动态共享包,可以按需打包,永远只有一份,体积优先建议使用
  • har - 公共能力(静态共享包,会多份拷贝,性能优先建议使用

hsp 和 har 区别?

相同点:

  • 模块间资源共享
  • 不支持在设备上单独安装/运行
  • 不支持在配置文件中声明UIAblity组件与ExtensionAbility组件
  • 不支持循环依赖,也不支持依赖传递

不同点:

  • har 发布二方或三方仓使用(hsp不可以)
  • har 中不支持在配置文件中声明 pages 页面
  • hsp 中支持在配置文件中声明 pages 页面
  • hsp 多模块依赖只会打包一份
  • hsp 支持按需加载

跳转方式1.1使用 地址 跳转:

// @bundle:包名/模块名/ets/pages/xxx
router.pushUrl({
  url: '@bundle:com.520td.project/library/ets/pages/Index'
})

跳转方式1.2使用Navigation进行各个模块间跳转

// TODO

七、生命周期

组件 - 生命周期

@Entry 装饰的组件,即页面生命周期,提供以下生命周期接口:

  • onPageShow: 页面每次显示时触发
  • onPageHide: 页面每次隐藏时触发
  • onBackPress: 当用户点击返回按钮时触发

@Component 装饰的自定义组件生命周期,提供以下生命周期接口:

  • aboutToAppear: 组件即将出现时回调该接口,在执行其 build() 函数之前执行
  • aboutToDisappear: 在自定义组件即将析构销毁时执行
  • aboutToReuse(API10新增): 当一个可复用的自定义组件从复用缓存中重新加入到节点树时回调该接口
  • onWillApplyTheme(API12新增): 获取当前组件上下文的主题色
  • onDidBuild(API12新增): onDidBuild函数在执行自定义组件的build()函数之后执行。不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现

因为 @Entry 也是 @Component 组件,所以页面组件同时拥有自定义组件的生命周期

smzq.png

UIAbility - 生命周期

UIAbility-生命周期

asmzq.png

  • onCreate: Ability创建时回调,执行初始化业务逻辑操作。
  • onDestory: Ability生命周期回调,在销毁时回调,执行资源清理等操作。
  • onWindowStageCreate: 当 WindowStage 创建后调用。
  • onWindowStageDestory: 当 WindowStage 销毁后调用。
  • onForeground: Ability 当应用从后台转到前台时触发。
  • onBackground: Ability 当应用从前台转到后台时触发
  • onBackPressed: 左滑销毁Ability生命周期时触发,return true可以不销毁Ability
  • onPrepareToTerminate: 销毁Ability时触发,可以添加交互,提示用户是否退出应用

另外更多UIAbility: 这15个能说多少算多少

image.png