鸿蒙NEXT开发案例:抛硬币

 

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

本项目旨在实现一个简单的“抛硬币”功能,用户可以通过点击屏幕上的地鼠图标来模拟抛硬币的过程。应用会记录并显示硬币正面(地鼠面)和反面(数字100面)出现的次数。为了增强用户体验,我们还添加了动画效果,使抛硬币的过程更加生动有趣。

【2】环境准备

电脑系统:windows 10

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

工程版本:API 12

真机:mate60 pro

语言:ArkTS、ArkUI

【3】应用结构

应用主要由两个部分组成:地鼠组件(Hamster)和主页面组件(CoinTossPage)。

地鼠组件(Hamster)

地鼠组件是应用的核心视觉元素之一,负责展示地鼠的形象。该组件通过@Component装饰器定义,并接收一个属性cellWidth,用于控制组件的大小。

主页面组件(CoinTossPage)

主页面组件是整个应用的入口点,负责组织和管理各个UI元素。该组件同样通过@Component装饰器定义,并包含多个状态变量用于跟踪硬币的状态和动画进度。

【4】功能解析

1. 地鼠组件:

• 通过Stack布局组合多个图形元素,创建了一个地鼠的形象。

• 每个图形元素都设置了具体的尺寸、颜色、边框等样式,并通过margin属性调整位置。

2. 主页面组件:

• 顶部有一个“抛硬币”的标题,下方是一个行布局,用于展示地鼠组件及正反两面出现的次数。

• 地鼠组件被放置在一个圆形区域内,背景采用线性渐变色。

• 点击地鼠时,会触发一系列动画效果,模拟硬币抛起再落下的过程。

• 通过计算最终的角度,判断是正面还是反面朝上,并更新相应的计数。

【完整代码】

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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
// 定义地鼠组件
@Component
struct Hamster {
  @Prop cellWidth: number // 单元格宽度
 
  build() {
    Stack() { // 创建一个堆叠布局
      // 身体
      Text()
        .width(`${this.cellWidth / 2}lpx`)// 宽度为单元格宽度的一半
        .height(`${this.cellWidth / 3 * 2}lpx`)// 高度为单元格高度的2/3
        .backgroundColor("#b49579")// 背景颜色
        .borderRadius({ topLeft: '50%', topRight: '50%' })// 圆角
        .borderColor("#2a272d")// 边框颜色
        .borderWidth(1) // 边框宽度
      // 嘴巴
      Ellipse()
        .width(`${this.cellWidth / 4}lpx`)// 嘴巴的宽度
        .height(`${this.cellWidth / 5}lpx`)// 嘴巴的高度
        .fillOpacity(1)// 填充不透明度
        .fill("#e7bad7")// 填充颜色
        .stroke("#563e3f")// 边框颜色
        .strokeWidth(1)// 边框宽度
        .margin({ top: `${this.cellWidth / 6}lpx` }) // 上边距
      // 左眼睛
      Ellipse()
        .width(`${this.cellWidth / 9}lpx`)// 左眼睛的宽度
        .height(`${this.cellWidth / 6}lpx`)// 左眼睛的高度
        .fillOpacity(1)// 填充不透明度
        .fill("#313028")// 填充颜色
        .stroke("#2e2018")// 边框颜色
        .strokeWidth(1)// 边框宽度
        .margin({ bottom: `${this.cellWidth / 3}lpx`, right: `${this.cellWidth / 6}lpx` }) // 下边距和右边距
      // 右眼睛
      Ellipse()
        .width(`${this.cellWidth / 9}lpx`)// 右眼睛的宽度
        .height(`${this.cellWidth / 6}lpx`)// 右眼睛的高度
        .fillOpacity(1)// 填充不透明度
        .fill("#313028")// 填充颜色
        .stroke("#2e2018")// 边框颜色
        .strokeWidth(1)// 边框宽度
        .margin({ bottom: `${this.cellWidth / 3}lpx`, left: `${this.cellWidth / 6}lpx` }) // 下边距和左边距
      // 左眼瞳
      Ellipse()
        .width(`${this.cellWidth / 20}lpx`)// 左眼瞳的宽度
        .height(`${this.cellWidth / 15}lpx`)// 左眼瞳的高度
        .fillOpacity(1)// 填充不透明度
        .fill("#fefbfa")// 填充颜色
        .margin({ bottom: `${this.cellWidth / 2.5}lpx`, right: `${this.cellWidth / 6}lpx` }) // 下边距和右边距
      // 右眼瞳
      Ellipse()
        .width(`${this.cellWidth / 20}lpx`)// 右眼瞳的宽度
        .height(`${this.cellWidth / 15}lpx`)// 右眼瞳的高度
        .fillOpacity(1)// 填充不透明度
        .fill("#fefbfa")// 填充颜色
        .margin({ bottom: `${this.cellWidth / 2.5}lpx`, left: `${this.cellWidth / 6}lpx` }) // 下边距和左边距
    }.width(`${this.cellWidth}lpx`).height(`${this.cellWidth}lpx`) // 设置组件的宽度和高度
  }
}
 
// 定义页面组件
@Entry
@Component
struct CoinTossPage {
  @State cellWidth: number = 50 // 单元格宽度
  @State headsCount: number = 0 // 正面朝上的次数
  @State tailsCount: number = 0 // 反面朝上的次数
  @State rotationAngle: number = 0 // 旋转角度
  @State verticalOffset: number = 0 // 纵向位移
  @State isAnimRun: boolean = false // 动画是否正在执行
 
