鸿蒙殊效教程07-九宫格荣幸抽奖

打印 上一主题 下一主题

主题 1773|帖子 1773|积分 5319

鸿蒙殊效教程07-九宫格荣幸抽奖

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



  • DevEco Studio 5.0.3
  • HarmonyOS Next API 15
最终效果预览

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


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

实现步调

步调一:创建基本结构和数据模子

起首,我们必要创建一个基础页面结构和定义数据模子。通过定义奖品的数据结构,为后续的九宫格结构做预备。
  1. // 定义奖品项的接口
  2. interface PrizeItem {
  3.   id: number
  4.   name: string
  5.   icon: ResourceStr
  6.   color: string
  7. }
  8. @Entry
  9. @Component
  10. struct LuckyDraw {
  11.   // 基本页面结构
  12.   build() {
  13.     Column() {
  14.       Text('幸运抽奖')
  15.         .fontSize(24)
  16.         .fontColor(Color.White)
  17.     }
  18.     .width('100%')
  19.     .height('100%')
  20.     .backgroundColor('#121212')
  21.   }
  22. }
复制代码
在这一步,我们定义了PrizeItem接口来规范奖品的数据结构,并创建了一个基本的页面结构,只包罗一个标题。
步调二:创建奖品数据和状态管理

接下来,我们添加详细的奖品数据,并定义抽奖功能所需的状态变量。
  1. @Entry
  2. @Component
  3. struct LuckyDraw {
  4.   // 定义奖品数组
  5.   @State prizes: PrizeItem[] = [
  6.     { id: 1, name: '谢谢参与', icon: $r('app.media.startIcon'), color: '#FF9500' },
  7.     { id: 2, name: '10积分', icon: $r('app.media.startIcon'), color: '#34C759' },
  8.     { id: 3, name: '优惠券', icon: $r('app.media.startIcon'), color: '#007AFF' },
  9.     { id: 8, name: '1元红包', icon: $r('app.media.startIcon'), color: '#FF3B30' },
  10.     { id: 0, name: '开始\n抽奖', icon: $r('app.media.startIcon'), color: '#FF2D55' },
  11.     { id: 4, name: '5元红包', icon: $r('app.media.startIcon'), color: '#5856D6' },
  12.     { id: 7, name: '免单券', icon: $r('app.media.startIcon'), color: '#E73C39' },
  13.     { id: 6, name: '50积分', icon: $r('app.media.startIcon'), color: '#38B0DE' },
  14.     { id: 5, name: '会员卡', icon: $r('app.media.startIcon'), color: '#39A5DC' },
  15.   ]
  16.   
  17.   // 当前高亮的奖品索引
  18.   @State currentIndex: number = -1
  19.   
  20.   // 是否正在抽奖
  21.   @State isRunning: boolean = false
  22.   
  23.   // 中奖结果
  24.   @State result: string = '点击开始抽奖'
  25.   
  26.   build() {
  27.     // 页面结构保持不变
  28.   }
  29. }
复制代码
在这一步,我们添加了以下内容:

  • 创建了一个包罗9个奖品的数组,每个奖品都有id、名称、图标和颜色属性
  • 添加了三个状态变量:

    • currentIndex:跟踪当前高亮的奖品索引
    • isRunning:标记抽奖是否正在进行
    • result:记录并表现抽奖结果

步调三:实现九宫格结构

