OpenHarmony ArkUI+原生绘图之幸运大转盘

开发
实现转盘抽奖功能,可以设定中奖概率。奖项的数量、内容可自由设定,先生成一个随机数,根据随机数取值大小,决定奖品内容。

[[439117]]

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

效果展示

#星光计划2.0# OpenHarmony ArkUI+原生绘图之幸运大转盘-鸿蒙HarmonyOS技术社区

此外,转盘的奖项的数量,内容都是可以变动的(菜单就是用来编辑奖项的,后续完善),如下:

#星光计划2.0# OpenHarmony ArkUI+原生绘图之幸运大转盘-鸿蒙HarmonyOS技术社区

主要功能

  1. 实现转盘抽奖功能,可以设定中奖概率。
  2. 奖项的数量、内容可自由设定。
  3. 原生html\css\js代码,没有使用资源文件,可复用。

设计时考虑到的问题

1.控件是使用现有图片还是通过CSS画出?

先是用的图片充当控件,考虑到奖项的内容可编辑性,还是老老实实画控件比较好。

2.每个奖项的概率如何设计?

先生成一个随机数,根据随机数取值大小,决定奖品内容。假设所有奖项的取值范围坐落到0100的数轴上,并且1号奖品的取值范围是010,2号:10~30, 3号:30~35,。。。通过设定每个奖项取值区间的大小来决定中奖的权重,这样就能控制中奖概率了。

3.如何实现奖项可编辑?

我将所有奖项存放在一个数据数组中,先能通过遍历数组中奖项信息,画出转盘,这是第一步。

之后,通过菜单功能提供一个列表控件,使其能够对数组中的信息进行增删改查,这是第二步。

在界面加载的onShow()函数中进行初始化,这样每次界面显示的时候就能更新转盘了。

具体代码

index.hml

<div class="container"
    <text class="title"> 幸运大转盘 </text> 
    <div class="outer" id="outer"
    <!--画布--> 
        <canvas id="canvas" class="canvas"></canvas> 
    <!--内圆--> 
        <div class="circle"></div> 
    <!--长方形--> 
        <div class="rectangle"></div> 
    <!--正方形箭头--> 
        <div class="square"></div> 
    </div> 
    <div class="btns"
        <button class="button" type="capsule" onclick="start"> 抽奖 </button> 
        <button class="button" type="capsule" onclick="menu"> 菜单 </button> 
    </div> 
</div> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

outer就是转盘整体,包含转盘和箭头。我箭头是通过将圆+长方形+正方形平移、旋转组合而成的(虽然有点笨,没有想到其它办法)。转盘是一个画布canvas,通过移动画笔起点,旋转,一个扇区接一个扇区画出的。按键有两个,抽奖就是转动转盘,实现抽奖逻辑。菜单按键跳转到新的界面,实现奖项内容的编辑,当然还没写完。。。 

index.css