  build() {
    Column() {
      // 页面标题
      Text('抛硬币')
        .height(50)// 高度设置为50
        .width('100%')// 宽度设置为100%
        .textAlign(TextAlign.Center)// 文本居中对齐
        .fontColor("#fefefe")// 字体颜色
        .fontSize(20); // 字体大小
 
      // 显示地鼠和计数
      Row({ space: 20 }) {
        Stack() {
          Hamster({ cellWidth: this.cellWidth }) // 创建地鼠组件
        }
        .borderRadius('50%') // 设置圆角
        .width(`${this.cellWidth}lpx`) // 设置宽度
        .height(`${this.cellWidth}lpx`) // 设置高度
        .linearGradient({
          // 设置线性渐变背景
          direction: GradientDirection.LeftBottom,
          colors: [['#ebcf2f', 0.0], ['#fef888', 0.5], ['#ebcf2f', 1.0]]
        });
 
        // 显示反面朝上的次数
        Text(`${this.tailsCount}`)
          .fontSize(20)
          .fontColor("#fefefe");
 
        Stack() {
          // 显示100
          Text("100")
            .fontColor("#9f7606")
            .fontSize(`${this.cellWidth / 2}lpx`);
        }
        .borderRadius('50%') // 设置圆角
        .width(`${this.cellWidth}lpx`) // 设置宽度
        .height(`${this.cellWidth}lpx`) // 设置高度
        .linearGradient({
          // 设置线性渐变背景
          direction: GradientDirection.LeftBottom,
          colors: [['#ebcf2f', 0.0], ['#fef888', 0.5], ['#ebcf2f', 1.0]]
        });
 
        // 显示正面朝上的次数
        Text(`${this.headsCount}`)
          .fontSize(20)
          .fontColor("#fefefe");
 
      }.width('100%').justifyContent(FlexAlign.Center); // 设置宽度和内容居中对齐
 
      Stack() {
        Stack() {
          // 创建放大版地鼠组件
          Hamster({ cellWidth: this.cellWidth * 3 })
            .visibility(this.isHeadsFaceUp() ? Visibility.Visible : Visibility.Hidden); // 根据状态显示或隐藏
 
          // 显示100
          Text("100")
            .fontColor("#9f7606")// 字体颜色
            .fontSize(`${this.cellWidth / 2 * 3}lpx`)// 字体大小
            .visibility(!this.isHeadsFaceUp() ? Visibility.Visible : Visibility.Hidden)// 根据状态显示或隐藏
            .rotate({
              // 旋转180度
              x: 1,
              y: 0,
              z: 0,
              angle: 180
            });
        }
        .borderRadius('50%') // 设置圆角
        .width(`${this.cellWidth * 3}lpx`) // 设置宽度
        .height(`${this.cellWidth * 3}lpx`) // 设置高度
        .linearGradient({
          // 设置线性渐变背景
          direction: GradientDirection.LeftBottom,
          colors: [['#ebcf2f', 0.0], ['#fef888', 0.5], ['#ebcf2f', 1.0]]
        })
        .rotate({
          // 根据当前角度旋转
          x: 1,
          y: 0,
          z: 0,
          angle: this.rotationAngle
        })
        .translate({ x: 0, y: this.verticalOffset }) // 设置纵向位移
        .onClick(() => { // 点击事件处理
 
          if (this.isAnimRun) {
            return;
          }
          this.isAnimRun = true
 
          let maxAnimationSteps = 2 * (10 + Math.floor(Math.random() * 10)); // 计算最大动画次数
          let totalAnimationDuration = 2000; // 动画总时长
 
          // 第一次动画,向上抛出
          animateToImmediately({
            duration: totalAnimationDuration / 2, // 动画时长为总时长的一半
            onFinish: () => { // 动画完成后的回调
              // 第二次动画,向下落
              animateToImmediately({
                duration: totalAnimationDuration / 2,
                onFinish: () => {
                  this.rotationAngle = this.rotationAngle % 360; // 确保角度在0到360之间
                  // 判断当前显示的面
                  if (this.isHeadsFaceUp()) { // 如果是地鼠面
                    this.tailsCount++; // 反面朝上的次数加1
                  } else { // 如果是反面
                    this.headsCount++; // 正面朝上的次数加1
                  }
                  this.isAnimRun = false
                }
              }, () => {
                this.verticalOffset = 0; // 重置纵向位移
              });
            }
          }, () => {
            // 设置纵向位移,模拟抛硬币的效果
            this.verticalOffset = -100 * (1 + Math.floor(Math.random() * 5)); // 随机设置向上的位移
          });
 
          // 循环动画,增加旋转效果
          for (let i = 0; i < maxAnimationSteps; i++) {
            animateToImmediately({
              delay: i * totalAnimationDuration / maxAnimationSteps, // 设置每次动画的延迟
              duration: 100, // 每次动画的持续时间
              onFinish: () => {
                // 动画完成后的回调
              }
            }, () => {
              this.rotationAngle += 90; // 每次增加90度旋转
            });
          }
        });
 
      }.width('100%').layoutWeight(1).align(Alignment.Bottom).padding({ bottom: 80 }); // 设置组件的宽度、权重、对齐方式和底部内边距
    }
    .height('100%') // 设置整个页面的高度
    .width('100%') // 设置整个页面的宽度
    .backgroundColor("#0b0d0c"); // 设置背景颜色
  }
 
  // 判断当前是否显示地鼠面
  isHeadsFaceUp() {
    let normalizedAngle = this.rotationAngle % 360; // 规范化角度
    // 判断角度范围,确定是否显示地鼠面
    if (normalizedAngle >= 0 && normalizedAngle < 90 || normalizedAngle >= 270 && normalizedAngle <= 360) {
      return true; // 显示地鼠面
    }
    return false; // 显示反面
  }
}

  

posted @   zhongcx  阅读(490)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示