鸿蒙NEXT自定义组件:太极Loading

鸿蒙NEXT自定义组件:太极Loading_Stack

【引言】(完整代码在最后面)

本文将介绍如何在鸿蒙NEXT中创建一个自定义的“太极Loading”组件,为你的应用增添独特的视觉效果。

【环境准备】

电脑系统:windows 10

开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806

工程版本:API 12

真机:mate60 pro

语言:ArkTS、ArkUI

【项目分析】

1. 组件结构

我们将创建一个名为 TaiChiLoadingProgress 的自定义组件,它将模拟太极图的旋转效果,作为加载动画展示给用户。组件的基本结构如下:

@Component
struct TaiChiLoadingProgress {
  @Prop taiChiWidth: number = 400
  @Prop @Watch('animationCurveChanged') animationCurve: Curve = Curve.Linear
  @State angle: number = 0
  @State cellWidth: number = 0
  ...
}

2. 绘制太极图案

使用鸿蒙NEXT提供的UI组件,如 Rect 和 Circle,构建太极图的黑白两部分。关键在于利用 rotate 方法实现太极图的旋转效果。

build() {
  Stack() {
    Stack() {
      // 黑色半圆背景
      Stack() {
        Rect().width(`${this.cellWidth}px`).height(`${this.cellWidth / 2}px`).backgroundColor(Color.Black)
      }.width(`${this.cellWidth}px`).height(`${this.cellWidth}px`).rotate({ angle: -90 }).align(Alignment.Top)
      // 大黑球 上
      Stack() {
        Circle().width(`${this.cellWidth / 2}px`).height(`${this.cellWidth / 2}px`).fill(Color.Black)
        Circle().width(`${this.cellWidth / 8}px`).height(`${this.cellWidth / 8}px`).fill(Color.White)
      }.width(`${this.cellWidth}px`).height(`${this.cellWidth}px`).align(Alignment.Top)
      // 大白球 下
      Stack() {
        Circle().width(`${this.cellWidth / 2}px`).height(`${this.cellWidth / 2}px`).fill(Color.White)
        Circle().width(`${this.cellWidth / 8}px`).height(`${this.cellWidth / 8}px`).fill(Color.Black)
      }.width(`${this.cellWidth}px`).height(`${this.cellWidth}px`).align(Alignment.Bottom)
    }
    .width(`${this.cellWidth}px`)
    .height(`${this.cellWidth}px`)
    .borderWidth(1)
    .borderColor(Color.Black)
    .borderRadius('50%')
    .backgroundColor(Color.White)
    .clip(true)
    .rotate({
      angle: this.angle
    })
    .onVisibleAreaChange([0.0, 1.0], (isVisible: boolean, currentRatio: number) => {
      if (isVisible && currentRatio >= 1.0) {
        this.startAnim()
      }
      if (!isVisible && currentRatio <= 0.0) {
        this.endAnim()
      }
    })
  }
  .width(`${this.taiChiWidth}px`)
  .height(`${this.taiChiWidth}px`)
}

3. 动画实现

通过 animateTo 方法设置太极图的旋转动画,可以自定义动画曲线以实现不同的动画效果。

startAnim() {
  animateTo({
    duration: 2000,
    iterations: -1,
    curve: this.animationCurve
  }, () => {
    this.angle = 360 * 2
  })
}

endAnim() {
  animateTo({
    duration: 0
  }, () => {
    this.angle = 0
  })
}

【完整代码】

@Component
struct TaiChiLoadingProgress {
  @Prop taiChiWidth: number = 400
  @Prop @Watch('animationCurveChanged') animationCurve: Curve = Curve.Linear
  @State angle: number = 0
  @State cellWidth: number = 0

  animationCurveChanged() {
    this.endAnim()
    this.startAnim()
  }

  startAnim() {
    animateTo({
      duration: 2000,
      iterations: -1,
      curve: this.animationCurve
    }, () => {
      this.angle = 360 * 2
    })
  }

  endAnim() {
    animateTo({
      duration: 0
    }, () => {
      this.angle = 0
    })
  }

  aboutToAppear(): void {
    this.cellWidth = this.taiChiWidth / 2
  }