.container { 
    flex-direction: column
    align-items: center; 
    justify-content: space-between

 
.title { 
    font-size: 38px; 
    font-weight: 600; 
    height: 20%; 

 
.outer { 
    position: relative

 
.canvas { 
    width: 360px; 
    height: 400px; 

 
.circle { 
    position: absolute
    width: 40px; 
    height: 40px; 
    background-color: darkred; 
    border-radius: 20px; 
    transform: translate(160px,180px); 

 
.rectangle { 
    position: absolute
    width: 20px; 
    height: 40px; 
    background-color: darkred; 
    transform: translate(170px,150px); 

 
.square { 
    position:absolute
    width: 20px; 
    height: 20px; 
    background-color: darkred; 
    top: 140px; 
    left: 170px; 
    transform: rotate(45deg); 

 
.btns { 
    justify-content:space-around; 

 
.button{ 
    margin-top: 10%; 
    height: 10%; 
    font-size: 30px; 
    font-weight: 600; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.

canvas中的宽、高决定了转盘大小,代码中将转盘的半径设置为画布一半宽的长度。同时,由于箭头是由圆、长方形、正方形平移旋转组成,那他们的偏移量、大小也是相对.canvas的属性取的,如果大小有变动需要调整。

为什么不将箭头也画出来?

如果将箭头也画在画布上,那么我不能实现转盘转动,箭头不动的动画了,画布是一个整体。

index.js

import prompt from '@system.prompt'
import router from '@system.router'
 
export default { 
    data: { 
        //1.1创建奖项信息 
        infoArr: [ 
            { name'1号奖品' }, 
            { name'2号奖品' }, 
            { name'3号奖品' }, 
            { name'4号奖品' }, 
            { name'5号奖品' }, 
            { name'6号奖品' }, 
            { name'7号奖品' }, 
            { name'未中奖'  }, 
        ], 
        //1.2画布大小 
        circleHeight: 400, 
        circleWidth: 360, 
        //1.3扇区弧度 
        arcAngle: 0, 
        //1.4扇区角度 
        jiaoDu: 0, 
        //1.4动画参数 
        animation: ''
        options: { 
            duration: 5000, 
            fill: 'forwards'
            easing: 'cubic-bezier(.2,.93,.43,1);'
        }, 
    }, 
 
    onShow() { 
        const ca = this.$element('canvas'); 
        const ctx = ca.getContext('2d'); 
 
        //2.设定参数 
        //2.1定义圆心,显示在画布中间 
        var x0 = this.circleWidth * 0.5; 
        var y0 = this.circleHeight * 0.5; 
        //2.2定义半径 
        var radius = this.circleWidth * 0.5; 
        //2.3扇形弧度 
        this.arcAngle = 360 / this.infoArr.length * Math.PI / 180; 
        //2.4扇区角度 
        this.jiaoDu = 360 / this.infoArr.length; 
        //2.5定义起始弧度,箭头向上,初始度数需要-90deg 
        var beginAngle = this.arcAngle * 0.5 - 90 * Math.PI / 180; 
 
        //3.遍历,绘制扇区 
        for (var i = 0; i < this.infoArr.length; i++) { 
            //3.1结束弧度 
            var endAngle = beginAngle + this.arcAngle; 
            //3.2开启路径 
            ctx.beginPath(); 
            //3.3起点 
            ctx.moveTo(x0, y0); 
            //3.4绘制扇区 
            ctx.arc(x0, y0, radius, beginAngle, endAngle); 
            //3.5设置颜色 
            if (i == this.infoArr.length - 1) { 
                ctx.fillStyle = '#2f4f4f'; //未中奖灰色 
            } else if (i % 2) { 
                ctx.fillStyle = '#ffa500'
            } else { 
                ctx.fillStyle = '#ff4500'
            } 
            //3.6填充颜色 
            ctx.fill(); 
 
            //4.绘制文字 
            //4.1文字弧度 
            var textAngle = beginAngle + this.arcAngle * 0.5; 
            var text = this.infoArr[i].name
            //4.2文字坐标 
            var textX = x0 + (radius * 2 / 3) * Math.cos(textAngle); 
            var textY = y0 + (radius * 2 / 3) * Math.sin(textAngle); 
            //4.3平移画布起点到文字位置 
            ctx.translate(textX, textY); 
            //4.4旋转画布 
            ctx.rotate((this.jiaoDu * (i + 1) - 90) * Math.PI / 180); 
            //4.5设置文字字号和字体 
            ctx.font = "25px '微软雅黑'"
            //4.6文字居中对齐 
            ctx.textAlign = 'center'
            ctx.textBaseline = 'middle'
            //4.7绘制文字 
            ctx.strokeText(text, 0, 0); 
            //4.8还原旋转、平移,方便下次旋转 
            ctx.rotate(-(this.jiaoDu * (i + 1) - 90) * Math.PI / 180); 
            ctx.translate(-textX, -textY); 
 
            //5.更新起始弧度, 将当前扇形的结束弧度作为下一个扇形的起始弧度 
            beginAngle = endAngle; 
        } 
    }, 
 
    start: function () { 
        //6.旋转事件 
        //6.1奖品总数 
        let count = this.infoArr.length; 
        //6.2生成随机数 
        let randomNum = Math.floor(Math.random() * count); 
        //6.3转动角度(+ 360*3) 
        let deg = randomNum * this.jiaoDu + 360 * 3 + "deg"
        //6.4奖品名 
        let index = count - randomNum - 1; 
        let name = this.infoArr[index].name
        console.log("name == " + name); 
        //6.5动画帧 
        var frames = [ 
            { 
                transform: { 
                    rotate: '0deg' 
                }, 
            }, 
            { 
                transform: { 
                    rotate: deg 
                }, 
            } 
        ]; 
        //6.5动画绑定 
        this.animation = this.$element('canvas').animate(frames, this.options); 
        //6.6添加完成事件 
        this.animation.onfinish = function () { 
            if (randomNum % count) { 
                prompt.showDialog({ 
                    message: "恭喜抽中" + name + "!" 
                }); 
            } else { 
                prompt.showDialog({ 
                    message: "下次再来!" 
                }); 
            } 
        }; 
        //6.7调用播放开始的方法 
        this.animation.play(); 
    }, 
 
    menu: function () { 
        router.push ({ 
            uri: 'pages/menuPage/menuPage'
        }); 
    }, 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.

js中存放主要逻辑,所以对注释也比较详细。下面是个人踩坑中学习的点:

//1.1创建奖项信息 
可以增加减少奖项来预览将要实现的菜单功能,不要搞事情哈,奖项至少为1,代码中没有除0保护。 
//1.4动画参数 
duration是时长。easing,是描述动画的时间曲线,实现动画由快变慢。fill:forwards在动画结束后,目标将保留动画结束时的状态。 
//3.4绘制扇区 
x0, y0,扇区的起点坐标。radius,扇区半径。beginAngle,扇区起始的弧度,endAngle,扇区结束的弧度。 
//3.5设置颜色 
每个扇区设置两个相间的颜色,未中奖特殊扇区用灰色调标识。 
//4.2文字坐标 
由于文字在扇区中间,所以需要利用正弦余弦计算坐标,再进行画面旋转,才能调整正确的文字方向。 
//4.8还原旋转、平移,方便下次旋转 
translate函数是基于当前坐标进行偏移,旋转也是基于当前坐标进行旋转。所以当一个扇区的文字填写结束后,需要将坐标还原,这样才方便定位到一下处扇区位置。 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2015-03-03 16:06:32

软件圈

2018-11-13 17:12:53

戴尔

2022-08-23 16:07:02

ArkUI鸿蒙

2022-08-08 19:46:26

ArkUI鸿蒙

2022-08-12 19:13:07

etswifi连接操作

2023-05-31 10:08:51

2023-08-17 15:04:22

2022-07-26 14:40:42

ArkUIJS

2023-08-17 15:01:08

ArkUI布局渲染

2022-07-20 15:32:25

时钟翻页Text组件

2022-05-26 14:50:15

ArkUITS扩展

2022-08-24 16:08:22

ETS鸿蒙

2022-05-27 14:55:34

canvas画布鸿蒙

2024-01-11 15:54:55

eTS语言TypeScript应用开发

2022-08-04 13:55:08

拼数字小游戏鸿蒙

2022-07-28 14:26:11

AI作诗应用开发

2022-09-20 14:35:59

ArkUI鸿蒙JS

2022-09-02 15:17:04

ArkUI鸿蒙

2022-09-15 15:04:16

ArkUI鸿蒙

2023-03-13 15:03:05

鸿蒙ArkUI
点赞
收藏

51CTO技术栈公众号