如何解决元素显隐时动画只执行一次的问题
===================

#### 问题现象

元素在显示隐藏时使用animateTo显式动画来控制显示的元素实现360°旋转效果，元素只在第一次显示时执行动画，后续再点击元素显示时动画不再执行。

问题代码示例参考如下：

@ComponentV2
@Entry
struct Index {
  @Local rotateAngle: number \= 0
  @Local isAnimating: boolean \= false _//_ _动画状态_

  _// 启动动画的方法_
  startAnimation() {
    this.isAnimating \= true
    this.getUIContext()?.animateTo({
      duration: 1000, _//_ _动画持续时间，单位毫秒_
      curve: Curve.Linear, _//_ _线性动画，确保匀速_
      iterations: -1, _// 无限循环_
      playMode: PlayMode.Normal, _// 正常播放模式，避免交替_
      expectedFrameRateRange: {
        min: 10,
        max: 120,
        expected: 60,
      }
    }, () \=\> {
      if (this.isAnimating) {
        this.rotateAngle \= 360 _//_ _旋转角度从0到360_
      }
    })
  }

  build() {
    Column() {
      if (!this.isAnimating) {
        Text('遮挡条')
          .width('100%')
          .height(40)
          .backgroundColor('#2b3d79')
      } else {
        Image($r('app.media.startIcon'))
          .width(20)
          .height(20)
          .syncLoad(true) _//_ _同步加载避免闪烁_
          .interpolation(ImageInterpolation.High) _// 高质量渲染_
          .rotate({
            x: 0,
            y: 0,
            z: 1,
            angle: this.rotateAngle
          })
          .onAppear(() \=\> {
            this.startAnimation()
          })
      }
      Text(this.isAnimating ? '展示遮挡条' : '隐藏遮挡条')
        .width('100%')
        .height(40)
        .backgroundColor('#0092ff')
        .onClick(() \=\> {
          this.isAnimating \= !this.isAnimating
        })
    }
    .width('100%')
    .height('100%')
  }
}

#### 背景知识

*   [组件内转场](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-transition-animation-component)：通过[transition属性](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-transition-animation-component#属性)配置转场参数，在组件插入和删除时显示过渡动效，以提升用户体验。
*   [visibility](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-universal-attributes-visibility#visibility)属性：可以用来控制元素的显示与隐藏，并结合显式动画来实现元素的显示与隐藏效果。
*   [onAppear](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-universal-events-show-hide#onappear)：组件挂载显示后触发此回调。

#### 问题定位

观察代码发现Image图片只有在其onAppear时会执行一次startAnimation动画方法，后续点击下方的元素不会触发其onAppear方法，所以动画只执行了一次。

#### 分析结论

onAppear方法只执行一次导致动画只执行了一次。

#### 修改建议

针对onAppear方法只执行一次导致动画只执行了一次问题，有以下两种解决方案：

*   **方案一**：通过visibility属性来控制元素的显示隐藏，这样Image每次显示时都会触发onAppear方法。
    
    @Entry
    @ComponentV2
    struct ShowAnimationDemo {
      @Local rotateAngle: number \= 0;
      @Local isAnimating: boolean \= false; _// 动画状态_
    
     _ // 启动动画的方法_
      startAnimation() {
        this.getUIContext()?.animateTo({
          duration: 1000, _// 动画持续时间，单位毫秒_
          curve: Curve.Linear, _// 线性动画，确保匀速_
          iterations: -1, _//_ _无限循环_
          playMode: PlayMode.Normal, _//_ _正常播放模式，避免交替_
          expectedFrameRateRange: {
            min: 10,
            max: 120,
            expected: 60,
          }
        }, () \=\> {
          this.rotateAngle \= 360;
        });
      };
    
      build() {
        Column() {
          if (!this.isAnimating) {
            Text('遮挡条')
              .width('100%')
              .height(40)
              .backgroundColor('#2b3d79')
          }
          Image($r('app.media.startIcon'))
            .width(20)
            .height(20)
            .id('image1')
            .syncLoad(true)
            .interpolation(ImageInterpolation.High)
            .visibility(this.isAnimating ? Visibility.Visible : Visibility.None)
            .rotate({
              x: 0,
              y: 0,
              z: 1,
              angle: this.rotateAngle
            })
            .onAppear(() \=\> {
              this.startAnimation();
            });
          Text(this.isAnimating ? '展示遮挡条' : '隐藏遮挡条')
            .width('100%')
            .height(40)
            .backgroundColor('#0092ff')
            .onClick(() \=\> {
              this.isAnimating \= !this.isAnimating;
            });
        }
        .width('100%')
        .height('100%')
      }
    }
    
*   **方案二**：使用组件内转场通过transition属性配置转场参数，在组件插入和删除时显示过渡动效，主要用于容器组件中的子组件插入和删除时，提升用户体验。
    
    @Entry
    @ComponentV2
    struct ShowAnimationDemo2  {
      @Local rotateAngle: number \= 0;
      @Local isAnimating: boolean \= false; _// 动画状态_
    
      build() {
        Column() {
          if (!this.isAnimating) {
            Text('遮挡条')
              .width('100%')
              .height(40)
              .backgroundColor('#2b3d79')
          } else {
            Image($r('app.media.startIcon'))
              .width(20)
              .height(20)
              .margin({top:2})
              .id('image1')
              .syncLoad(true)
              .interpolation(ImageInterpolation.High)
              .transition( _// 配置组件内转场参数_
                TransitionEffect.rotate({ z: -1, angle: 360 })
                  .animation({ duration: 1000, iterations: -1, curve: Curve.Linear })
              )
          }
          Text(this.isAnimating ? '展示遮挡条' : '隐藏遮挡条')
            .width('100%')
            .height(40)
            .backgroundColor('#0092ff')
            .onClick(() \=\> {
              this.isAnimating \= !this.isAnimating;
            });
        }
        .width('100%')
        .height('100%')
      }
    }