如今我们来实现九宫格的基本结构,利用Grid组件和ForEach循环遍历奖品数组。
  1. build() {
  2.   Column({ space: 30 }) {
  3.     // 标题
  4.     Text('幸运抽奖')
  5.       .fontSize(24)
  6.       .fontWeight(FontWeight.Bold)
  7.       .fontColor(Color.White)
  8.    
  9.     // 结果显示区域
  10.     Column() {
  11.       Text(this.result)
  12.         .fontSize(20)
  13.         .fontColor(Color.White)
  14.     }
  15.     .width('90%')
  16.     .padding(15)
  17.     .backgroundColor('#0DFFFFFF')
  18.     .borderRadius(16)
  19.    
  20.     // 九宫格抽奖区域
  21.     Grid() {
  22.       ForEach(this.prizes, (prize: PrizeItem, index) => {
  23.         GridItem() {
  24.           Column() {
  25.             if (index === 4) {
  26.               // 中间的开始按钮
  27.               Button({ type: ButtonType.Capsule }) {
  28.                 Text(prize.name)
  29.                   .fontSize(18)
  30.                   .fontWeight(FontWeight.Bold)
  31.                   .textAlign(TextAlign.Center)
  32.                   .fontColor(Color.White)
  33.               }
  34.               .width('90%')
  35.               .height('90%')
  36.               .backgroundColor(prize.color)
  37.             } else {
  38.               // 普通奖品格子
  39.               Image(prize.icon)
  40.                 .width(40)
  41.                 .height(40)
  42.               Text(prize.name)
  43.                 .fontSize(14)
  44.                 .fontColor(Color.White)
  45.                 .margin({ top: 8 })
  46.                 .textAlign(TextAlign.Center)
  47.             }
  48.           }
  49.           .width('100%')
  50.           .height('100%')
  51.           .justifyContent(FlexAlign.Center)
  52.           .alignItems(HorizontalAlign.Center)
  53.           .backgroundColor(prize.color)
  54.           .borderRadius(12)
  55.           .padding(10)
  56.         }
  57.       })
  58.     }
  59.     .columnsTemplate('1fr 1fr 1fr')
  60.     .rowsTemplate('1fr 1fr 1fr')
  61.     .columnsGap(10)
  62.     .rowsGap(10)
  63.     .width('90%')
  64.     .aspectRatio(1)
  65.     .backgroundColor('#0DFFFFFF')
  66.     .borderRadius(16)
  67.     .padding(10)
  68.   }
  69.   .width('100%')
  70.   .height('100%')
  71.   .justifyContent(FlexAlign.Center)
  72.   .backgroundColor(Color.Black)
  73.   .linearGradient({
  74.     angle: 135,
  75.     colors: [
  76.       ['#121212', 0],
  77.       ['#242424', 1]
  78.     ]
  79.   })
  80. }
复制代码
在这一步,我们实现了以下内容:

  • 创建了整体的页面结构,包括标题、结果表现区域和九宫格区域
  • 利用 Grid 组件创建3×3的网格结构
  • 利用 ForEach 遍历奖品数组,为每个奖品创建一个格子
  • 根据索引判定,为中间位置创建"开始抽奖"按钮,其他位置表现奖品信息
  • 为每个格子设置了符合的样式和背景色
步调四:实现高亮效果和点击变乱

接下来,我们要实现格子的高亮效果,并添加点击变乱处理。
  1. build() {
  2.   Column({ space: 30 }) {
  3.     // 前面的代码保持不变...
  4.    
  5.     // 九宫格抽奖区域
  6.     Grid() {
  7.       ForEach(this.prizes, (prize: PrizeItem, index) => {
  8.         GridItem() {
  9.           Column() {
  10.             if (index === 4) {
  11.               // 中间的开始按钮
  12.               Button({ type: ButtonType.Capsule }) {
  13.                 Text(prize.name)
  14.                   .fontSize(18)
  15.                   .fontWeight(FontWeight.Bold)
  16.                   .textAlign(TextAlign.Center)
  17.                   .fontColor(Color.White)
  18.               }
  19.               .width('90%')
  20.               .height('90%')
  21.               .backgroundColor(prize.color)
  22.               .onClick(() => this.startLottery()) // 添加点击事件
  23.             } else {
  24.               // 普通奖品格子
  25.               Image(prize.icon)
  26.                 .width(40)
  27.                 .height(40)
  28.               Text(prize.name)
  29.                 .fontSize(14)
  30.                 .fontColor(index === this.currentIndex ? prize.color : Color.White) // 高亮时修改文字颜色
  31.                 .margin({ top: 8 })
  32.                 .textAlign(TextAlign.Center)
  33.             }
  34.           }
  35.           .width('100%')
  36.           .height('100%')
  37.           .justifyContent(FlexAlign.Center)
  38.           .alignItems(HorizontalAlign.Center)
  39.           .backgroundColor(index === this.currentIndex && index !== 4 ? Color.White : prize.color) // 高亮时切换背景色
  40.           .borderRadius(12)
  41.           .padding(10)
  42.           .animation({ // 添加动画效果
  43.             duration: 200,
  44.             curve: Curve.EaseInOut
  45.           })
  46.         }
  47.       })
  48.     }
  49.     // Grid的其他属性保持不变...
  50.   }
  51.   // Column的属性保持不变...
  52. }
  53. // 添加开始抽奖的空方法
  54. startLottery() {
  55.   // 在下一步实现
  56. }
复制代码
在这一步,我们:

  • 为中间的"开始抽奖"按钮添加了点击变乱处理方法startLottery()
  • 实现了格子高亮效果:

    • 当格子被选中时(index === this.currentIndex),背景色变为白色,文字颜色变为奖品颜色
    • 添加了动画效果,使高亮切换更加平滑

  • 预定义了startLottery()方法,暂时为空实现
步调五:实现抽奖动画逻辑

