HarmonyOS应用开发入门(三)

一、状态管理

1、概述

声明式UI中,是以状态驱动视图更新:

  • 状态(State):指驱动UI更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染。
  • 视图(View):UI渲染,指将build方法内的UI描述和@Builder装饰的方法内的UI描述映射到界面。

2、@State

@State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就可以触发其直接绑定UI组件的刷新。当状态改变时,UI会发生对应的渲染改变。

  • @State装饰器标记的变量必须初始化,不能为空值,可以作为其子组件单向和
    双向同步的数据源;
  • @State支持基本数据类型,数组,类 ;
  • 嵌套类型以及数组中的对象属性无法触发视图更新。

3、@Prop和@Link

当父子组件之间需要数据同步时,可使用@Prop和@Link装饰器。

@Prop@Link
同步类型单项同步双向同步
允许装饰的变量类型
  • 基本数据类型、enum类型
  • 父组件对象类型,子组件是对象属性
  •  不可以的数组、any
  • 父子类型一致:基本数据类型,数组,类 
  • 数组中元素增删替换会引起刷新
  • 嵌套类型以及数组中的对象属性无法触发视图更新
初始化方式不允许子组件初始化,变量是可变的,但修改不会同步回父组件父组件传递,禁止子组件初始化,变量的修改是同步的,父组件的更新也会同步给子组件

4、@Provide和@Consume

@Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。

@Provide基本数据类型,数组,类@Provide作为数据提供方,可以更新其子孙节点数据,并触发页面渲染
@Consume基本数据类型,数组,类感应到@Provide装饰的变量更新后,会触发当前自定义组件的重新渲染

注:

  • 在使用@Provide和@Consume时应避免循环引用,否则会造成死循环;
  • @Consume不可设置默认初始值;

5、@ObjectLink和@Observed

@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步。

@ObjectLink被@Observed所装饰类的对象父组件或者其他兄弟组件内关联的状态数据都会更新UI
@Observed应用于类UI页面管理该类中的数据变更

注:@ObjectLink装饰的变量是私有变量,只能在组件内访问。 

练习:

一些小tips:

(1)基础组件--Progress:进度条,用于显示内容加载或操作处理的进度。

Progress(options: {value: number, total?: number, type?: ProgressType})

        其中,value用于设置初始进度值,total用于设置进度总长度,type用于设置Progress样式。 Progress有5种可选类型,通过ProgressType可以设置进度条样式,ProgressType类型包括:

  • ProgressType.Linear(线性样式)
  • ProgressType.Ring(环形无刻度样式)
  • ProgressType.ScaleRing(环形有刻度样式)
  • ProgressType.Eclipse(圆形样式)
  • ProgressType.Capsule(胶囊样式)

(2)基础组件--Checkbox:提供多选框组件,通常用于某选项的打开或关闭。

Checkbox(options?: {name?: string, group?: string })

        其中,name用于设置多选框名称,group用于设置多选框的群组名称。其属性select用于设置多选框是否选中,selectedColor用于设置多选框选中状态颜色。可添onChange()事件。

(3)容器组件--叠层布局(Stack):用于在屏幕上预留一块区域来显示组件中的元素,提供元素可以重叠的布局。层叠布局通过Stack容器组件实现位置的固定定位与层叠,容器中的子元素依次入栈,后一个子元素覆盖前一个子元素,子元素可以叠加,也可以设置位置。

        Stack组件通过alignContent参数实现位置的相对移动,类型为Alignment。子元素在容器内的对齐方式有9种方式:

(4) 实现列表侧滑:ListItem的
swipeAction属性可用于实现列表项的左右滑动功能。swipeAction属性方法初始化时有必填参数SwipeActionOptions,其中,start参数表示设置列表项右滑时起始端滑出的组件,end参数表示设置列表项左滑时尾端滑出的组件。

 

// 任务类
@Observed
class Task {
  static id: number = 1
  // 任务名称
  name: string = `任务${Task.id++}`
  // 任务状态:是否完成
  finished: boolean = false
}

// 统一卡片样式
@Styles function card(){
  .width('95%')
  .padding(20)
  .backgroundColor(Color.White)
  .borderRadius(15)
  .shadow({ radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4 })
}

// 任务完成样式
@Extend(Text) function finishedTask(){
  .decoration({ type: TextDecorationType.LineThrough})
  .fontColor('#B1B2B1')
}


@Entry
@Component
struct PropPage {
  // 总任务数量
  @State totalTask: number = 0
  // 已经完成任务数量
  @State finishTask: number = 0
  // 任务组数
  @State tasks: Task[] = []

  build(){
    Column({ space: 10 }){
      // 任务进度卡片
      TaskStatistics({ finishTask: this.finishTask, totalTask: this.totalTask })
      // 任务列表
      TaskList({ finishTask: $finishTask, totalTask: $totalTask })

    }.width('100%').height('100%')
    .backgroundColor('#F1F2F3')
  }

}

@Component
struct TaskStatistics{

  @Prop finishTask: number
  @Prop totalTask: number

  build(){

    Row(){
      Text('任务进度').fontSize(30).fontWeight(FontWeight.Bold)
      Stack(){
        Progress({
          value: this.finishTask,
          total: this.totalTask,
          type: ProgressType.Ring
        }).width(100)
        Row(){
          Text(this.finishTask.toString()).fontSize(24).fontColor(Color.Blue)
          Text(' / ' + this.totalTask.toString()).fontSize(24)
        }
      }
    }.card().margin({ top:20, bottom: 10 }).justifyContent(FlexAlign.SpaceEvenly)
  }
}

@Component
struct TaskList{

  @Link totalTask: number
  @Link finishTask: number
  @State tasks: Task[] = []

  handleTaskChange()
  {
    this.totalTask = this.tasks.length
    this.finishTask = this.tasks.filter(item => item.finished).length
  }

  build(){
    Column(){
      // 新增任务按钮
      Button('新增任务').width(200)
        .onClick(() => {
          this.tasks.push(new Task())
          this.handleTaskChange()
        })
      // 任务列表
      List({ space: 10 }){
        ForEach(this.tasks,(item, index) => {
          ListItem(){
            TaskItem({ item: item, onTaskChange: this.handleTaskChange.bind(this)})
          }.swipeAction({ end: this.DeleteButton(index)})
        })
      }.width('100%').alignListItem(ListItemAlign.Center)
      .layoutWeight(1)
    }
  }

  @Builder DeleteButton(index: number){
    Button(){
      Image($r('app.media.delete')).fillColor(Color.White).width(20)
    }.width(40).height(40).type(ButtonType.Circle)
    .backgroundColor(Color.White).margin(5)
    .onClick(() => {
      this.tasks.splice(index, 1)
      this.handleTaskChange()
    })
  }
}

@Component
struct TaskItem {
  @ObjectLink item: Task
  onTaskChange: () => void
  build(){
    Row(){
      if (this.item.finished){
        Text(this.item.name).fontSize(20)
          .finishedTask()
      }else {
        Text(this.item.name).fontSize(20)
      }
      Checkbox().select(this.item.finished)
        .onChange(val => {
          this.item.finished = val
          this.onTaskChange()
        })
    }.card().justifyContent(FlexAlign.SpaceBetween)
  }
}

二、页面路由

页面路由指在应用程序中实现不同页面之间的跳转和数据传递。 这些页面存储在页面栈中,页面栈的最大容量上限为32个页面,使用router.clear()方法可以清空页面栈,释放内存。

1、跳转模式

Router模块提供了两种跳转模式,分别是:

  • router.pushUrl():目标页面不会替换当前页,而是压入页面栈。这样可以保留当前页的状态,并且可以通过返回键或者调用router.back()方法返回到当前页。

  • router.replaceUrl():目标页面会替换当前页,并销毁当前页。这样可以释放当前页的资源,并且无法返回到当前页。

2、实例模式

Router模块提供了两种实例模式,分别是:

  • Standard:多实例模式,也是默认情况下的跳转模式。目标页面会被添加到页面栈顶,无论栈中是否存在相同url的页面。

  • Single:单实例模式。如果目标页面的url已经存在于页面栈中,则会将离栈顶最近的同url页面移动到栈顶,该页面成为新建页。如果目标页面的url在页面栈中不存在同url页面,则按照默认的多实例模式进行跳转。

3、使用方法

(1)首先导入HarmonyOS提供的Router模块:

import router from '@ohos.router';

(2)然后利用router实现跳转、返回等操作:

跳转以pushUrl为例:

错误码
100001内部错误,可能是渲染失败
100002路由地址错误
10003路由栈中页面超过32

要获取传递过来的参数,可通过getParams()方法。

返回上一页通过router.back(), 如果要返回指定页可通过设置url参数实现,同时可携带参数。

注:为了实现页面跳转我们需要在resources>base>profile中的main_pages.json中进行页面配置。也可在创建页面时,选择new Page,可自动配置。

返回前弹窗警告: router.showAlertBeforeBackPage()。

练习:

import router from '@ohos.router'
class RouterInfo{
  // 页面路径
  url: string
  // 页面标题
  title: string

  constructor(url: string, title: string) {
    this.url = url
    this.title = title
  }
}

@Entry
@Component
struct Index {
  @State message: string = '页面列表'

  private routers: RouterInfo[] = [
    new RouterInfo('pages/Day1','图片查看案例'),
    new RouterInfo('pages/Day1.2','商品列表案例'),
    new RouterInfo('pages/Day2','任务列表案例')
  ]

  build() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .height(80)

        List({space: 15}){
          ForEach(this.routers,(router) => {
            ListItem(){
              this.RouterItem(router)
            }
          })
        }.layoutWeight(1).alignListItem(ListItemAlign.Center)
        .width('100%')
      }
      .width('100%')
    .height('100%')
  }
  @Builder
  RouterItem(r: RouterInfo){
    Row(){
      Text(r.title).fontSize(20).fontColor(Color.White)
    }.width('90%').padding(12).backgroundColor(Color.Blue)
    .justifyContent(FlexAlign.Center)
    .onClick(() => {
      router.pushUrl(
        {
          url: r.url
        },
        router.RouterMode.Single,
        err => {
          if (err) {
           console.log(`路由失败,errCode:${err.code} errMsg:${err.message}`)
          }
        }
      )
    })

  }
}

 

  • 27
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值