鸿蒙特效教程08-幸运大转盘抽奖

news/2025/3/29 21:36:08/文章来源:https://www.cnblogs.com/Megasu/p/18794469

鸿蒙特效教程08-幸运大转盘抽奖

本教程将带领大家从零开始,一步步实现一个完整的转盘抽奖效果,包括界面布局、Canvas绘制、动画效果和抽奖逻辑等。

1. 需求分析与整体设计

温馨提醒:本案例有一定难度,建议先收藏起来。

在开始编码前,让我们先明确转盘抽奖的基本需求:

  • 展示一个可旋转的奖品转盘
  • 转盘上有多个奖品区域,每个区域有不同的颜色和奖品名称
  • 点击"开始抽奖"按钮后,转盘开始旋转
  • 转盘停止后,指针指向的位置即为抽中的奖品
  • 每个奖品有不同的中奖概率

整体设计思路:

  • 使用HarmonyOS的Canvas组件绘制转盘
  • 利用动画效果实现转盘旋转
  • 根据概率算法确定最终停止位置

image

2. 基础界面布局

首先,我们创建基础的页面布局,包括标题、转盘区域和结果显示。

@Entry
@Component
struct LuckyWheel {build() {Column() {// 标题Text('幸运大转盘').fontSize(28).fontWeight(FontWeight.Bold).fontColor(Color.White).margin({ bottom: 20 })// 抽奖结果显示Text('点击开始抽奖').fontSize(20).fontColor(Color.White).backgroundColor('#1AFFFFFF').width('90%').textAlign(TextAlign.Center).padding(15).borderRadius(16).margin({ bottom: 30 })// 转盘容器(后续会添加Canvas)Stack({ alignContent: Alignment.Center }) {// 这里稍后会添加Canvas绘制转盘// 中央开始按钮Button({ type: ButtonType.Circle }) {Text('开始\n抽奖').fontSize(18).fontWeight(FontWeight.Bold).textAlign(TextAlign.Center).fontColor(Color.White)}.width(80).height(80).backgroundColor('#FF6B6B')}.width('90%').aspectRatio(1).backgroundColor('#0DFFFFFF').borderRadius(16).padding(15)}.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor(Color.Black).linearGradient({angle: 135,colors: [['#1A1B25', 0],['#2D2E3A', 1]]})}
}

这个基础布局创建了一个带有标题、结果显示区和转盘容器的页面。转盘容器使用Stack组件,这样我们可以在转盘上方放置"开始抽奖"按钮。

3. 定义数据结构

接下来,我们需要定义转盘上的奖品数据结构:

// 奖品数据接口
interface PrizesItem {name: string     // 奖品名称color: string    // 转盘颜色probability: number // 概率权重
}@Entry
@Component
struct LuckyWheel {// 奖品数据private prizes: PrizesItem[] = [{ name: '谢谢参与', color: '#FFD8A8', probability: 30 },{ name: '10积分', color: '#B2F2BB', probability: 20 },{ name: '5元红包', color: '#D0BFFF', probability: 10 },{ name: '优惠券', color: '#A5D8FF', probability: 15 },{ name: '免单券', color: '#FCCFE7', probability: 5 },{ name: '50积分', color: '#BAC8FF', probability: 15 },{ name: '会员月卡', color: '#99E9F2', probability: 3 },{ name: '1元红包', color: '#FFBDBD', probability: 2 }]// 状态变量@State isSpinning: boolean = false // 是否正在旋转@State rotation: number = 0 // 当前旋转角度@State result: string = '点击开始抽奖' // 抽奖结果// ...其余代码
}

这里我们定义了转盘上的8个奖品,每个奖品包含名称、颜色和概率权重。同时定义了三个状态变量来跟踪转盘的状态。

4. 初始化Canvas

现在,让我们初始化Canvas来绘制转盘:

@Entry
@Component
struct LuckyWheel {// Canvas 相关设置private readonly settings: RenderingContextSettings = new RenderingContextSettings(true); // 启用抗锯齿private readonly ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);// 转盘相关属性private canvasWidth: number = 0 // 画布宽度private canvasHeight: number = 0 // 画布高度// ...其余代码build() {Column() {// ...之前的代码// 转盘容器Stack({ alignContent: Alignment.Center }) {// 使用Canvas绘制转盘Canvas(this.ctx).width('100%').height('100%').onReady(() => {// 获取Canvas尺寸this.canvasWidth = this.ctx.widththis.canvasHeight = this.ctx.height// 初始绘制转盘this.drawWheel()})// 中央开始按钮// ...按钮代码}// ...容器样式}// ...外层容器样式}// 绘制转盘(先定义一个空方法,稍后实现)private drawWheel(): void {// 稍后实现}
}

这里我们创建了Canvas绘制上下文,并在onReady回调中获取Canvas尺寸,然后调用drawWheel方法绘制转盘。

5. 实现转盘绘制

接下来,我们实现drawWheel方法,绘制转盘:

// 绘制转盘
private drawWheel(): void {if (!this.ctx) returnconst centerX = this.canvasWidth / 2const centerY = this.canvasHeight / 2const radius = Math.min(centerX, centerY) * 0.85// 清除画布this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)// 保存当前状态this.ctx.save()// 移动到中心点this.ctx.translate(centerX, centerY)// 应用旋转this.ctx.rotate((this.rotation % 360) * Math.PI / 180)// 绘制转盘扇形const anglePerPrize = 2 * Math.PI / this.prizes.lengthfor (let i = 0; i < this.prizes.length; i++) {const startAngle = i * anglePerPrizeconst endAngle = (i + 1) * anglePerPrizethis.ctx.beginPath()this.ctx.moveTo(0, 0)this.ctx.arc(0, 0, radius, startAngle, endAngle)this.ctx.closePath()// 填充扇形this.ctx.fillStyle = this.prizes[i].colorthis.ctx.fill()// 绘制边框this.ctx.strokeStyle = "#FFFFFF"this.ctx.lineWidth = 2this.ctx.stroke()}// 恢复状态this.ctx.restore()
}

这段代码实现了基本的转盘绘制:

