鸿蒙特效教程07-九宫格幸运抽奖

news/2025/3/20 23:35:04/文章来源:https://www.cnblogs.com/Megasu/p/18784205

鸿蒙特效教程07-九宫格幸运抽奖

在移动应用中,抽奖功能是一种常见且受欢迎的交互方式,能够有效提升用户粘性。本教程将带领大家从零开始,逐步实现一个九宫格抽奖效果,适合HarmonyOS开发的初学者阅读。

最终效果预览

我们将实现一个经典的九宫格抽奖界面,包含以下核心功能:

  • 3×3网格布局展示奖品
  • 点击中间按钮启动抽奖
  • 高亮格子循环移动的动画效果
  • 动态变速,模拟真实抽奖过程
  • 预设中奖结果的展示

image

实现步骤

步骤一:创建基本结构和数据模型

首先,我们需要创建一个基础页面结构和定义数据模型。通过定义奖品的数据结构,为后续的九宫格布局做准备。

// 定义奖品项的接口
interface PrizeItem {id: numbername: stringicon: ResourceStrcolor: string
}@Entry
@Component
struct LuckyDraw {// 基本页面结构build() {Column() {Text('幸运抽奖').fontSize(24).fontColor(Color.White)}.width('100%').height('100%').backgroundColor('#121212')}
}

在这一步,我们定义了PrizeItem接口来规范奖品的数据结构,并创建了一个基本的页面结构,只包含一个标题。

步骤二:创建奖品数据和状态管理

接下来,我们添加具体的奖品数据,并定义抽奖功能所需的状态变量。

@Entry
@Component
struct LuckyDraw {// 定义奖品数组@State prizes: PrizeItem[] = [{ id: 1, name: '谢谢参与', icon: $r('app.media.startIcon'), color: '#FF9500' },{ id: 2, name: '10积分', icon: $r('app.media.startIcon'), color: '#34C759' },{ id: 3, name: '优惠券', icon: $r('app.media.startIcon'), color: '#007AFF' },{ id: 8, name: '1元红包', icon: $r('app.media.startIcon'), color: '#FF3B30' },{ id: 0, name: '开始\n抽奖', icon: $r('app.media.startIcon'), color: '#FF2D55' },{ id: 4, name: '5元红包', icon: $r('app.media.startIcon'), color: '#5856D6' },{ id: 7, name: '免单券', icon: $r('app.media.startIcon'), color: '#E73C39' },{ id: 6, name: '50积分', icon: $r('app.media.startIcon'), color: '#38B0DE' },{ id: 5, name: '会员卡', icon: $r('app.media.startIcon'), color: '#39A5DC' },]// 当前高亮的奖品索引@State currentIndex: number = -1// 是否正在抽奖@State isRunning: boolean = false// 中奖结果@State result: string = '点击开始抽奖'build() {// 页面结构保持不变}
}

在这一步,我们添加了以下内容:

  1. 创建了一个包含9个奖品的数组,每个奖品都有id、名称、图标和颜色属性
  2. 添加了三个状态变量:
    • currentIndex:跟踪当前高亮的奖品索引
    • isRunning:标记抽奖是否正在进行
    • result:记录并显示抽奖结果

步骤三:实现九宫格布局

现在我们来实现九宫格的基本布局,使用Grid组件和ForEach循环遍历奖品数组。

