HarmonyOS开发(状态管理,页面路由,动画)

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

一、状态管理

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

1.@State

状态(State):指驱动视图更新的数据,被装饰器标记的变量

视图(View):基于UI描述渲染得到用户界面

说明

@State装饰器标记的变量必须初始化,不能为空值。

@State支持Object,class,string,number,boolean,enum类型以及这些类型的数组。

嵌套类型以及数组中的对象属性无法触发视图更新。

TypeScript 复制代码
class Person{
  name:string
  age:number
  gf?:Person
  constructor(name:string,age:number,gf?:Person) {
    this.name=name;
    this.age=age;
    this.gf=gf;
  }
}
@Entry
@Component
struct Index {
  @State idx:number=1
  @State name:string='Jack'
  @State age:number=10
  @State p:Person=new Person('Tony',20)
  @State p1:Person=new Person('Tom',20,new Person('Lily',18))
  @State gfs:Person[]=[
    new Person('露西',18),
    new Person('安娜',19)
  ]
  build() {
    Column(){
      Text(`${this.name}${this.age}`)
        .fontSize(40)
        .onClick(()=>{
          this.age++
        })
      Text(`${this.p.name}${this.p.age}`)
        .fontSize(40)
        .onClick(()=>{
          this.p.age++
        })
      if (this.p1.gf)Text(`${this.p1.gf.name}${this.p1.gf.age}`)
        .fontSize(40)
        .onClick(()=>{
          //嵌套对象,对象内的属性发生变更无法触发视图变更
          if (this.p1.gf) this.p1.gf.age++
        })
      ForEach(this.gfs,(item:Person,index:number)=>{
        Row(){
          Text(`${item.name}:${item.age}`)
            .fontSize(40)
            .onClick(()=>{
              //数组中的对象属性无法触发视图更新
              item.age++
              //添加删除重新赋值才会触发视图更新
              this.gfs[index]=new Person(item.name,item.age)
            })
          Button('删除')
            .onClick(()=>{
              this.gfs.splice(index,1)
            })
        }
        .justifyContent(FlexAlign.SpaceBetween)

      })
      Button('添加女友')
        .onClick(()=>{
          this.gfs.push(new Person('女友'+this.idx++,18))
        })
    }
    .width('100%')
    .height('100%')
  }
}

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

|-----------|------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|
| | @Props | @Link |
| 同步类型 | 单向同步 | 双向同步 |
| 允许装饰的变量类型 | 1.@Props只支持string,number,boolean,enum类型 2.父组件对象类型,子组件是对象属性 3不可以是数组,any | 1.父子类型一致string,number,boolean,enum,object,class,以及它们的数组 2.数组中的元素增删替换会引起更新 3.嵌套类型以及数组中的对象属性无法触发视图更新 |
| 初始化方式 | 不允许子组件初始化 | 父组件传递,禁止子组件初始化 |

TypeScript 复制代码
@Component
struct TaskStatistics{
  @Prop totalTask:number
  @Prop finishTask:number
  build() {
    Row(){
      Text('任务进度:')
        .fontSize(30)
      Stack({ alignContent: Alignment.Center }) {
        Progress({ value: this.finishTask, total: this.totalTask, type: ProgressType.Ring })
          .width(100)
          .height(100)
        Text(`${this.finishTask}/${this.totalTask}`)
          .fontSize(30)
      }
    }
    .width('100%')
    .height(200)
    .borderRadius(10)
    .backgroundColor('#fff')
    .justifyContent(FlexAlign.SpaceAround)
  }
}

@Component
struct TaskList{
  @Link stat:Station
  @State tasks:Task[]=[]
  build() {
    Column(){
      Row(){
        Button('新增任务')
          .onClick(()=>{
            this.tasks.push(new Task())
            this.stat.totalTask=this.tasks.length
          })
          .width(160)
      }
      .margin({top:10})
      List({space:10}) {
        ForEach(this.tasks, (item: Task, index: number) => {
          ListItem(){
            Row() {
              Text(`${item.name}`)
                .fontSize(20)
              // .finishedStyle()
              Checkbox()
                .select(item.finish)
                .onChange((val) => {
                  item.finish = val;
                  this.stat.finishTask = this.tasks.filter((item) => {
                    return item.finish
                  }).length
                })
            }
            .margin({ top: 10 })
            .width('100%')
            .height(50)
            .borderRadius(10)
            .backgroundColor('#fff')
            .justifyContent(FlexAlign.SpaceBetween)
          }
          .swipeAction({end:this.DeleteButton(index)})
        })
      }
      .layoutWeight(1)
    }
  }
  @Builder DeleteButton(index:number) {
    Button() {
      Image($r('app.media.ic_public_delete_filled'))
        .width(40).height(40)
    }
    .width(40).height(40).type(ButtonType.Circle)
    .onClick(()=>{
      this.tasks.splice(index,1)
      this.stat.totalTask=this.tasks.length
      this.stat.finishTask = this.tasks.filter((item) => {
        return item.finish
      }).length
    })
  }
}