  1. 计算中心点和半径
  2. 清除画布
  3. 平移坐标系到转盘中心
  4. 应用旋转角度
  5. 绘制每个奖品的扇形区域

运行后,你应该能看到一个彩色的转盘,但还没有文字和指针。

6. 添加奖品文字

继续完善drawWheel方法,添加奖品文字:

// 绘制转盘扇形
const anglePerPrize = 2 * Math.PI / this.prizes.length
for (let i = 0; i < this.prizes.length; i++) {// ...之前的扇形绘制代码// 绘制文字this.ctx.save()this.ctx.rotate(startAngle + anglePerPrize / 2)this.ctx.textAlign = 'center'this.ctx.textBaseline = 'middle'this.ctx.fillStyle = '#333333'this.ctx.font = '24px sans-serif'// 旋转文字,使其可读性更好// 第一象限和第四象限的文字需要额外旋转180度,保证文字朝向const needRotate = (i >= this.prizes.length / 4) && (i < this.prizes.length * 3 / 4)if (needRotate) {this.ctx.rotate(Math.PI)this.ctx.fillText(this.prizes[i].name, -radius * 0.6, 0, radius * 0.5)} else {this.ctx.fillText(this.prizes[i].name, radius * 0.6, 0, radius * 0.5)}this.ctx.restore()
}

这里我们在每个扇形区域添加了奖品文字,并根据位置进行适当旋转,确保文字朝向正确,提高可读性。

7. 添加中心圆盘和指针

继续完善drawWheel方法,添加中心圆盘和指针:

// 恢复状态
this.ctx.restore()// 绘制中心圆盘
this.ctx.beginPath()
this.ctx.arc(centerX, centerY, radius * 0.2, 0, 2 * Math.PI)
this.ctx.fillStyle = '#FF8787'
this.ctx.fill()
this.ctx.strokeStyle = '#FFFFFF'
this.ctx.lineWidth = 3
this.ctx.stroke()// 绘制指针 - 固定在顶部中央
this.ctx.beginPath()
// 三角形指针
this.ctx.moveTo(centerX, centerY - radius - 10)
this.ctx.lineTo(centerX - 15, centerY - radius * 0.8)
this.ctx.lineTo(centerX + 15, centerY - radius * 0.8)
this.ctx.closePath()
this.ctx.fillStyle = '#FF6B6B'
this.ctx.fill()
this.ctx.strokeStyle = '#FFFFFF'
this.ctx.lineWidth = 2
this.ctx.stroke()// 绘制中心文字
this.ctx.textAlign = 'center'
this.ctx.textBaseline = 'middle'
this.ctx.fillStyle = '#FFFFFF'
this.ctx.font = '18px sans-serif'// 绘制两行文字
this.ctx.fillText('开始', centerX, centerY - 10)
this.ctx.fillText('抽奖', centerX, centerY + 10)

这段代码添加了:

  1. 中心的红色圆盘
  2. 顶部的三角形指针
  3. 中心的"开始抽奖"文字

现在转盘的静态部分已经完成。下一步,我们将实现转盘的旋转动画。

8. 实现抽奖逻辑

在实现转盘旋转前,我们需要先实现抽奖逻辑,决定最终奖品:

// 生成随机目标索引(基于概率权重)
private generateTargetIndex(): number {const weights = this.prizes.map(prize => prize.probability)const totalWeight = weights.reduce((a, b) => a + b, 0)const random = Math.random() * totalWeightlet currentWeight = 0for (let i = 0; i < weights.length; i++) {currentWeight += weights[i]if (random < currentWeight) {return i}}return 0
}

这个方法根据每个奖品的概率权重生成一个随机索引,概率越高的奖品被选中的机会越大。

9. 实现转盘旋转

现在,让我们实现转盘旋转的核心逻辑:

// 转盘属性
private spinDuration: number = 4000 // 旋转持续时间(毫秒)
private targetIndex: number = 0 // 目标奖品索引
private spinTimer: number = 0 // 旋转定时器// 开始抽奖
private startSpin(): void {if (this.isSpinning) returnthis.isSpinning = truethis.result = '抽奖中...'// 生成目标奖品索引this.targetIndex = this.generateTargetIndex()console.info(`抽中奖品索引: ${this.targetIndex}, 名称: ${this.prizes[this.targetIndex].name}`)// 计算目标角度// 每个奖品占据的角度 = 360 / 奖品数量const anglePerPrize = 360 / this.prizes.length// 因为Canvas中0度是在右侧,顺时针旋转,而指针在顶部(270度位置)// 所以需要将奖品旋转到270度位置对应的角度// 目标奖品中心点的角度 = 索引 * 每份角度 + 半份角度const prizeAngle = this.targetIndex * anglePerPrize + anglePerPrize / 2// 需要旋转到270度位置的角度 = 270 - 奖品角度// 但由于旋转方向是顺时针,所以需要计算为正向旋转角度const targetAngle = (270 - prizeAngle + 360) % 360// 获取当前角度的标准化值(0-360范围内)const currentRotation = this.rotation % 360// 计算从当前位置到目标位置需要旋转的角度(确保是顺时针旋转)let deltaAngle = targetAngle - currentRotationif (deltaAngle <= 0) {deltaAngle += 360}// 最终旋转角度 = 当前角度 + 5圈 + 到目标的角度差const finalRotation = this.rotation + 360 * 5 + deltaAngleconsole.info(`当前角度: ${currentRotation}°, 奖品角度: ${prizeAngle}°, 目标角度: ${targetAngle}°, 旋转量: ${deltaAngle}°, 最终角度: ${finalRotation}°`)// 使用基于帧动画的方式旋转,确保视觉上平滑旋转let startTime = Date.now()let initialRotation = this.rotation// 清除可能存在的定时器if (this.spinTimer) {clearInterval(this.spinTimer)}// 创建新的动画定时器this.spinTimer = setInterval(() => {const elapsed = Date.now() - startTimeif (elapsed >= this.spinDuration) {// 动画结束clearInterval(this.spinTimer)this.spinTimer = 0this.rotation = finalRotationthis.drawWheel()this.isSpinning = falsethis.result = `恭喜获得: ${this.prizes[this.targetIndex].name}`return}// 使用easeOutExpo效果:慢慢减速const progress = this.easeOutExpo(elapsed / this.spinDuration)this.rotation = initialRotation + progress * (finalRotation - initialRotation)// 重绘转盘this.drawWheel()}, 16) // 大约60fps的刷新率
}// 缓动函数:指数减速
private easeOutExpo(t: number): number {return t === 1 ? 1 : 1 - Math.pow(2, -10 * t)
}

这段代码实现了转盘旋转的核心逻辑:

  1. 根据概率生成目标奖品
  2. 计算目标奖品对应的角度
  3. 计算需要旋转的总角度(多转几圈再停在目标位置
  4. 使用定时器实现转盘的平滑旋转
  5. 使用缓动函数实现转盘的减速效果
  6. 旋转结束后显示中奖结果

10. 连接按钮点击事件

现在我们需要将"开始抽奖"按钮与startSpin方法连接起来:

// 中央开始按钮
Button({ type: ButtonType.Circle }) {Text('开始\n抽奖').fontSize(18).fontWeight(FontWeight.Bold).textAlign(TextAlign.Center).fontColor(Color.White)
}
.width(80)
.height(80)
.backgroundColor('#FF6B6B')
.onClick(() => this.startSpin())
.enabled(!this.isSpinning)
.stateEffect(true) // 启用点击效果

这里我们给按钮添加了onClick事件处理器,点击按钮时调用startSpin方法。同时使用enabled属性确保在转盘旋转过程中按钮不可点击。

11. 添加资源释放

为了防止内存泄漏,我们需要在页面销毁时清理定时器:

aboutToDisappear() {// 清理定时器if (this.spinTimer !== 0) {clearInterval(this.spinTimer)this.spinTimer = 0}
}

12. 添加底部概率说明(可选)

最后,我们在页面底部添加奖品概率说明:

// 底部说明
Text('奖品说明:概率从高到低排序').fontSize(14).fontColor(Color.White).opacity(0.7).margin({ top: 20 })// 概率说明
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) {ForEach(this.prizes, (prize: PrizesItem, index) => {Text(`${prize.name}: ${prize.probability}%`).fontSize(12).fontColor(Color.White).backgroundColor(prize.color).borderRadius(12).padding({left: 10,right: 10,top: 4,bottom: 4}).margin(4)})
}
.width('90%')
.margin({ top: 10 })

这段代码在页面底部添加了奖品概率说明,直观展示各个奖品的中奖概率。

13. 美化优化

为了让转盘更加美观,我们可以进一步优化转盘的视觉效果:

// 绘制转盘
private drawWheel(): void {// ...之前的代码// 绘制转盘外圆边框this.ctx.beginPath()this.ctx.arc(centerX, centerY, radius + 5, 0, 2 * Math.PI)this.ctx.fillStyle = '#2A2A2A'this.ctx.fill()this.ctx.strokeStyle = '#FFD700' // 金色边框this.ctx.lineWidth = 3this.ctx.stroke()// ...其余绘制代码// 给指针添加渐变色和阴影let pointerGradient = this.ctx.createLinearGradient(centerX, centerY - radius - 15,centerX, centerY - radius * 0.8)pointerGradient.addColorStop(0, '#FF0000')pointerGradient.addColorStop(1, '#FF6666')this.ctx.fillStyle = pointerGradientthis.ctx.fill()this.ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'this.ctx.shadowBlur = 5this.ctx.shadowOffsetX = 2this.ctx.shadowOffsetY = 2// ...其余代码
}

完整代码

以下是完整的实现代码:

interface PrizesItem {name: string // 奖品名称color: string // 转盘颜色probability: number // 概率权重
}@Entry
@Component
struct Index {// Canvas 相关设置private readonly settings: RenderingContextSettings = new RenderingContextSettings(true); // 启用抗锯齿private readonly ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);// 奖品数据private prizes: PrizesItem[] = [{ name: '谢谢参与', color: '#FFD8A8', probability: 30 },{ name: '10积分', color: '#B2F2BB', probability: 20 },{ name: '5元红包', color: '#D0BFFF', probability: 1 },{ name: '优惠券', color: '#A5D8FF', probability: 15 },{ name: '免单券', color: '#FCCFE7', probability: 5 },{ name: '50积分', color: '#BAC8FF', probability: 15 },{ name: '会员月卡', color: '#99E9F2', probability: 3 },{ name: '1元红包', color: '#FFBDBD', probability: 2 }]// 转盘属性@State isSpinning: boolean = false // 是否正在旋转@State rotation: number = 0 // 当前旋转角度@State result: string = '点击开始抽奖' // 抽奖结果private spinDuration: number = 4000 // 旋转持续时间(毫秒)private targetIndex: number = 0 // 目标奖品索引private spinTimer: number = 0 // 旋转定时器private canvasWidth: number = 0 // 画布宽度private canvasHeight: number = 0 // 画布高度// 生成随机目标索引(基于概率权重)private generateTargetIndex(): number {const weights = this.prizes.map(prize => prize.probability)const totalWeight = weights.reduce((a, b) => a + b, 0)const random = Math.random() * totalWeightlet currentWeight = 0for (let i = 0; i < weights.length; i++) {currentWeight += weights[i]if (random < currentWeight) {return i}}return 0}// 开始抽奖private startSpin(): void {if (this.isSpinning) {return}this.isSpinning = truethis.result = '抽奖中...'// 生成目标奖品索引this.targetIndex = this.generateTargetIndex()console.info(`抽中奖品索引: ${this.targetIndex}, 名称: ${this.prizes[this.targetIndex].name}`)// 计算目标角度// 每个奖品占据的角度 = 360 / 奖品数量const anglePerPrize = 360 / this.prizes.length// 因为Canvas中0度是在右侧,顺时针旋转,而指针在顶部(270度位置)// 所以需要将奖品旋转到270度位置对应的角度// 目标奖品中心点的角度 = 索引 * 每份角度 + 半份角度const prizeAngle = this.targetIndex * anglePerPrize + anglePerPrize / 2// 需要旋转到270度位置的角度 = 270 - 奖品角度// 但由于旋转方向是顺时针,所以需要计算为正向旋转角度const targetAngle = (270 - prizeAngle + 360) % 360// 获取当前角度的标准化值(0-360范围内)const currentRotation = this.rotation % 360// 计算从当前位置到目标位置需要旋转的角度(确保是顺时针旋转)let deltaAngle = targetAngle - currentRotationif (deltaAngle <= 0) {deltaAngle += 360}// 最终旋转角度 = 当前角度 + 5圈 + 到目标的角度差const finalRotation = this.rotation + 360 * 5 + deltaAngleconsole.info(`当前角度: ${currentRotation}°, 奖品角度: ${prizeAngle}°, 目标角度: ${targetAngle}°, 旋转量: ${deltaAngle}°, 最终角度: ${finalRotation}°`)// 使用基于帧动画的方式旋转,确保视觉上平滑旋转let startTime = Date.now()let initialRotation = this.rotation// 清除可能存在的定时器if (this.spinTimer) {clearInterval(this.spinTimer)}// 创建新的动画定时器this.spinTimer = setInterval(() => {const elapsed = Date.now() - startTimeif (elapsed >= this.spinDuration) {// 动画结束clearInterval(this.spinTimer)this.spinTimer = 0this.rotation = finalRotationthis.drawWheel()this.isSpinning = falsethis.result = `恭喜获得: ${this.prizes[this.targetIndex].name}`return}// 使用easeOutExpo效果:慢慢减速const progress = this.easeOutExpo(elapsed / this.spinDuration)this.rotation = initialRotation + progress * (finalRotation - initialRotation)// 重绘转盘this.drawWheel()}, 16) // 大约60fps的刷新率}// 缓动函数:指数减速private easeOutExpo(t: number): number {return t === 1 ? 1 : 1 - Math.pow(2, -10 * t)}// 绘制转盘private drawWheel(): void {if (!this.ctx) {return}const centerX = this.canvasWidth / 2const centerY = this.canvasHeight / 2const radius = Math.min(centerX, centerY) * 0.85// 清除画布this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)// 保存当前状态this.ctx.save()// 移动到中心点this.ctx.translate(centerX, centerY)// 应用旋转this.ctx.rotate((this.rotation % 360) * Math.PI / 180)// 绘制转盘扇形const anglePerPrize = 2 * Math.PI / this.prizes.lengthfor (let i = 0; i < this.prizes.length; i++) {const startAngle = i * anglePerPrizeconst endAngle = (i + 1) * anglePerPrizethis.ctx.beginPath()this.ctx.moveTo(0, 0)this.ctx.arc(0, 0, radius, startAngle, endAngle)this.ctx.closePath()// 填充扇形this.ctx.fillStyle = this.prizes[i].colorthis.ctx.fill()// 绘制边框this.ctx.strokeStyle = "#FFFFFF"this.ctx.lineWidth = 2this.ctx.stroke()// 绘制文字this.ctx.save()this.ctx.rotate(startAngle + anglePerPrize / 2)this.ctx.textAlign = 'center'this.ctx.textBaseline = 'middle'this.ctx.fillStyle = '#333333'this.ctx.font = '30px'// 旋转文字,使其可读性更好// 第一象限和第四象限的文字需要额外旋转180度,保证文字朝向const needRotate = (i >= this.prizes.length / 4) && (i < this.prizes.length * 3 / 4)if (needRotate) {this.ctx.rotate(Math.PI)this.ctx.fillText(this.prizes[i].name, -radius * 0.6, 0, radius * 0.5)} else {this.ctx.fillText(this.prizes[i].name, radius * 0.6, 0, radius * 0.5)}this.ctx.restore()}// 恢复状态this.ctx.restore()// 绘制中心圆盘this.ctx.beginPath()this.ctx.arc(centerX, centerY, radius * 0.2, 0, 2 * Math.PI)this.ctx.fillStyle = '#FF8787'this.ctx.fill()this.ctx.strokeStyle = '#FFFFFF'this.ctx.lineWidth = 3this.ctx.stroke()// 绘制指针 - 固定在顶部中央this.ctx.beginPath()// 三角形指针this.ctx.moveTo(centerX, centerY - radius - 10)this.ctx.lineTo(centerX - 15, centerY - radius * 0.8)this.ctx.lineTo(centerX + 15, centerY - radius * 0.8)this.ctx.closePath()this.ctx.fillStyle = '#FF6B6B'this.ctx.fill()this.ctx.strokeStyle = '#FFFFFF'this.ctx.lineWidth = 2this.ctx.stroke()// 绘制中心文字this.ctx.textAlign = 'center'this.ctx.textBaseline = 'middle'this.ctx.fillStyle = '#FFFFFF'this.ctx.font = '18px sans-serif'// 绘制两行文字this.ctx.fillText('开始', centerX, centerY - 10)this.ctx.fillText('抽奖', centerX, centerY + 10)}aboutToDisappear() {// 清理定时器if (this.spinTimer !== 0) {clearInterval(this.spinTimer) // 改成 clearIntervalthis.spinTimer = 0}}build() {Column() {// 标题Text('幸运大转盘').fontSize(28).fontWeight(FontWeight.Bold).fontColor(Color.White).margin({ bottom: 20 })// 抽奖结果显示Text(this.result).fontSize(20).fontColor(Color.White).backgroundColor('#1AFFFFFF').width('90%').textAlign(TextAlign.Center).padding(15).borderRadius(16).margin({ bottom: 30 })// 转盘容器Stack({ alignContent: Alignment.Center }) {// 使用Canvas绘制转盘Canvas(this.ctx).width('100%').height('100%').onReady(() => {// 获取Canvas尺寸this.canvasWidth = this.ctx.widththis.canvasHeight = this.ctx.height// 初始绘制转盘this.drawWheel()})// 中央开始按钮Button({ type: ButtonType.Circle }) {Text('开始\n抽奖').fontSize(18).fontWeight(FontWeight.Bold).textAlign(TextAlign.Center).fontColor(Color.White)}.width(80).height(80).backgroundColor('#FF6B6B').onClick(() => this.startSpin()).enabled(!this.isSpinning).stateEffect(true) // 启用点击效果}.width('90%').aspectRatio(1).backgroundColor('#0DFFFFFF').borderRadius(16).padding(15)// 底部说明Text('奖品概率说明').fontSize(14).fontColor(Color.White).opacity(0.7).margin({ top: 20 })// 概率说明Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) {ForEach(this.prizes, (prize: PrizesItem) => {Text(`${prize.name}: ${prize.probability}%`).fontSize(12).fontColor(Color.White).backgroundColor(prize.color).borderRadius(12).padding({left: 10,right: 10,top: 4,bottom: 4}).margin(4)})}.width('90%').margin({ top: 10 })}.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor(Color.Black).linearGradient({angle: 135,colors: [['#1A1B25', 0],['#2D2E3A', 1]]}).expandSafeArea()}
}

总结

本教程对 Canvas 的使用有一定难度,建议先点赞收藏。

这个幸运大转盘效果包含以下知识点:

  1. 使用Canvas绘制转盘,支持自定义奖品数量和概率
  2. 平滑的旋转动画和减速效果
  3. 基于概率权重的抽奖算法
  4. 美观的UI设计和交互效果

在实际应用中,你还可以进一步扩展这个组件:

  • 添加音效
  • 实现3D效果
  • 添加中奖历史记录
  • 连接后端API获取真实抽奖结果
  • 添加抽奖次数限制

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

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

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

相关文章

ZGC圣经:ZGC垃圾回收器的原理、调优,ZGC 漏标的 分析与 研究

本文的 原始地址 ,传送门 本文的 原始地址 ,传送门 尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:听说你是高手,说说,你的ZGC 怎…

20242312 2024-2025-2 《Python程序设计》实验一报告

20242321 2024-2025-2 《Python程序设计》实验报告 课程:《Python程序设计》 班级: 2423 姓名: 周梓濠 学号:20242312 实验教师:王志强 实验日期:2025年3月12日 必修/选修: 公选课 1.实验内容 1.熟悉Python开发环境,配置VSCode; 2.配置gitee仓库,掌握git技能; 3.…

Tita:OKR 与项目管理、绩效一体化,助力企业实现目标驱动型发展

在当今竞争激烈的商业环境中,企业要想脱颖而出,实现可持续发展,明确目标、高效执行以及精准衡量结果至关重要。Tita 作为一款先进的管理平台,通过将 OKR(目标与关键成果法)与项目管理、绩效一体化相结合,为企业提供了一套完整的目标管理与执行解决方案,助力企业 “树目…

R语言绘图笔记—火山图

做个R语言绘图笔记,方便以后查看。 火山图:library(ggplot2) p1 <- ggplot(res, aes(x =log2FoldChange, y=-log10(pvalue), colour=sig)) + geom_point(size=2, shape = 16) + # 设置节点大小# 设置节点对应颜色scale_color_manual("significant" ,values=c(&…

git拉取代码到本地

1.先登入git,然后点击克隆,复制http地址:2.打开终端,输入 git clone http://xxxxxx3.然后在这个文件夹下就能看到复制的代码文件了4.可以把这个文件剪切到自己想要的路径下 5.pycharm打开就能用了

20232425 实验一 《python程序设计》实验报告

20232425 2024-2025-2 《Python程序设计》实验1报告 课程:《Python程序设计》 班级: 2324 姓名: 刘润民 学号:20232425 实验教师:王志强老师 实验日期:2025年3月12日 必修/选修: 公选课 一、实验内容 1、熟悉Python开发环境; 2、练习Python运行、调试技能;(编写书中的…

软件产品开发中常见的10个问题及处理方法

常见的10个问题 产品开发中常见的10个问题思维导图需求相关 1. 需求不明确 在日常工作中,需求来源于用户、老板、客户、竞品分析、业务部门、产品经理等,这些人或部门会提出需求,因为他们不是产品经理,提出的需求可能是一句话、一个想法这些简单的需求点,这些需求模糊且不…

025 登录页-记住密码的功能实现

这个页面这样写 这个页面这样写 这里定义两个常量 这里用常量代替 记住密码的勾选这样写

matplotlib之柱状图

# 柱状图 import matplotlib.pyplot as pltmovie_name = [雷神3:诸神黄昏,正义联盟,东方快车谋杀案,寻梦环游记,全球风暴,降魔传,追捕,横坐标] x = range(len(movie_name)) y = [73853,57767,22354,15969,14839,8725,8716,8318]plt.figure(figsize=(20,8), dpi=100) bars = plt…

matplotlib之散点图

# 散点图 import matplotlib.pyplot as plt import random from pylab import mpl # 设置显示中文字体 mpl.rcParams["font.sans-serif"] = ["SimHei"]# 准备数据 x=[225.98,247.07,253.14,457.85,241.58,301.01, 20.67,288.64…

Jmeter 连接hive配置

环境:部署的hive 版本是4.0.0 jmeter 连接hive时连接驱动跟安装的hive版本有极大关系,比如说hive 版本是4.0.0版本,在jmeter的测试计划【test plan】中添加的hive-jdbc驱动如果是hive-jdbc-3.1.3-standalone.jar版本的话会连接失败,只能添加hive 4.0.0 或以上的版本。如hiv…