HarmonyOS鸿蒙Next中Monitor中使用animateTo动画失效?

HarmonyOS鸿蒙Next中Monitor中使用animateTo动画失效?

@Entry
@ComponentV2
struct pageOne {
  @Local isPlay:boolean = false
  @Local fontSize:number = 16

  [@Monitor](/user/Monitor)('isPlay')
  onPlayChange(i:IMonitor){
    if(i.value()?.now){
      this.getUIContext().animateTo({duration:500},() =>{
        this.fontSize = 50
      })
    }else {
      this.getUIContext().animateTo({duration:500},() =>{
        this.fontSize = 20
      })
    }
  }

  build() {
    Column({space:20}){
      Text('hello')
        .fontSize(this.fontSize)
      Button('button').onClick(() =>{
        this.isPlay = !this.isPlay
        /*if(this.isPlay){
          this.getUIContext().animateTo({duration:500},() =>{
            this.fontSize = 50
          })
        }else {
          this.getUIContext().animateTo({duration:500},() =>{
            this.fontSize = 20
          })
        }*/
      })
    }.width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

如上在@Monitor中使用animateTo ,点击按钮瞬间改变没有动画。

cke_39084.png

文档里面也没有搜到@Monitor中使用animateTo的限制条件!


更多关于HarmonyOS鸿蒙Next中Monitor中使用animateTo动画失效?的实战教程也可以访问 https://www.itying.com/category-93-b0.html

7 回复

楼主可以在外面套一层定时器:在 [@Monitor](/user/Monitor) 中不要直接 animateTo,而是包一层 UI 异步执行 或 定时器,让动画运行在合适的渲染帧上下文里:

@Entry
@ComponentV2
struct MonitorTestPage {
  @Local isPlay:boolean = false
  @Local fontSize:number = 16

  [@Monitor](/user/Monitor)('isPlay')
  onPlayChange(i:IMonitor){
    if(i.value()?.now){
      setTimeout(() => {
        this.getUIContext().animateTo({duration:500},() =>{
          this.fontSize = 50
        })

      },0)
    }else {
      setTimeout(() => {
        this.getUIContext().animateTo({duration:500},() =>{
          this.fontSize = 20
        })

      },0)
    }
  }

  build() {
    Column({space:20}){
      Text('hello')
        .fontSize(this.fontSize)
      Button('button').onClick(() =>{
        this.isPlay = !this.isPlay
 /*       if(this.isPlay){
          this.getUIContext().animateTo({duration:500},() =>{
            this.fontSize = 50
          })
        }else {
          this.getUIContext().animateTo({duration:500},() =>{
            this.fontSize = 20
          })
        }*/
      })
    }.width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

这样这个动画就会生效的了,

原理:

  1. ArkUI 渲染机制

ArkUI 的 UI 更新和动画是跑在 UI 渲染管道(主线程的 UIFrame)里。

@Monitor 装饰器的回调是属性变化的同步监听,它执行时 可能还在数据更新阶段,还没进入 UI 刷新周期。

如果这时调用 animateTo,动画请求没法正确挂到 UI 渲染循环上 → 动画被忽略。

  1. setTimeout(…, 0) 的作用

setTimeout(…, 0) 会把回调放到 事件循环队列,等当前同步任务(数据更新、@Monitor 执行等)完成后再执行。

这样 animateTo 就不是在 属性变化监听的执行上下文里,而是在下一次事件循环里 → UI 渲染管道已经准备好接受动画请求。

所以动画能够正常触发。

更多关于HarmonyOS鸿蒙Next中Monitor中使用animateTo动画失效?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


【背景知识】

  • 自定义组件内的build还未执行,内部组件还未创建,动画时机过早,动画属性没有初值无法对组件产生动画。
  • 某些场景下,在状态管理V2中使用animateTo动画,会产生异常效果,具体可参考:在状态管理V2中使用animateTo动画效果异常

【解决方案】

在状态管理V2中,当使用@Param@Monitor修饰的状态变量发生变化时,系统会首先执行@Monitor绑定的方法。由于自定义组件内的build方法还未执行,内部组件还未创建,动画时机过早,导致动画属性没有初值,无法对组件产生动画效果。为了解决该问题,可以为动画添加setTimeout延时,确保内部组件创建完成后再执行动画。

代码示例如下:

// StateV2Page.ets
import { StateV2Loading } from '../views/StateV2Loading';

@Entry
@ComponentV2
struct StateV2Page {
  @Local message: string = '点击触发Loading';
  @Local isLoading: boolean = false;
  [@Param](/user/Param) animationDuration: number = 800;
  @Local loadingRotate: number = 360;

  build() {
    RelativeContainer() {
      Row() {
        Text(this.message)
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.isLoading = true
          })
        StateV2Loading({ isLoading: this.isLoading })
      }.justifyContent(FlexAlign.Center)
      .width('100%')
      .height('100%')
      .id("row")
      .alignRules({
        center: { anchor: '__container__', align: VerticalAlign.Center },
        middle: { anchor: '__container__', align: HorizontalAlign.Center }
      })
    }
    .height('100%')
    .width('100%')
  }
}
// StateV2Loading.ets
@ComponentV2
export struct StateV2Loading {
  [@Param](/user/Param) isLoading: boolean = false;
  [@Param](/user/Param) animationDuration: number = 800;
  @Local loadingRotate: number = 0;
  [@Monitor](/user/Monitor)('isLoading')
  startAnim(){
    // 在setTimeout内就可执行动画
    setTimeout(() => {
      animateTo({
        duration: this.animationDuration,
        curve: Curve.Linear,
        iterations: -1
      }, () => {
        this.loadingRotate = 360;
      });
    }, 10)
  }

  build() {
    Row() {
      Image($r('app.media.startIcon'))
        .width(16)
        .height(16)
        .rotate({ angle: this.loadingRotate })
        .animation({
          duration: this.animationDuration,
          curve: Curve.Linear,
          iterations: -1
        })
    }
  }
}

@Monitor属性监听器的回调执行时,可能处于非主线程或UI上下文未明确的状态,而animateTo动画必须依赖正确的UI执行上下文。此时动画闭包无法正确捕获组件渲染状态。@Monitor会立即响应状态变化,而animateTo内部通过异步队列执行动画。当isPlay状态快速切换时,动画可能被后续状态变更覆盖。

修改方案:

1/ 将动画逻辑移动到按钮点击事件中,确保动画与状态变更同步:

Button('button').onClick(() => {
  this.isPlay = !this.isPlay;
  const context = this.getUIContext();
  context.animateTo({ duration: 500 }, () => {
    this.fontSize = this.isPlay ? 50 : 20; 
  });
})

2/ 如果你必须使用@Monitor,需添加动画锁防止冲突:

@Local animating: boolean = false;

[@Monitor](/user/Monitor)('isPlay')

onPlayChange(i: IMonitor) {
  if (this.animating) return;
  this.animating = true;
  const targetSize = i.value()?.now ? 50 : 20;
  this.getUIContext().animateTo({ duration: 500 }, () => {
    this.fontSize = targetSize;
    this.animating = false;
  });
}

我换成了V1装饰器 发现是有动画效果的 , 试了好几种方法 发现都没有动画 ,你去提个工单 看看把 上面那个参考也不行 。

@Monitor装饰器会在@Local状态变量修改后立即触发回调,此时组件可能尚未完成重新渲染,导致动画上下文环境未准备好

建议使用事件触发动画

Button('button').onClick(() => {
  this.isPlay = !this.isPlay
  const targetSize = this.isPlay ? 50 : 20
  this.getUIContext().animateTo({ duration:500 }, () => {
    this.fontSize = targetSize
  })
})

完整代码

@Entry
@ComponentV2
struct pageOne {
  @Local isPlay:boolean = false
  @Local fontSize:number = 16

  @Monitor('isPlay')
  onPlayChange(i:IMonitor){
    if(i.value()?.now){
      this.getUIContext().animateTo({duration:500},() =>{
        this.fontSize = 50
      })
    }else {
      this.getUIContext().animateTo({duration:500},() =>{
        this.fontSize = 20
      })
    }
  }

  build() {
    Column({space:20}){
      Text('hello')
        .fontSize(this.fontSize)
      Button('button').onClick(() => {
        this.isPlay = !this.isPlay
        const targetSize = this.isPlay ? 50 : 20
        this.getUIContext().animateTo({ duration:500 }, () => {
          this.fontSize = targetSize
        })
      })
    }.width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

在HarmonyOS Next中,animateTo动画在Monitor中失效可能是由于UI线程阻塞或状态未正确触发。请检查是否在UI线程执行动画,并确认状态变量是否通过@State@Prop装饰器声明。确保动画代码未嵌套在非响应式逻辑中。

在HarmonyOS Next中,@Monitor装饰器主要用于监听状态变化并触发回调,但直接在@Monitor回调中使用animateTo可能导致动画失效,因为@Monitor的回调执行时机可能与UI渲染线程不同步,导致动画无法正确触发。

建议将动画逻辑移至按钮的onClick事件中(如代码中注释部分所示),确保动画在UI上下文中同步执行。@Monitor更适合处理状态变化后的数据逻辑,而非直接驱动UI动画。

若仍需在@Monitor中触发动画,可尝试通过setTimeout或异步任务延迟动画调用,但需注意可能引入的时序问题。推荐优先使用事件回调(如onClick)处理动画。

回到顶部