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%' })
  }
}
相关推荐
zhanshuo11 小时前
在鸿蒙里优雅地处理网络错误:从 Demo 到实战案例
harmonyos
zhanshuo12 小时前
在鸿蒙中实现深色/浅色模式切换:从原理到可运行 Demo
harmonyos
whysqwhw17 小时前
鸿蒙分布式投屏
harmonyos
whysqwhw18 小时前
鸿蒙AVSession Kit
harmonyos
whysqwhw20 小时前
鸿蒙各种生命周期
harmonyos
whysqwhw21 小时前
鸿蒙音频编码
harmonyos
whysqwhw21 小时前
鸿蒙音频解码
harmonyos
whysqwhw21 小时前
鸿蒙视频解码
harmonyos
whysqwhw21 小时前
鸿蒙视频编码
harmonyos
ajassi200021 小时前
开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器
华为·开源·harmonyos