34345 发表于 2025-2-7 03:30:17

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