#星光计划2.0# OpenHarmony ArkUI+原生绘图之幸运大转盘(一) 原创 精华

发布于 2021-12-2 17:08
浏览
6收藏

【本文正在参与51CTO HarmonyOS技术社区创作者激励计划-星光计划2.0】

活动链接: https://harmonyos.51cto.com/posts/9422


ArkUI实操,结合原生绘图实现的奖项数量与内容可变的幸运大转盘实例。
@[toc]

效果展示

#星光计划2.0# OpenHarmony ArkUI+原生绘图之幸运大转盘(一)-开源基础软件社区

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

#星光计划2.0# OpenHarmony ArkUI+原生绘图之幸运大转盘(一)-开源基础软件社区

主要功能

  1. 实现转盘抽奖功能,可以设定中奖概率。

  2. 奖项的数量、内容可自由设定。

  3. 原生html\css\js代码,没有使用资源文件,可复用。

设计时考虑到的问题

  1. 控件是使用现有图片还是通过CSS画出?
    先是用的图片充当控件,考虑到奖项的内容可编辑性,还是老老实实画控件比较好。

  2. 每个奖项的概率如何设计?
    先生成一个随机数,根据随机数取值大小,决定奖品内容。假设所有奖项的取值范围坐落到0~100的数轴上,并且1号奖品的取值范围是0~10,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>

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;
}

.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',
        });
    },
}

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函数是基于当前坐标进行偏移,旋转也是基于当前坐标进行旋转。所以当一个扇区的文字填写结束后,需要将坐标还原,这样才方便定位到一下处扇区位置。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2021-12-7 17:34:34修改
8
收藏 6
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