build() {Column({ space: 30 }) {// 标题Text('幸运抽奖').fontSize(24).fontWeight(FontWeight.Bold).fontColor(Color.White)// 结果显示区域Column() {Text(this.result).fontSize(20).fontColor(Color.White)}.width('90%').padding(15).backgroundColor('#0DFFFFFF').borderRadius(16)// 九宫格抽奖区域Grid() {ForEach(this.prizes, (prize: PrizeItem, index) => {GridItem() {Column() {if (index === 4) {// 中间的开始按钮Button({ type: ButtonType.Capsule }) {Text(prize.name).fontSize(18).fontWeight(FontWeight.Bold).textAlign(TextAlign.Center).fontColor(Color.White)}.width('90%').height('90%').backgroundColor(prize.color)} else {// 普通奖品格子Image(prize.icon).width(40).height(40)Text(prize.name).fontSize(14).fontColor(Color.White).margin({ top: 8 }).textAlign(TextAlign.Center)}}.width('100%').height('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).backgroundColor(prize.color).borderRadius(12).padding(10)}})}.columnsTemplate('1fr 1fr 1fr').rowsTemplate('1fr 1fr 1fr').columnsGap(10).rowsGap(10).width('90%').aspectRatio(1).backgroundColor('#0DFFFFFF').borderRadius(16).padding(10)}.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor(Color.Black).linearGradient({angle: 135,colors: [['#121212', 0],['#242424', 1]]})
}

在这一步,我们实现了以下内容:

  1. 创建了整体的页面布局,包括标题、结果显示区域和九宫格区域
  2. 使用 Grid 组件创建3×3的网格布局
  3. 使用 ForEach 遍历奖品数组,为每个奖品创建一个格子
  4. 根据索引判断,为中间位置创建"开始抽奖"按钮,其他位置显示奖品信息
  5. 为每个格子设置了合适的样式和背景色

步骤四:实现高亮效果和点击事件

接下来,我们要实现格子的高亮效果,并添加点击事件处理。

build() {Column({ space: 30 }) {// 前面的代码保持不变...// 九宫格抽奖区域Grid() {ForEach(this.prizes, (prize: PrizeItem, index) => {GridItem() {Column() {if (index === 4) {// 中间的开始按钮Button({ type: ButtonType.Capsule }) {Text(prize.name).fontSize(18).fontWeight(FontWeight.Bold).textAlign(TextAlign.Center).fontColor(Color.White)}.width('90%').height('90%').backgroundColor(prize.color).onClick(() => this.startLottery()) // 添加点击事件} else {// 普通奖品格子Image(prize.icon).width(40).height(40)Text(prize.name).fontSize(14).fontColor(index === this.currentIndex ? prize.color : Color.White) // 高亮时修改文字颜色.margin({ top: 8 }).textAlign(TextAlign.Center)}}.width('100%').height('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).backgroundColor(index === this.currentIndex && index !== 4 ? Color.White : prize.color) // 高亮时切换背景色.borderRadius(12).padding(10).animation({ // 添加动画效果duration: 200,curve: Curve.EaseInOut})}})}// Grid的其他属性保持不变...}// Column的属性保持不变...
}// 添加开始抽奖的空方法
startLottery() {// 在下一步实现
}

在这一步,我们:

  1. 为中间的"开始抽奖"按钮添加了点击事件处理方法startLottery()
  2. 实现了格子高亮效果:
    • 当格子被选中时(index === this.currentIndex),背景色变为白色,文字颜色变为奖品颜色
    • 添加了动画效果,使高亮切换更加平滑
  3. 预定义了startLottery()方法,暂时为空实现

步骤五:实现抽奖动画逻辑

现在我们来实现抽奖动画的核心逻辑,包括循环高亮、速度变化和结果控制。

@Entry
@Component
struct LuckyDraw {// 前面的状态变量保持不变...// 添加动画控制相关变量private timer: number = 0private speed: number = 100private totalRounds: number = 30private currentRound: number = 0private targetIndex: number = 2 // 假设固定中奖"优惠券"// 开始抽奖startLottery() {if (this.isRunning) {return // 防止重复点击}this.isRunning = truethis.result = '抽奖中...'this.currentRound = 0this.speed = 100// 启动动画this.runLottery()}// 运行抽奖动画runLottery() {if (this.timer) {clearTimeout(this.timer)}this.timer = setTimeout(() => {// 更新当前高亮的格子this.currentIndex = (this.currentIndex + 1) % 9if (this.currentIndex === 4) { // 跳过中间的"开始抽奖"按钮this.currentIndex = 5}this.currentRound++// 增加速度变化,模拟减速效果if (this.currentRound > this.totalRounds * 0.7) {this.speed += 10 // 大幅减速} else if (this.currentRound > this.totalRounds * 0.5) {this.speed += 5 // 小幅减速}// 结束条件判断if (this.currentRound >= this.totalRounds && this.currentIndex === this.targetIndex) {// 抽奖结束this.isRunning = falsethis.result = `恭喜获得: ${this.prizes[this.targetIndex].name}`} else {// 继续动画this.runLottery()}}, this.speed)}// 组件销毁时清除定时器aboutToDisappear() {if (this.timer) {clearTimeout(this.timer)this.timer = 0}}// build方法保持不变...
}

在这一步,我们实现了抽奖动画的核心逻辑:

  1. 添加了动画控制相关变量:

    • timer:用于存储定时器ID
    • speed:控制动画速度
    • totalRounds:总共旋转的轮数
    • currentRound:当前已旋转的轮数
    • targetIndex:预设的中奖索引
  2. 实现了startLottery()方法:

    • 防止重复点击
    • 初始化抽奖状态
    • 调用runLottery()开始动画
  3. 实现了runLottery()方法:

    • 使用setTimeout创建循环动画
    • 更新高亮格子的索引,并跳过中间的开始按钮
    • 根据进度增加延迟时间,模拟减速效果
    • 根据条件判断是否结束动画
    • 递归调用自身形成动画循环
  4. 添加了aboutToDisappear()生命周期方法,确保在组件销毁时清除定时器,避免内存泄漏

完整代码

最后,我们对代码进行完善和优化,确保抽奖功能正常工作并提升用户体验。

完整的代码如下:

interface PrizeItem {id: numbername: stringicon: ResourceStrcolor: string
}@Entry
@Component
struct LuckyDraw {// 定义奖品数组@State prizes: PrizeItem[] = [{id: 1,name: '谢谢参与',icon: $r('app.media.startIcon'),color: '#FF9500'},{id: 2,name: '10积分',icon: $r('app.media.startIcon'),color: '#34C759'},{id: 3,name: '优惠券',icon: $r('app.media.startIcon'),color: '#007AFF'},{id: 8,name: '1元红包',icon: $r('app.media.startIcon'),color: '#FF3B30'},{id: 0,name: '开始\n抽奖',icon: $r('app.media.startIcon'),color: '#FF2D55'},{id: 4,name: '5元红包',icon: $r('app.media.startIcon'),color: '#5856D6'},{id: 7,name: '免单券',icon: $r('app.media.startIcon'),color: '#E73C39'},{id: 6,name: '50积分',icon: $r('app.media.startIcon'),color: '#38B0DE'},{id: 5,name: '会员卡',icon: $r('app.media.startIcon'),color: '#39A5DC'},]// 当前高亮的奖品索引@State currentIndex: number = -1// 是否正在抽奖@State isRunning: boolean = false// 中奖结果@State result: string = '点击下方按钮开始抽奖'// 动画定时器private timer: number = 0// 动画速度控制private speed: number = 100private totalRounds: number = 30private currentRound: number = 0// 预设中奖索引(可以根据概率随机生成)private targetIndex: number = 2 // 假设固定中奖"优惠券"// 开始抽奖startLottery() {if (this.isRunning) {return}this.isRunning = truethis.result = '抽奖中...'this.currentRound = 0this.speed = 100// 启动动画this.runLottery()}// 运行抽奖动画runLottery() {if (this.timer) {clearTimeout(this.timer)}this.timer = setTimeout(() => {// 更新当前高亮的格子this.currentIndex = (this.currentIndex + 1) % 9if (this.currentIndex === 4) { // 跳过中间的"开始抽奖"按钮this.currentIndex = 5}this.currentRound++// 增加速度变化,模拟减速效果if (this.currentRound > this.totalRounds * 0.7) {this.speed += 10} else if (this.currentRound > this.totalRounds * 0.5) {this.speed += 5}// 结束条件判断if (this.currentRound >= this.totalRounds && this.currentIndex === this.targetIndex) {// 抽奖结束this.isRunning = falsethis.result = `恭喜获得: ${this.prizes[this.targetIndex].name}`} else {// 继续动画this.runLottery()}}, this.speed)}// 组件销毁时清除定时器aboutToDisappear() {if (this.timer) {clearTimeout(this.timer)this.timer = 0}}build() {Column({ space: 30 }) {// 标题Text('幸运抽奖').fontSize(24).fontWeight(FontWeight.Bold).fontColor(Color.White)// 结果显示Column() {Text(this.result).fontSize(20).fontColor(Color.White)}.width('90%').padding(15).backgroundColor('#0DFFFFFF').borderRadius(16)// 九宫格抽奖区域Grid() {ForEach(this.prizes, (prize: PrizeItem, index) => {GridItem() {Column() {if (index === 4) {// 中间的开始按钮Button({ type: ButtonType.Capsule }) {Text(prize.name).fontSize(18).fontWeight(FontWeight.Bold).textAlign(TextAlign.Center).fontColor(Color.White)}.width('90%').height('90%').backgroundColor(prize.color).onClick(() => this.startLottery())} else {// 普通奖品格子Image(prize.icon).width(40).height(40)Text(prize.name).fontSize(14).fontColor(index === this.currentIndex && index !== 4 ? prize.color : Color.White).margin({ top: 8 }).textAlign(TextAlign.Center)}}.width('100%').height('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).backgroundColor(index === this.currentIndex && index !== 4 ? Color.White : prize.color).borderRadius(12).padding(10).animation({duration: 200,curve: Curve.EaseInOut})}})}.columnsTemplate('1fr 1fr 1fr').rowsTemplate('1fr 1fr 1fr').columnsGap(10).rowsGap(10).width('90%').aspectRatio(1).backgroundColor('#0DFFFFFF').borderRadius(16).padding(10)}.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor(Color.Black).linearGradient({angle: 135,colors: [['#121212', 0],['#242424', 1]]}).expandSafeArea() // 颜色扩展到安全区域}
}

核心概念解析

1. Grid组件

Grid组件是实现九宫格布局的核心,它具有以下重要属性:

  • columnsTemplate:定义网格的列模板。'1fr 1fr 1fr'表示三列等宽布局。
  • rowsTemplate:定义网格的行模板。'1fr 1fr 1fr'表示三行等高布局。
  • columnsGaprowsGap:设置列和行之间的间距。
  • aspectRatio:设置宽高比,确保网格是正方形。

2. 动画实现原理

抽奖动画的核心是通过定时器和状态更新实现的:

  1. 循环高亮:通过setTimeout定时更新currentIndex状态,实现格子的循环高亮。
  2. 动态速度:随着循环轮数的增加,逐渐增加延迟时间(this.speed += 10),实现减速效果。
  3. 结束条件:当满足两个条件时停止动画:
    • 已完成设定的总轮数(this.currentRound >= this.totalRounds
    • 当前高亮的格子是目标奖品(this.currentIndex === this.targetIndex

3. 高亮效果

格子的高亮效果是通过条件样式实现的:

.backgroundColor(index === this.currentIndex && index !== 4 ? Color.White : prize.color)

当格子被选中时(index === this.currentIndex),背景色变为白色,文字颜色变为奖品颜色,产生对比鲜明的高亮效果。

4. 资源清理

在组件销毁时,我们需要清除定时器以避免内存泄漏:

aboutToDisappear() {if (this.timer) {clearTimeout(this.timer)this.timer = 0}
}

进阶优化思路

完成基本功能后,可以考虑以下优化方向:

1. 随机中奖结果

目前中奖结果是固定的,可以实现一个随机算法,根据概率分配不同奖品:

// 根据概率生成中奖索引
generatePrizeIndex() {// 定义各奖品的概率权重const weights = [50, 10, 5, 3, 0, 2, 1, 8, 20]; // 数字越大概率越高const totalWeight = weights.reduce((a, b) => a + b, 0);// 生成随机数const random = Math.random() * totalWeight;// 根据权重决定中奖索引let currentWeight = 0;for (let i = 0; i < weights.length; i++) {if (i === 4) continue; // 跳过中间的"开始抽奖"按钮currentWeight += weights[i];if (random < currentWeight) {return i;}}return 0; // 默认返回第一个奖品
}

2. 抽奖音效

添加音效可以提升用户体验:

// 播放抽奖音效
playSound(type: 'start' | 'running' | 'end') {// 根据不同阶段播放不同音效
}

3. 振动反馈

在抽奖开始和结束时添加振动反馈:

// 导入振动模块
import { vibrator } from '@kit.SensorServiceKit';// 触发振动
triggerVibration() {vibrator.vibrate(50); // 振动50毫秒
}

4. 抽奖次数限制

添加抽奖次数限制和剩余次数显示:

@State remainingTimes: number = 3; // 剩余抽奖次数startLottery() {if (this.isRunning || this.remainingTimes <= 0) {return;}this.remainingTimes--;// 其他抽奖逻辑...
}

总结

本教程从零开始,一步步实现了九宫格抽奖效果,涵盖了以下关键内容:

  1. 数据结构定义和状态管理
  2. 网格布局和循环渲染
  3. 条件样式和动画效果
  4. 定时器控制和动态速度
  5. 生命周期管理和资源清理

希望这篇 HarmonyOS Next 教程对你有所帮助,期待您的点赞、评论、收藏。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/902273.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ollama系列05:Ollama API 使用指南

本文是Ollama系列教程的第5篇,在前面的4篇内容中,给大家分享了如何再本地通过Ollama运行DeepSeek等大模型,演示了chatbox、CherryStudio等UI界面中集成Ollama的服务,并介绍了如何通过cherryStudio构建私有知识库。 在今天的分享中,我将分享如何通过API来调用ollama服务,通…

前端HTML+CSS+JS速成笔记

HTML 超文本标记语言。 单标签与双标签的区别 单标签用于没有内容的元素,双标签用于有内容的元素。 HTML文件结构 告诉浏览器这还是一个 Html 文件: <!DOCTYPE html>Html文件的范围: <html>...</html>Html 文件的头: <head>...</head>实际显…

12. ADC

一、ADC简介生活中接触到的大多数信息是醉着时间连续变化的物理量,如声音、温度、压力等。表达这些信息的电信号,称为 模拟信号(Analog Signal)。为了方便存储、处理,在计算机系统中,都是数字 0 和 1 信号,将模拟信号(连续信号)转换为数字信号(离散信号)的器件就叫模…

【刷题笔记】力扣 40. 组合总和 II——回溯算法中的去重

40. 组合总和 II 中等 给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意:解集不能包含重复的组合。 示例 1: 输入: candidates = [10,1,2,7,6,1,…

Spring AI 搭建AI原生应用 [clone]

作者 | 文心智能体平台导读 本文以快速开发一个 AI 原生应用为目的,介绍了 Spring AI 的包括对话模型、提示词模板、Function Calling、结构化输出、图片生成、向量化、向量数据库等全部核心功能,并介绍了检索增强生成的技术。依赖 Spring AI 提供的功能,我们可以轻松开发出…

mybatis逆向工程插件配置(mybatis-generator-maven-plugin)

MyBatis逆向工程是一种自动化工具,可以将数据库表结构转换为MyBatis的Mapper XML文件和相应的Java接口和对应的实体类。 1.生成maven项目 2.pom.xml文件中导入逆向工程插件相关配置<!--mybatis逆向工程--><build><plugins><!--其中的一个插件,逆向工程插…

Day01-Java项目学习

Day01 后端环境搭建 lombok插件 通过lombok插件。@Data 可以使用@Data、@Getter、@Setter等注解的形式,来对一个Java Bean(具有Getter,Setter方法的对象)实现快速的初始化。 @Slf4j 可以以注解的形式,自动化日志变量,通过添加@Slf4j(simple logging Facade for Java)生成…

20241105 实验一 《Python程序设计》

课程:《Python程序设计》 班级: 2411 姓名: 王梓墨 学号:20241105 实验教师:王志强 实验日期:2025年3月12日 必修/选修: 公选课 一.实验内容 1.熟悉Python开发环境; 2.练习Python运行、调试技能;(编写书中的程序,并进行调试分析) 3.编写程序,练习变量和类型、字…

英语四级备考第二天

第二天 今天是开始英语备考的第二天,当迈出第二步的时候,就意味着正走在通过考试的路上。到时当你以425分毋庸置疑地通过考试时,过去的90天都不曾虚度。 单词 今天新学的单词加上昨天应复习的单词,在50~60个之间。阅读 今天的阅读还是用扇贝单词推荐的包含学习的单词的文章…

投资日记_道氏理论技术分析

主要用于我自己参考,我感觉我做事情的时候容易上头,忘掉很多事情。技术分析有很多方法,但是我个人相信并实践的还是以道氏理论为根本的方法。方法千千万万只有适合自己价值观,习惯,情绪,性格的方法才是好的方法。 趋势 趋势是技术分析的根本,要是连当前趋势都看不懂,最…

asp.net core webapi 完整Swagger配置

在当前项目下新建Utility文件夹,Utility文件夹下面在创建SwaggerExt文件夹,文档结果如下 CustomSwaggerExt.cs文件如下using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models;namespace xxxxxxxxxx {/// <summary>/// 扩展Swagger/// </summary>pub…