class Station{
  totalTask:number=0
  finishTask:number=0
}
class Task{
  static id:number=1
  name:string='任务'+Task.id++
  finish:boolean=false
}
@Extend(Text) function finishedStyle(){
  .decoration({type:TextDecorationType.LineThrough})
  .fontColor('#b1b2b1')
}
@Entry
@Component
struct Index {
  @State stat:Station=new Station()
  @State tasks:Task[]=[]
  build() {
    Column() {
      //任务进度卡片
      TaskStatistics({ totalTask: this.stat.totalTask, finishTask: this.stat.finishTask })
      // 任务列表
      TaskList({stat:$stat })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f6f6f6')
    .padding(20)
  }
}
3.@Provide和Consume

可以跨组件提供类似于@Props和@Link的双向同步

TypeScript 复制代码
@Component
struct TaskStatistics{
  @Consume stat:Station
  build() {
    Row(){
      Text('任务进度:')
        .fontSize(30)
      Stack({ alignContent: Alignment.Center }) {
        Progress({ value: this.stat.finishTask, total: this.stat.totalTask, type: ProgressType.Ring })
          .width(100)
          .height(100)
        Text(`${this.stat.finishTask}/${this.stat.totalTask}`)
          .fontSize(30)
      }
    }
    .width('100%')
    .height(200)
    .borderRadius(10)
    .backgroundColor('#fff')
    .justifyContent(FlexAlign.SpaceAround)
  }
}

@Component
struct TaskList{
  @Consume stat:Station
  @State tasks:Task[]=[]
  build() {
    Column(){
      Row(){
        Button('新增任务')
          .onClick(()=>{
            this.tasks.push(new Task())
            this.stat.totalTask=this.tasks.length
          })
          .width(160)
      }
      .margin({top:10})
      List({space:10}) {
        ForEach(this.tasks, (item: Task, index: number) => {
          ListItem(){
            Row() {
              Text(`${item.name}`)
                .fontSize(20)
              // .finishedStyle()
              Checkbox()
                .select(item.finish)
                .onChange((val) => {
                  item.finish = val;
                  this.stat.finishTask = this.tasks.filter((item) => {
                    return item.finish
                  }).length
                })
            }
            .margin({ top: 10 })
            .width('100%')
            .height(50)
            .borderRadius(10)
            .backgroundColor('#fff')
            .justifyContent(FlexAlign.SpaceBetween)
          }
          .swipeAction({end:this.DeleteButton(index)})
        })
      }
      .layoutWeight(1)
    }
  }
  @Builder DeleteButton(index:number) {
    Button() {
      Image($r('app.media.ic_public_delete_filled'))
        .width(40).height(50)
    }
    .width(40).height(50).type(ButtonType.Circle)
    .onClick(()=>{
      this.tasks.splice(index,1)
      this.stat.totalTask=this.tasks.length
      this.stat.finishTask = this.tasks.filter((item) => {
        return item.finish
      }).length
    })
  }
}

class Station{
  totalTask:number=0
  finishTask:number=0
}
class Task{
  static id:number=1
  name:string='任务'+Task.id++
  finish:boolean=false
}
@Extend(Text) function finishedStyle(){
  .decoration({type:TextDecorationType.LineThrough})
  .fontColor('#b1b2b1')
}
@Entry
@Component
struct Index {
  @Provide stat:Station=new Station()
  @State tasks:Task[]=[]
  build() {
    Column() {
      //任务进度卡片
      TaskStatistics()
      // 任务列表
      TaskList()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f6f6f6')
    .padding(20)
  }
}

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

TypeScript 复制代码
//任务进度卡片组件
@Component
struct TaskStatistics{
  @Consume stat:Station
  build() {
    Row(){
      Text('任务进度:')
        .fontSize(30)
      Stack({ alignContent: Alignment.Center }) {
        Progress({ value: this.stat.finishTask, total: this.stat.totalTask, type: ProgressType.Ring })
          .width(100)
          .height(100)
        Text(`${this.stat.finishTask}/${this.stat.totalTask}`)
          .fontSize(30)
      }
    }
    .width('100%')
    .height(200)
    .borderRadius(10)
    .backgroundColor('#fff')
    .justifyContent(FlexAlign.SpaceAround)
  }
}
// 任务列表项组件
@Component
struct TaskItem{
  @ObjectLink item:Task
  onTaskChange:()=>void=()=>{}
  build() {
    Row() {
      if(this.item.finish) {
        Text(`${this.item.name}`)
          .fontSize(20)
          .finishedStyle()
      }else{
        Text(`${this.item.name}`)
          .fontSize(20)
      }
      Checkbox()
        .select(this.item.finish)
        .onChange((val) => {
          this.item.finish = val;
          this.onTaskChange()
        })
    }
    .margin({ top: 10 })
    .width('100%')
    .height(50)
    .borderRadius(10)
    .backgroundColor('#fff')
    .justifyContent(FlexAlign.SpaceBetween)
  }
}
// 任务列表组件
@Component
struct TaskList{
  @Consume stat:Station
  @State tasks:Task[]=[]
  handleTaskChange(){
    this.stat.finishTask = this.tasks.filter((item) => {
      return item.finish
    }).length
    this.stat.totalTask=this.tasks.length
  }
  build() {
    Column(){
      Row(){
        Button('新增任务')
          .onClick(()=>{
            this.tasks.push(new Task())
            this.handleTaskChange()
          })
          .width(160)
      }
      .margin({top:10})
      List({space:10}) {
        ForEach(this.tasks, (item: Task, index: number) => {
          ListItem(){
            TaskItem({ item: item, onTaskChange: this.handleTaskChange.bind(this) })
          }
          .swipeAction({end:this.DeleteButton(index)})
        })
      }
      .layoutWeight(1)
    }
  }
  @Builder DeleteButton(index:number) {
    Button() {
      Image($r('app.media.ic_public_delete_filled'))
        .width(40).height(50)
    }
    .width(40).height(50).type(ButtonType.Circle)
    .onClick(()=>{
      this.tasks.splice(index,1)
      this.handleTaskChange()
    })
  }
}

class Station{
  totalTask:number=0
  finishTask:number=0
}
@Observed
class Task{
  static id:number=1
  name:string='任务'+Task.id++
  finish:boolean=false
}
@Extend(Text) function finishedStyle(){
  .decoration({type:TextDecorationType.LineThrough})
  .fontColor('#b1b2b1')
}
@Entry
@Component
struct Index {
  @Provide stat:Station=new Station()
  build() {
    Column() {
      //任务进度卡片
      TaskStatistics()
      // 任务列表
      TaskList()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f6f6f6')
    .padding(20)
  }
}

二、页面路由

页面路由是指在应用程序中实现不同页面之间的跳转和传递函数。

页面栈的最大容量上限为32 个页面,使用,router.clear()方法可以清空页面栈,释放内存。

1.Router有两种页面跳转模式

router.pushUrl()目标页不会替换当前页,而是压入栈,因此可以用router.back()返回当前页

router.replaceUrl目标页替换当前页,当前页面会被销毁并释放资源,无法返回当前页

2.Router有两种页面实例模式

standard标准实例模式,每一次跳转都会新建一个目标页压入栈顶,默认就是这种模式

single单实例模式,如果目标页已经在栈中,则离栈顶最近的url页面会被移动到栈顶并重新加载

3.使用步骤
TypeScript 复制代码
//1.导入harmonyos提供的Router模块
import router from '@ohos.router';

//利用router实现跳转、返回操作
router.pushUrl(
    {
        url:'pages/ImagePage',
        params:{id:1}
    },
    router.RouterMode.Single,
    err=>{
        if(err){
            console.log(err)
        }
    }
}

//获取传递过来的参数
params:any=router.getParams()
//返回上一页
router.back()
//返回到指定页,并携带参数
router.back(
    {
        url:'pages/Index',
        params:{id:10}
    }
)
4.示例
TypeScript 复制代码
import router from '@ohos.router';
class Router{
  url:string
  title:string
  constructor(url:string,title:string) {
    this.url=url;
    this.title=title;
  }
}
@Entry
@Component
struct Index {
  @State message:string='页面列表'
  private routers:Router[]=[
    new Router('pages/ComIndex','图片页面'),
    new Router('pages/CustomIndex','自定义组件页面'),
    new Router('pages/ForEachIndex','渲染页面'),
    new Router('pages/ProvideIndex','任务列表页面')
  ]
  build() {
    Column() {
      //标题
      Text('页面列表')
        .fontSize(35)
      //列表
      List(){
        ForEach(this.routers,(router:Router,index)=>{
          ListItem(){
            this.RouterItem(router,index+1)
          }
        })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f6f6f6')
    .padding(20)
  }
  @Builder RouterItem(r:Router,i:number){
    Row(){
      Text(i+'.')
        .fontSize(30)
      Blank()
      Text(r.title)
        .fontSize(30)
    }
    .width('100%')
    .height(60)
    .padding(10)
    .margin({bottom:20})
    .backgroundColor('#36d')
    .borderRadius(25)
    .onClick(()=>{
      router.pushUrl(
        {
        url:r.url,
        params:{idx:i}
        },
        router.RouterMode.Single,
        err=>{
          if(err){
            console.log(err.message)
          }
        }
      )
    })
  }
}
TypeScript 复制代码
//Header组件可以返回上一页
import router from '@ohos.router';
interface Params {
  idx?: number; // idx 是可选的,可能存在也可能不存在
}
@Component
export struct Header {
  private title:ResourceStr|string=''
  @State params: Params = router.getParams() as Params; // 强制类型转换为 Params
  build(){
    Row(){
      Image($r('app.media.left'))
        .width(30)
        .onClick(()=>{
          router.showAlertBeforeBackPage({message:'确定要返回吗?'})
          router.back()
        })
      if(this.params &&this.title) {
        Text(this.params.idx +'.'+this.title)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
      }
      Blank()
      Image($r('app.media.reset'))
        .width(24)
        .onClick(()=>{})
    }
    .width('100%')
    .height(30)
    .backgroundColor('#f6f6f6')
  }
}

三、动画

属性动画、显示动画、组件转场动画

1.属性动画

属性动画是通过设置组件的animation属性来给组件添加动画,当前组件的width,height,opacity,backgroundColor,scale,rotate,translate属性时,可以实现渐变过渡效果。

TypeScript 复制代码
import router from '@ohos.router';
@Entry
@Component
struct FishIndex {
  @State fishX:number=200
  @State fishY:number=100
  @State isBegin:boolean=false
  @State angle:number=0
  @State src:Resource=$r('app.media.fish')
  build() {
    Row() {
      Button('返回')
        .position({ x: 0, y: 0 })
        .onClick((event: ClickEvent) => {
          router.back()
      })
      if(!this.isBegin){
        Button('开始游戏')
          .onClick(()=>{
            this.isBegin=true;
          })
      }else{
        Image(this.src)
          .width(50)
          .height(50)
          .position({x:this.fishX,y:this.fishY})
          .rotate({angle:this.angle,centerX:'50%',centerY:'50%'})
          .animation({duration:500})
        Row(){
          Button('←')
            .backgroundColor('#20101010')
            .onClick((event: ClickEvent) => {
              this.fishX-=20
              this.src=$r('app.media.fish')
            })
          Column(){
            Button('↑')
              .backgroundColor('#20101010')
              .onClick((event: ClickEvent) => {
                this.fishY-=20
              })
            Button("↓")
              .backgroundColor('#20101010')
              .onClick((event: ClickEvent) => {
                this.fishY+=20
              })
          }
          Button('→')
            .backgroundColor('#20101010')
            .onClick((event: ClickEvent) => {
              this.src=$r('app.media.fish_rev')
              this.fishX+=20
            })
        }
      }
    }
    .width('100%')
    .height('100%')
    .backgroundImage($r('app.media.bg'))
    .backgroundImageSize({ width: '100%', height: '100%' })
  }
}
2.显示动画

显示动画是通过全局animateTo函数来修改组件属性,实现属性变化时的渐变效果。

TypeScript 复制代码
import router from '@ohos.router';
@Entry
@Component
struct FishIndex {
  @State fishX:number=200
  @State fishY:number=100
  @State isBegin:boolean=false
  @State angle:number=0
  @State src:Resource=$r('app.media.fish')
  build() {
    Row() {
      Button('返回')
        .position({ x: 0, y: 0 })
        .onClick((event: ClickEvent) => {
          router.back()
      })
      if(!this.isBegin){
        Button('开始游戏')
          .onClick(()=>{
            this.isBegin=true;
          })
      }else{
        Image(this.src)
          .width(50)
          .height(50)
          .position({x:this.fishX,y:this.fishY})
          .rotate({angle:this.angle,centerX:'50%',centerY:'50%'})
        Row(){
          Button('←')
            .backgroundColor('#20101010')
            .onClick((event: ClickEvent) => {
              animateTo({duration:500},()=>{
                this.fishX-=20
                this.src=$r('app.media.fish')
              })
            })
          Column(){
            Button('↑')
              .backgroundColor('#20101010')
              .onClick((event: ClickEvent) => {
                animateTo({duration:500},()=>{
                  this.fishY-=20
                })
              })
            Button("↓")
              .backgroundColor('#20101010')
              .onClick((event: ClickEvent) => {
                animateTo({duration:500},()=>{
                  this.fishY+=20
                })
              })
          }
          Button('→')
            .backgroundColor('#20101010')
            .onClick((event: ClickEvent) => {
              animateTo({duration:500},()=>{
                this.src=$r('app.media.fish_rev')
                this.fishX+=20
              })
            })
        }
      }
    }
    .width('100%')
    .height('100%')
    .backgroundImage($r('app.media.bg'))
    .backgroundImageSize({ width: '100%', height: '100%' })
  }
}
3.组件转场动画

组件的转场动画是在组件插入或移除时的过渡动画,通过组件的transition属性来配置

TypeScript 复制代码
import router from '@ohos.router';
@Entry
@Component
struct FishIndex {
  @State fishX:number=200
  @State fishY:number=100
  @State isBegin:boolean=false
  @State angle:number=0
  @State src:Resource=$r('app.media.fish')
  build() {
    Row() {
      Button('返回')
        .position({ x: 0, y: 0 })
        .onClick((event: ClickEvent) => {
          router.back()
      })
      if(!this.isBegin){
        Button('开始游戏')
          .onClick(()=>{
            animateTo({duration:500},()=>{
              this.isBegin=true;
            })
          })
      }else{
        Image(this.src)
          .width(50)
          .height(50)
          .position({x:this.fishX,y:this.fishY})
          .rotate({angle:this.angle,centerX:'50%',centerY:'50%'})
          .transition({
            type:TransitionType.Insert,
            opacity:0,
            translate:{x:-250}
          })
      }
      Row(){
        Button('←')
          .backgroundColor('#20101010')
          .onClick((event: ClickEvent) => {
            animateTo({duration:500},()=>{
              this.fishX-=20
              this.src=$r('app.media.fish')
            })
          })
        Column(){
          Button('↑')
            .backgroundColor('#20101010')
            .onClick((event: ClickEvent) => {
              animateTo({duration:500},()=>{
                this.fishY-=20
              })
            })
          Button("↓")
            .backgroundColor('#20101010')
            .onClick((event: ClickEvent) => {
              animateTo({duration:500},()=>{
                this.fishY+=20
              })
            })
        }
        Button('→')
          .backgroundColor('#20101010')
          .onClick((event: ClickEvent) => {
            animateTo({duration:500},()=>{
              this.src=$r('app.media.fish_rev')
              this.fishX+=20
            })
          })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundImage($r('app.media.bg'))
    .backgroundImageSize({ width: '100%', height: '100%' })
  }
}
相关推荐
ChinaDragonDreamer2 小时前
HarmonyOS:自定义组件冻结功能
开发语言·harmonyos·鸿蒙
雪芽蓝域zzs5 小时前
uni-app 运行HarmonyOS项目
uni-app·harmonyos
Bert.King6 小时前
<HarmonyOS第一课>给应用添加通知和提醒的习题
华为·harmonyos
觉醒法师19 小时前
HarmonyOS开发 - 本地持久化之实现LocalStorage支持多实例
前端·javascript·华为·typescript·harmonyos
东林知识库1 天前
2024年10月HarmonyOS应用开发者基础认证全新题库
学习·华为·harmonyos
ChinaDragonDreamer1 天前
HarmonyOS:@Watch装饰器:状态变量更改通知
开发语言·harmonyos·鸿蒙
Lei活在当下1 天前
【初探鸿蒙01】鸿蒙生态用开发白皮书V3.0解读
harmonyos
SameX1 天前
实现多子类型输入法:如何在 HarmonyOS中加载不同的输入模式
harmonyos
SuperHeroWu71 天前
【HarmonyOS】判断应用是否已安装
华为·微信·harmonyos·qq·微博·应用是否安装·canopenlink