如今我们来实现抽奖动画的核心逻辑,包括循环高亮、速度厘革和结果控制。
  1. @Entry
  2. @Component
  3. struct LuckyDraw {
  4.   // 前面的状态变量保持不变...
  5.   
  6.   // 添加动画控制相关变量
  7.   private timer: number = 0
  8.   private speed: number = 100
  9.   private totalRounds: number = 30
  10.   private currentRound: number = 0
  11.   private targetIndex: number = 2 // 假设固定中奖"优惠券"
  12.   
  13.   // 开始抽奖
  14.   startLottery() {
  15.     if (this.isRunning) {
  16.       return // 防止重复点击
  17.     }
  18.    
  19.     this.isRunning = true
  20.     this.result = '抽奖中...'
  21.     this.currentRound = 0
  22.     this.speed = 100
  23.    
  24.     // 启动动画
  25.     this.runLottery()
  26.   }
  27.   
  28.   // 运行抽奖动画
  29.   runLottery() {
  30.     if (this.timer) {
  31.       clearTimeout(this.timer)
  32.     }
  33.    
  34.     this.timer = setTimeout(() => {
  35.       // 更新当前高亮的格子
  36.       this.currentIndex = (this.currentIndex + 1) % 9
  37.       if (this.currentIndex === 4) { // 跳过中间的"开始抽奖"按钮
  38.         this.currentIndex = 5
  39.       }
  40.       
  41.       this.currentRound++
  42.       
  43.       // 增加速度变化,模拟减速效果
  44.       if (this.currentRound > this.totalRounds * 0.7) {
  45.         this.speed += 10 // 大幅减速
  46.       } else if (this.currentRound > this.totalRounds * 0.5) {
  47.         this.speed += 5 // 小幅减速
  48.       }
  49.       
  50.       // 结束条件判断
  51.       if (this.currentRound >= this.totalRounds && this.currentIndex === this.targetIndex) {
  52.         // 抽奖结束
  53.         this.isRunning = false
  54.         this.result = `恭喜获得: ${this.prizes[this.targetIndex].name}`
  55.       } else {
  56.         // 继续动画
  57.         this.runLottery()
  58.       }
  59.     }, this.speed)
  60.   }
  61.   
  62.   // 组件销毁时清除定时器
  63.   aboutToDisappear() {
  64.     if (this.timer) {
  65.       clearTimeout(this.timer)
  66.       this.timer = 0
  67.     }
  68.   }
  69.   
  70.   // build方法保持不变...
  71. }
复制代码
在这一步,我们实现了抽奖动画的核心逻辑:

  • 添加了动画控制相干变量:

    • timer:用于存储定时器ID
    • speed:控制动画速度
    • totalRounds:总共旋转的轮数
    • currentRound:当前已旋转的轮数
    • targetIndex:预设的中奖索引

  • 实现了startLottery()方法:

    • 防止重复点击
    • 初始化抽奖状态
    • 调用runLottery()开始动画

  • 实现了runLottery()方法:

    • 利用setTimeout创建循环动画
    • 更新高亮格子的索引,并跳过中间的开始按钮
    • 根据进度增长延迟时间,模仿减速效果
    • 根据条件判定是否竣事动画
    • 递归调用自身形成动画循环

  • 添加了aboutToDisappear()生命周期方法,确保在组件销毁时扫除定时器,避免内存走漏
完备代码