  build() {
    Stack() {
      Stack() {
        //黑色 半圆 背景
        Stack() {
          Rect().width(`${this.cellWidth}px`).height(`${this.cellWidth / 2}px`).backgroundColor(Color.Black)
        }.width(`${this.cellWidth}px`).height(`${this.cellWidth}px`).rotate({ angle: -90 }).align(Alignment.Top)

        //大黑球 上
        Stack() {
          Stack() {
            Circle().width(`${this.cellWidth / 2}px`).height(`${this.cellWidth / 2}px`).fill(Color.Black)
            Circle().width(`${this.cellWidth / 8}px`).height(`${this.cellWidth / 8}px`).fill(Color.White)
          }
        }.width(`${this.cellWidth}px`).height(`${this.cellWidth}px`).align(Alignment.Top)

        //大白球 下
        Stack() {
          Stack() {
            Circle().width(`${this.cellWidth / 2}px`).height(`${this.cellWidth / 2}px`).fill(Color.White)
            Circle().width(`${this.cellWidth / 8}px`).height(`${this.cellWidth / 8}px`).fill(Color.Black)
          }
        }.width(`${this.cellWidth}px`).height(`${this.cellWidth}px`).align(Alignment.Bottom)

      }
      .width(`${this.cellWidth}px`)
      .height(`${this.cellWidth}px`)
      .borderWidth(1)
      .borderColor(Color.Black)
      .borderRadius('50%')
      .backgroundColor(Color.White)
      .clip(true)
      .rotate({
        angle: this.angle
      })
      .onVisibleAreaChange([0.0, 1.0], (isVisible: boolean, currentRatio: number) => {
        console.info('Test Row isVisible:' + isVisible + ', currentRatio:' + currentRatio)
        if (isVisible && currentRatio >= 1.0) {
          console.info('Test Row is fully visible.')
          this.startAnim()
        }

        if (!isVisible && currentRatio <= 0.0) {
          console.info('Test Row is completely invisible.')
          this.endAnim()
        }
      })
    }
    .width(`${this.taiChiWidth}px`)
    .height(`${this.taiChiWidth}px`)
  }
}

@Entry
@Component
struct Page08 {
  @State loadingWidth: number = 150
  @State isShowLoading: boolean = true;
  @State animationCurve: Curve = Curve.Linear

  build() {
    Column({ space: 20 }) {

      Text('官方Loading组件')
      Column() {
        LoadingProgress().width(this.loadingWidth)
          .visibility(this.isShowLoading ? Visibility.Visible : Visibility.None)
      }.height(this.loadingWidth).width(this.loadingWidth)

      Text('自定义太极Loading组件')
      Column() {
        TaiChiLoadingProgress({ taiChiWidth: vp2px(this.loadingWidth), animationCurve: this.animationCurve })
          .visibility(this.isShowLoading ? Visibility.Visible : Visibility.Hidden)
      }.height(this.loadingWidth).width(this.loadingWidth)

      Row() {
        Flex({ wrap: FlexWrap.Wrap }) {
          Text('显示/隐藏')
            .textAlign(TextAlign.Center)
            .width('200lpx')
            .height('200lpx')
            .margin('10lpx')
            .backgroundColor(Color.Black)
            .borderRadius(5)
            .backgroundColor(Color.Orange)
            .fontColor(Color.White)
            .clickEffect({ level: ClickEffectLevel.LIGHT })
            .onClick(() => {
              this.isShowLoading = !this.isShowLoading
            })
          Text('Linear动画')
            .textAlign(TextAlign.Center)
            .width('200lpx')
            .height('200lpx')
            .margin('10lpx')
            .backgroundColor(Color.Black)
            .borderRadius(5)
            .backgroundColor(Color.Orange)
            .fontColor(Color.White)
            .clickEffect({ level: ClickEffectLevel.LIGHT })
            .onClick(() => {
              this.animationCurve = Curve.Linear
            })
          Text('FastOutLinearIn动画')
            .textAlign(TextAlign.Center)
            .width('200lpx')
            .height('200lpx')
            .margin('10lpx')
            .backgroundColor(Color.Black)
            .borderRadius(5)
            .backgroundColor(Color.Orange)
            .fontColor(Color.White)
            .clickEffect({ level: ClickEffectLevel.LIGHT })
            .onClick(() => {
              this.animationCurve = Curve.FastOutLinearIn
            })
          Text('EaseIn动画')
            .textAlign(TextAlign.Center)
            .width('200lpx')
            .height('200lpx')
            .margin('10lpx')
            .backgroundColor(Color.Black)
            .borderRadius(5)
            .backgroundColor(Color.Orange)
            .fontColor(Color.White)
            .clickEffect({ level: ClickEffectLevel.LIGHT })
            .onClick(() => {
              this.animationCurve = Curve.EaseIn
            })
          Text('EaseOut动画')
            .textAlign(TextAlign.Center)
            .width('200lpx')
            .height('200lpx')
            .margin('10lpx')
            .backgroundColor(Color.Black)
            .borderRadius(5)
            .backgroundColor(Color.Orange)
            .fontColor(Color.White)
            .clickEffect({ level: ClickEffectLevel.LIGHT })
            .onClick(() => {
              this.animationCurve = Curve.EaseOut
            })
          Text('EaseInOut动画')
            .textAlign(TextAlign.Center)
            .width('200lpx')
            .height('200lpx')
            .margin('10lpx')
            .backgroundColor(Color.Black)
            .borderRadius(5)
            .backgroundColor(Color.Orange)
            .fontColor(Color.White)
            .clickEffect({ level: ClickEffectLevel.LIGHT })
            .onClick(() => {
              this.animationCurve = Curve.EaseInOut
            })
        }.width('660lpx')
      }.width('100%').justifyContent(FlexAlign.Center)
    }
    .height('100%')
    .width('100%')
    .backgroundColor("#f9feff")
  }
}
上一篇:CAP与BASE分布式理论-BASE理论


下一篇:Redux Toolkit:简化Redux应用状态管理