鸿蒙NEXT开发案例:抛硬币

zhongcx
发布于 2024-12-1 10:07
浏览
0收藏

鸿蒙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. 主页面组件:
    • 顶部有一个“抛硬币”的标题,下方是一个行布局,用于展示地鼠组件及正反两面出现的次数。
    • 地鼠组件被放置在一个圆形区域内,背景采用线性渐变色。
    • 点击地鼠时,会触发一系列动画效果,模拟硬币抛起再落下的过程。
    • 通过计算最终的角度,判断是正面还是反面朝上,并更新相应的计数。
    【完整代码】
// 定义地鼠组件
@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; // 显示反面
  }
}
  • 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.

分类
标签
收藏
回复
举报


回复
    相关推荐