鸿蒙殊效教程07-九宫格荣幸抽奖
在移动应用中,抽奖功能是一种常见且受欢迎的交互方式,能够有效提升用户粘性。本教程将带领各人从零开始,逐步实现一个九宫格抽奖效果,得当HarmonyOS开辟的初学者阅读。
开辟情况预备
- DevEco Studio 5.0.3
- HarmonyOS Next API 15
最终效果预览
我们将实现一个经典的九宫格抽奖界面,包罗以下核心功能:
- 3×3网格结构展示奖品
- 点击中间按钮启动抽奖
- 高亮格子循环移动的动画效果
- 动态变速,模仿真实抽奖过程
- 预设中奖结果的展示
实现步调
步调一:创建基本结构和数据模子
起首,我们必要创建一个基础页面结构和定义数据模子。通过定义奖品的数据结构,为后续的九宫格结构做预备。
- // 定义奖品项的接口
- interface PrizeItem {
- id: number
- name: string
- icon: ResourceStr
- color: 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() {
- // 页面结构保持不变
- }
- }
复制代码 在这一步,我们添加了以下内容:
- 创建了一个包罗9个奖品的数组,每个奖品都有id、名称、图标和颜色属性
- 添加了三个状态变量:
- 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]
- ]
- })
- }
复制代码 在这一步,我们实现了以下内容:
- 创建了整体的页面结构,包括标题、结果表现区域和九宫格区域
- 利用 Grid 组件创建3×3的网格结构
- 利用 ForEach 遍历奖品数组,为每个奖品创建一个格子
- 根据索引判定,为中间位置创建"开始抽奖"按钮,其他位置表现奖品信息
- 为每个格子设置了符合的样式和背景色
步调四:实现高亮效果和点击变乱
接下来,我们要实现格子的高亮效果,并添加点击变乱处理。
- 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() {
- // 在下一步实现
- }
复制代码 在这一步,我们:
- 为中间的"开始抽奖"按钮添加了点击变乱处理方法startLottery()
- 实现了格子高亮效果:
- 当格子被选中时(index === this.currentIndex),背景色变为白色,文字颜色变为奖品颜色
- 添加了动画效果,使高亮切换更加平滑
- 预定义了startLottery()方法,暂时为空实现
步调五:实现抽奖动画逻辑
如今我们来实现抽奖动画的核心逻辑,包括循环高亮、速度厘革和结果控制。
- @Entry
- @Component
- struct LuckyDraw {
- // 前面的状态变量保持不变...
-
- // 添加动画控制相关变量
- private timer: number = 0
- private speed: number = 100
- private totalRounds: number = 30
- private currentRound: number = 0
- private targetIndex: number = 2 // 假设固定中奖"优惠券"
-
- // 开始抽奖
- startLottery() {
- if (this.isRunning) {
- return // 防止重复点击
- }
-
- this.isRunning = true
- this.result = '抽奖中...'
- this.currentRound = 0
- this.speed = 100
-
- // 启动动画
- this.runLottery()
- }
-
- // 运行抽奖动画
- runLottery() {
- if (this.timer) {
- clearTimeout(this.timer)
- }
-
- this.timer = setTimeout(() => {
- // 更新当前高亮的格子
- this.currentIndex = (this.currentIndex + 1) % 9
- if (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 = false
- this.result = `恭喜获得: ${this.prizes[this.targetIndex].name}`
- } else {
- // 继续动画
- this.runLottery()
- }
- }, this.speed)
- }
-
- // 组件销毁时清除定时器
- aboutToDisappear() {
- if (this.timer) {
- clearTimeout(this.timer)
- this.timer = 0
- }
- }
-
- // build方法保持不变...
- }
复制代码 在这一步,我们实现了抽奖动画的核心逻辑:
- 添加了动画控制相干变量:
- timer:用于存储定时器ID
- speed:控制动画速度
- totalRounds:总共旋转的轮数
- currentRound:当前已旋转的轮数
- targetIndex:预设的中奖索引
- 实现了startLottery()方法:
- 防止重复点击
- 初始化抽奖状态
- 调用runLottery()开始动画
- 实现了runLottery()方法:
- 利用setTimeout创建循环动画
- 更新高亮格子的索引,并跳过中间的开始按钮
- 根据进度增长延迟时间,模仿减速效果
- 根据条件判定是否竣事动画
- 递归调用自身形成动画循环
- 添加了aboutToDisappear()生命周期方法,确保在组件销毁时扫除定时器,避免内存走漏
完备代码
末了,我们对代码进行完善和优化,确保抽奖功能正常工作并提升用户体验。
完备的代码如下:
- interface PrizeItem {
- id: number
- name: string
- icon: ResourceStr
- color: 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 = 100
- private totalRounds: number = 30
- private currentRound: number = 0
- // 预设中奖索引(可以根据概率随机生成)
- private targetIndex: number = 2 // 假设固定中奖"优惠券"
- // 开始抽奖
- startLottery() {
- if (this.isRunning) {
- return
- }
- this.isRunning = true
- this.result = '抽奖中...'
- this.currentRound = 0
- this.speed = 100
- // 启动动画
- this.runLottery()
- }
- // 运行抽奖动画
- runLottery() {
- if (this.timer) {
- clearTimeout(this.timer)
- }
- this.timer = setTimeout(() => {
- // 更新当前高亮的格子
- this.currentIndex = (this.currentIndex + 1) % 9
- if (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 = false
- this.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'表示三行等高结构。
- columnsGap和rowsGap:设置列和行之间的间距。
- aspectRatio:设置宽高比,确保网格是正方形。
2. 动画实现原理
抽奖动画的核心是通过定时器和状态更新实现的:
- 循环高亮:通过setTimeout定时更新currentIndex状态,实现格子的循环高亮。
- 动态速度:随着循环轮数的增长,渐渐增长延迟时间(this.speed += 10),实现减速效果。
- 竣事条件:当满足两个条件时制止动画:
- 已完成设定的总轮数(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--;
- // 其他抽奖逻辑...
- }
复制代码 总结
本教程从零开始,一步步实现了九宫格抽奖效果,涵盖了以下关键内容:
- 数据结构定义和状态管理
- 网格结构和循环渲染
- 条件样式和动画效果
- 定时器控制和动态速度
- 生命周期管理和资源清理
希望这篇 HarmonyOS Next 教程对你有所资助,期待您的点赞、评论、收藏。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |