当前位置: 首页 > article >正文

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

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

一、状态管理

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

1.@State

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

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

说明

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

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

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

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%')
  }
}
2.@Props和@Link

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

@Props@Link
同步类型单向同步双向同步
允许装饰的变量类型

1.@Props只支持string,number,boolean,enum类型

2.父组件对象类型,子组件是对象属性

3不可以是数组,any

1.父子类型一致string,number,boolean,enum,object,class,以及它们的数组

2.数组中的元素增删替换会引起更新

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

初始化方式不允许子组件初始化父组件传递,禁止子组件初始化

@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的双向同步


@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)
  }
}

4.@Observed和@ObiectLink

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

//任务进度卡片组件
@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.使用步骤
//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.示例

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)
          }
        }
      )
    })
  }
}

//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属性时,可以实现渐变过渡效果。

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函数来修改组件属性,实现属性变化时的渐变效果。

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属性来配置

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%' })
  }
}


http://www.kler.cn/news/358235.html

相关文章:

  • MySQL面试专题-索引
  • 缓存常见问题:缓存穿透、雪崩、击穿及解决方案分析
  • Java中的校验性判断
  • 远程连接服务器
  • Qt-系统网络HTTP客户端(66)
  • CAXA制造工程师2024软件下载
  • 一句话木马的多种变形方式
  • Java程序OutOfMemoryError分析与heap dump
  • SpringBoot高校学科竞赛平台:安全与维护指南
  • react函数组件和类组件
  • (五)若使用LQR控制小车倒立摆,该如何对小车和摆杆的动力学方程线性化?哪些变量是可以进行简化的,线性化后的状态空间方程应该怎么列写
  • CVE-2024-22120:Zabbix低权限SQL注入至RCE+权限绕过
  • Axure使用echarts详细教程
  • 优阅达携手 Theobald 亮相新加坡科技周,助力企业 SAP 数据集成与应用
  • 基于Python实现“科研通”自动签到
  • 点评项目-7-缓存击穿的两种解决方案、缓存工具类的编写
  • 计算机毕业设计选题推荐-动漫番剧推荐系统-Python项目实战
  • 看门狗(基于ESP-IDF)
  • JavaWeb 24.Vue3的简介和快速体验
  • QT实现校园导航