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

【引言】(完整代码在最后面)
本文将介绍如何在鸿蒙NEXT中创建一个自定义的“太极Loading”组件,为你的应用增添独特的视觉效果。
【环境准备】
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:mate60 pro
语言:ArkTS、ArkUI
【项目分析】
1. 组件结构
我们将创建一个名为 TaiChiLoadingProgress 的自定义组件,它将模拟太极图的旋转效果,作为加载动画展示给用户。组件的基本结构如下:
1 2 3 4 5 6 7 8 | @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 方法实现太极图的旋转效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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 方法设置太极图的旋转动画,可以自定义动画曲线以实现不同的动画效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | startAnim() { animateTo({ duration: 2000, iterations: -1, curve: this .animationCurve }, () => { this .angle = 360 * 2 }) } endAnim() { animateTo({ duration: 0 }, () => { this .angle = 0 }) } |
【完整代码】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | @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" ) } } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库