鴻蒙NEXT自定義元件:太極Loading

zhongcx發表於2024-11-15

【引言】(完整程式碼在最後面)

本文將介紹如何在鴻蒙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")
  }
}

  

相關文章