# 如何在全局自定义Builder中实现动画效果

#### 问题现象

在全局自定义Builder函数中，通过修改组件的属性，如何实现动画效果？问题代码示例参考如下：

```
@Builder
function bottomViewBuilder() {
  Column() {
    Image($r('app.media.start_branding_light_icon'))
      .width(150)
  }
  .padding({ bottom: AppStorage.get('bottomRectHeight') as number })
  .justifyContent(FlexAlign.End)
  .backgroundColor($r('app.color.background_color_level2'))
  .width(windowWidth)
  .height(windowHeight - adHeight)
  .animation({
    duration: 1000,
    curve: Curve.Linear,
    playMode: PlayMode.Normal
  })
  .onAppear(() => {
  })
}
```

以上代码中windowWidth、windowHeight、adHeight为全局变量，当全局变量修改时，无法触发动画效果。

#### 背景知识

*   实现属性动画：通过可动画属性改变引起UI上产生的连续视觉效果，即为属性动画。属性动画是最基础易懂的动画，ArkUI提供三种动画接口animateTo、animation和keyframeAnimateTo驱动组件属性按照动画曲线等动画参数进行连续的变化，产生属性动画。
*   Builder函数：该函数分为全局自定义构建函数和私有自定义构建函数两种形式，全局函数调用时，无法直接通过this指针调用父组件的状态变量，必须通过传参的方式调用@Component父组件内声明的状态变量。同时，如果@Builder传入的参数是两个或两个以上，不会触发动态渲染UI，也就不会触发动画渲染。

#### 解决方案

*   **方案一**：采用animateTo的方式实现全局自定义构建函数的动画效果。
    
    1.  由于Builder函数的参数限制，若需实现由多个参数触发的动画效果，建议将多个参数封装为可深度观测的类，通过@Observed/@ObjectLink、@ObservedV2/@Trace修饰类，实现深度观测，并传递至Builder内。
    2.  将传递的参数绑定可动画的属性，即可在属性修改时触发动画效果。
    3.  在animateTo内绑定动画的参数，并修改可动画的属性。
    
    ```
    @Builder
    function bottomViewBuilderOne(simple: AnimatesOne) {
      Column() {
        Image($r('app.media.startIcon'))
          .width(150);
      }
      .padding({ bottom: AppStorage.get('bottomRectHeight') as number })
      .justifyContent(FlexAlign.End)
      .width(simple.windowWidth)
      .height(simple.windowHeight - simple.adHeight)
      .onAppear(() => {
      });
    }
    
    // 动画属性只支持状态变量的修改，同时由于Builder的传参限制，建议封装为一个可深度观测的类
    class AnimatesOne {
      @Track windowWidth: number = 100;
      @Track windowHeight: number = 100;
      @Track adHeight: number = 0;
    }
    
    @Entry
    @Component
    struct OptionOne {
      @State simple: AnimatesOne = new AnimatesOne();
    
      build() {
        Column() {
          Text('开始动画')
            .onClick(() => {
              this.getUIContext()?.animateTo({
                duration: 2000,
                curve: Curve.Linear,
                iterations: -1,
                playMode: PlayMode.Normal,
                onFinish: () => {
                  console.info('play end');
                }
              }, () => {
                this.simple.windowWidth = 200;
                this.simple.adHeight = 50;
              });
            });
          bottomViewBuilderOne(this.simple);
        };
      }
    }
    ```
    
*   **方案二**：采用animation属性动画，实现方式与方案一类似，将方案一的步骤3的动画参数绑定在animation内。
    
    ```
    @Builder
    function bottomViewBuilderTwo(simple: AnimatesTwo) {
      Column() {
        Image($r('app.media.startIcon'))
          .width(150);
      }
      .padding({ bottom: AppStorage.get('bottomRectHeight') as number })
      .justifyContent(FlexAlign.End)
      .width(simple.windowWidth)
      .height(simple.windowHeight - simple.adHeight)
      .animation({
        duration: 3000,
        iterations: -1,
        curve: Curve.Linear,
        playMode: PlayMode.Normal
      })
      .onAppear(() => {
      });
    }
    
    class AnimatesTwo {
      @Track windowWidth: number = 100;
      @Track windowHeight: number = 100;
      @Track adHeight: number = 0;
    }
    
    @Entry
    @Component
    struct OptionTwo {
      @State simple: AnimatesTwo = new AnimatesTwo();
    
      build() {
        Column() {
          Text('开始动画')
            .onClick(() => {
              this.simple.windowWidth = 200;
              this.simple.adHeight = 50;
            });
          bottomViewBuilderTwo(this.simple);
        };
      }
    }
    ```
    
    效果预览：
    
    由于全局自定义函数的Builder的父容器Column组件没有设置宽高限制，导致Column组件自适应子组件大小，所以Text组件也跟随移动。