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

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

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

【环境准备】

电脑系统:windows 10

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

工程版本:API 12

真机:mate60 pro

语言:ArkTS、ArkUI

【项目分析】

  1. 组件结构

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

TypeScript 复制代码
@Component
struct TaiChiLoadingProgress {
  @Prop taiChiWidth: number = 400
  @Prop @Watch('animationCurveChanged') animationCurve: Curve = Curve.Linear
  @State angle: number = 0
  @State cellWidth: number = 0
  ...
}
  1. 绘制太极图案

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

TypeScript 复制代码
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`)
}
  1. 动画实现

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

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

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

【完整代码】

TypeScript 复制代码
@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")
  }
}
相关推荐
李游Leo1 小时前
HarmonyOS AbilityStage 实战:别把启动参数散落在每个页面里
harmonyos
李李李勃谦3 小时前
鸿蒙PCBI 报表工具:连接数据库与可视化报表生成
数据库·华为·交互·harmonyos
maaath3 小时前
【maaath】 Flutter for OpenHarmony 实战:电池优化应用开发指南
flutter·华为·harmonyos
aqi005 小时前
一文读懂 HarmonyOS 6.1 带来的十大重要升级
android·华为·harmonyos·鸿蒙·harmony
智能化咨询5 小时前
(105页PPT)华为智慧供应链ISC+IT蓝图规划设计方案ISC+战略愿景与蓝图设计ISC+能力框架与架构设计实施路径(附下载方式)
华为
李李李勃谦6 小时前
鸿蒙PC配色方案工具:取色、配色生成与 CSS 导出
前端·css·华为·harmonyos
SmartBrain7 小时前
《资治通鉴》20 条智慧赋能企业经营管理
华为·架构·创业创新
条tiao条7 小时前
从静态到动态:鸿蒙 ArkTS 列表组件与状态装饰器实战
华为·harmonyos
maaath8 小时前
【无标题】Flutter for OpenHarmony 的文具手账应用开发实践
flutter·华为·harmonyos
李李李勃谦8 小时前
鸿蒙PC打造电子书阅读器:支持 EPUB/PDF、书签同步、笔记管理
笔记·华为·pdf·harmonyos