末了,我们对代码进行完善和优化,确保抽奖功能正常工作并提升用户体验。
完备的代码如下:
  1. interface PrizeItem {
  2.   id: number
  3.   name: string
  4.   icon: ResourceStr
  5.   color: string
  6. }
  7. @Entry
  8. @Component
  9. struct LuckyDraw {
  10.   // 定义奖品数组
  11.   @State prizes: PrizeItem[] = [
  12.     {
  13.       id: 1,
  14.       name: '谢谢参与',
  15.       icon: $r('app.media.startIcon'),
  16.       color: '#FF9500'
  17.     },
  18.     {
  19.       id: 2,
  20.       name: '10积分',
  21.       icon: $r('app.media.startIcon'),
  22.       color: '#34C759'
  23.     },
  24.     {
  25.       id: 3,
  26.       name: '优惠券',
  27.       icon: $r('app.media.startIcon'),
  28.       color: '#007AFF'
  29.     },
  30.     {
  31.       id: 8,
  32.       name: '1元红包',
  33.       icon: $r('app.media.startIcon'),
  34.       color: '#FF3B30'
  35.     },
  36.     {
  37.       id: 0,
  38.       name: '开始\n抽奖',
  39.       icon: $r('app.media.startIcon'),
  40.       color: '#FF2D55'
  41.     },
  42.     {
  43.       id: 4,
  44.       name: '5元红包',
  45.       icon: $r('app.media.startIcon'),
  46.       color: '#5856D6'
  47.     },
  48.     {
  49.       id: 7,
  50.       name: '免单券',
  51.       icon: $r('app.media.startIcon'),
  52.       color: '#E73C39'
  53.     },
  54.     {
  55.       id: 6,
  56.       name: '50积分',
  57.       icon: $r('app.media.startIcon'),
  58.       color: '#38B0DE'
  59.     },
  60.     {
  61.       id: 5,
  62.       name: '会员卡',
  63.       icon: $r('app.media.startIcon'),
  64.       color: '#39A5DC'
  65.     },
  66.   ]
  67.   // 当前高亮的奖品索引
  68.   @State currentIndex: number = -1
  69.   // 是否正在抽奖
  70.   @State isRunning: boolean = false
  71.   // 中奖结果
  72.   @State result: string = '点击下方按钮开始抽奖'
  73.   // 动画定时器
  74.   private timer: number = 0
  75.   // 动画速度控制
  76.   private speed: number = 100
  77.   private totalRounds: number = 30
  78.   private currentRound: number = 0
  79.   // 预设中奖索引(可以根据概率随机生成)
  80.   private targetIndex: number = 2 // 假设固定中奖"优惠券"
  81.   // 开始抽奖
  82.   startLottery() {
  83.     if (this.isRunning) {
  84.       return
  85.     }
  86.     this.isRunning = true
  87.     this.result = '抽奖中...'
  88.     this.currentRound = 0
  89.     this.speed = 100
  90.     // 启动动画
  91.     this.runLottery()
  92.   }
  93.   // 运行抽奖动画
  94.   runLottery() {
  95.     if (this.timer) {
  96.       clearTimeout(this.timer)
  97.     }
  98.     this.timer = setTimeout(() => {
  99.       // 更新当前高亮的格子
  100.       this.currentIndex = (this.currentIndex + 1) % 9
  101.       if (this.currentIndex === 4) { // 跳过中间的"开始抽奖"按钮
  102.         this.currentIndex = 5
  103.       }
  104.       this.currentRound++
  105.       // 增加速度变化,模拟减速效果
  106.       if (this.currentRound > this.totalRounds * 0.7) {
  107.         this.speed += 10
  108.       } else if (this.currentRound > this.totalRounds * 0.5) {
  109.         this.speed += 5
  110.       }
  111.       // 结束条件判断
  112.       if (this.currentRound >= this.totalRounds && this.currentIndex === this.targetIndex) {
  113.         // 抽奖结束
  114.         this.isRunning = false
  115.         this.result = `恭喜获得: ${this.prizes[this.targetIndex].name}`
  116.       } else {
  117.         // 继续动画
  118.         this.runLottery()
  119.       }
  120.     }, this.speed)
  121.   }
  122.   // 组件销毁时清除定时器
  123.   aboutToDisappear() {
  124.     if (this.timer) {
  125.       clearTimeout(this.timer)
  126.       this.timer = 0
  127.     }
  128.   }
  129.   build() {
  130.     Column({ space: 30 }) {
  131.       // 标题
  132.       Text('幸运抽奖')
  133.         .fontSize(24)
  134.         .fontWeight(FontWeight.Bold)
  135.         .fontColor(Color.White)
  136.       // 结果显示
  137.       Column() {
  138.         Text(this.result)
  139.           .fontSize(20)
  140.           .fontColor(Color.White)
  141.       }
  142.       .width('90%')
  143.       .padding(15)
  144.       .backgroundColor('#0DFFFFFF')
  145.       .borderRadius(16)
  146.       // 九宫格抽奖区域
  147.       Grid() {
  148.         ForEach(this.prizes, (prize: PrizeItem, index) => {
  149.           GridItem() {
  150.             Column() {
  151.               if (index === 4) {
  152.                 // 中间的开始按钮
  153.                 Button({ type: ButtonType.Capsule }) {
  154.                   Text(prize.name)
  155.                     .fontSize(18)
  156.                     .fontWeight(FontWeight.Bold)
  157.                     .textAlign(TextAlign.Center)
  158.                     .fontColor(Color.White)
  159.                 }
  160.                 .width('90%')
  161.                 .height('90%')
  162.                 .backgroundColor(prize.color)
  163.                 .onClick(() => this.startLottery())
  164.               } else {
  165.                 // 普通奖品格子
  166.                 Image(prize.icon)
  167.                   .width(40)
  168.                   .height(40)
  169.                 Text(prize.name)
  170.                   .fontSize(14)
  171.                   .fontColor(index === this.currentIndex && index !== 4 ? prize.color : Color.White)
  172.                   .margin({ top: 8 })
  173.                   .textAlign(TextAlign.Center)
  174.               }
  175.             }
  176.             .width('100%')
  177.             .height('100%')
  178.             .justifyContent(FlexAlign.Center)
  179.             .alignItems(HorizontalAlign.Center)
  180.             .backgroundColor(index === this.currentIndex && index !== 4 ? Color.White : prize.color)
  181.             .borderRadius(12)
  182.             .padding(10)
  183.             .animation({
  184.               duration: 200,
  185.               curve: Curve.EaseInOut
  186.             })
  187.           }
  188.         })
  189.       }
  190.       .columnsTemplate('1fr 1fr 1fr')
  191.       .rowsTemplate('1fr 1fr 1fr')
  192.       .columnsGap(10)
  193.       .rowsGap(10)
  194.       .width('90%')
  195.       .aspectRatio(1)
  196.       .backgroundColor('#0DFFFFFF')
  197.       .borderRadius(16)
  198.       .padding(10)
  199.     }
  200.     .width('100%')
  201.     .height('100%')
  202.     .justifyContent(FlexAlign.Center)
  203.     .backgroundColor(Color.Black)
  204.     .linearGradient({
  205.       angle: 135,
  206.       colors: [
  207.         ['#121212', 0],
  208.         ['#242424', 1]
  209.       ]
  210.     })
  211.     .expandSafeArea() // 颜色扩展到安全区域
  212.   }
  213. }
