OpenHarmony ArkUI+原生繪圖之幸運大轉盤
效果展示

此外,轉盤的獎項的數量,內容都是可以變動的(菜單就是用來編輯獎項的,后續完善),如下:

主要功能
- 實現轉盤抽獎功能,可以設定中獎概率。
- 獎項的數量、內容可自由設定。
- 原生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>
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函數是基于當前坐標進行偏移,旋轉也是基于當前坐標進行旋轉。所以當一個扇區的文字填寫結束后,需要將坐標還原,這樣才方便定位到一下處扇區位置。