在移动应用中,抽奖功能是一种常见且受欢迎的交互方式,能够有效提升用户粘性。
本教程将带领大家从零开始,逐步实现一个九宫格抽奖效果,适合HarmonyOS开发的初学者阅读。
下载代码仓库
我们将实现一个经典的九宫格抽奖界面,包含以下核心功能:
首先,我们需要创建一个基础页面结构和定义数据模型。通过定义奖品的数据结构,为后续的九宫格布局做准备。
// 定义奖品项的接口
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() {
// 页面结构保持不变
}
}
在这一步,我们添加了以下内容:
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
:用于存储定时器IDspeed
:控制动画速度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() // 颜色扩展到安全区域
}
}
Grid组件是实现九宫格布局的核心,它具有以下重要属性:
columnsTemplate
:定义网格的列模板。'1fr 1fr 1fr'
表示三列等宽布局。rowsTemplate
:定义网格的行模板。'1fr 1fr 1fr'
表示三行等高布局。columnsGap
和rowsGap
:设置列和行之间的间距。aspectRatio
:设置宽高比,确保网格是正方形。抽奖动画的核心是通过定时器和状态更新实现的:
setTimeout
定时更新currentIndex
状态,实现格子的循环高亮。this.speed += 10
),实现减速效果。this.currentRound >= this.totalRounds
)this.currentIndex === this.targetIndex
)格子的高亮效果是通过条件样式实现的:
.backgroundColor(index === this.currentIndex && index !== 4 ? Color.White : prize.color)
当格子被选中时(index === this.currentIndex
),背景色变为白色,文字颜色变为奖品颜色,产生对比鲜明的高亮效果。
在组件销毁时,我们需要清除定时器以避免内存泄漏:
aboutToDisappear() {
if (this.timer) {
clearTimeout(this.timer)
this.timer = 0
}
}
完成基本功能后,可以考虑以下优化方向:
目前中奖结果是固定的,可以实现一个随机算法,根据概率分配不同奖品:
// 根据概率生成中奖索引
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; // 默认返回第一个奖品
}
添加音效可以提升用户体验:
// 播放抽奖音效
playSound(type: 'start' | 'running' | 'end') {
// 根据不同阶段播放不同音效
}
在抽奖开始和结束时添加振动反馈:
// 导入振动模块
import { vibrator } from '@kit.SensorServiceKit';
// 触发振动
triggerVibration() {
vibrator.vibrate(50); // 振动50毫秒
}
添加抽奖次数限制和剩余次数显示:
@State remainingTimes: number = 3; // 剩余抽奖次数
startLottery() {
if (this.isRunning || this.remainingTimes <= 0) {
return;
}
this.remainingTimes--;
// 其他抽奖逻辑...
}
本教程从零开始,一步步实现了九宫格抽奖效果,涵盖了以下关键内容:
希望这篇 HarmonyOS Next 教程对你有所帮助,期待您的点赞、评论、收藏。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。