复制代码
核心概念剖析

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. 高亮效果

格子的高亮效果是通过条件样式实现的:
  1. .backgroundColor(index === this.currentIndex && index !== 4 ? Color.White : prize.color)
复制代码
当格子被选中时(index === this.currentIndex),背景色变为白色,文字颜色变为奖品颜色,产生对比鲜明的高亮效果。
4. 资源清理

在组件销毁时,我们必要扫除定时器以避免内存走漏:
  1. aboutToDisappear() {
  2.   if (this.timer) {
  3.     clearTimeout(this.timer)
  4.     this.timer = 0
  5.   }
  6. }
复制代码
进阶优化思路

完成基本功能后,可以思量以下优化方向:
1. 随机中奖结果

如今中奖结果是固定的,可以实现一个随机算法,根据概率分配不同奖品:
  1. // 根据概率生成中奖索引
  2. generatePrizeIndex() {
  3.   // 定义各奖品的概率权重
  4.   const weights = [50, 10, 5, 3, 0, 2, 1, 8, 20]; // 数字越大概率越高
  5.   const totalWeight = weights.reduce((a, b) => a + b, 0);
  6.   
  7.   // 生成随机数
  8.   const random = Math.random() * totalWeight;
  9.   
  10.   // 根据权重决定中奖索引
  11.   let currentWeight = 0;
  12.   for (let i = 0; i < weights.length; i++) {
  13.     if (i === 4) continue; // 跳过中间的"开始抽奖"按钮
  14.    
  15.     currentWeight += weights[i];
  16.     if (random < currentWeight) {
  17.       return i;
  18.     }
  19.   }
  20.   
  21.   return 0; // 默认返回第一个奖品
  22. }
复制代码
2. 抽奖音效

添加音效可以提升用户体验:
  1. // 播放抽奖音效
  2. playSound(type: 'start' | 'running' | 'end') {
  3.   // 根据不同阶段播放不同音效
  4. }
复制代码
3. 振动反馈

在抽奖开始和竣事时添加振动反馈:
  1. // 导入振动模块
  2. import { vibrator } from '@kit.SensorServiceKit';
  3. // 触发振动
  4. triggerVibration() {
  5.   vibrator.vibrate(50); // 振动50毫秒
  6. }
复制代码
4. 抽奖次数限制

添加抽奖次数限制和剩余次数表现:
  1. @State remainingTimes: number = 3; // 剩余抽奖次数
  2. startLottery() {
  3.   if (this.isRunning || this.remainingTimes <= 0) {
  4.     return;
  5.   }
  6.   
  7.   this.remainingTimes--;
  8.   // 其他抽奖逻辑...
  9. }
复制代码
总结

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

  • 数据结构定义和状态管理
  • 网格结构和循环渲染
  • 条件样式和动画效果
  • 定时器控制和动态速度
  • 生命周期管理和资源清理
希望这篇 HarmonyOS Next 教程对你有所资助,期待您的点赞、评论、收藏。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

数据人与超自然意识

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表