作者:楊尚曉
前言
Openharmony作為我們國内開源系統,并且以js/ets最為開發語言,完全抛棄了java。作為一名前端開發人員,也需要為咱們國産系統壯大起來,是以貢獻一份微薄之力。😄😄
項目介紹
ohos-pattern是一個解鎖功能的元件,是基于openharmony AkrUI(JS)實作的。在手機指紋解鎖還沒來臨之前,圖案解鎖是一個很熱門的功能,包括數字解鎖。不過現在也還很多應用場景中有使用到圖案解鎖和數字解鎖的功能。本文圖案解鎖僅僅隻是實作UI層面,并不涉及到加密解密的的方式。這個可以二次開發使用。
本文為第一篇,後續會更新加入數字解鎖功能,如果後續能涉及到生物識别API,還會加入指紋解鎖等三種解鎖方式。
環境說明
- 開發工具:DevEco Studio 3.0 Beta4
- SDK版本:3.1.6.6(API Version 8)
- 主要元件:canvas
效果展示
元件屬性
屬性名 | 類型 | 預設值 | 作用 |
---|---|---|---|
radius | Number | 5 | 密碼點半徑[5-20] |
scope | Number | 20 | 熱區範圍[20-40] |
point-color | String | #383838 | 密碼點内顔色 |
point-stroke-color | String | #ff9800 | 密碼點邊框顔色 |
active-color | String | #ff9800 | 激活密碼點顔色 |
line-color | String | #1a73e8 | 密碼線顔色 |
active-line-color | String | #04be02 | 激活密碼線顔色 |
is-show-line | Boolean | true | 是否顯示密碼線 |
元件事件
屬性名 | 類型 | 傳回值 | 備注 |
---|---|---|---|
result-cb | Function | Array | 傳回密碼結果 |
引用元件
<element name="ohos-pattern" src="../../common/component/ohosPattern/ohosPattern"></element>
<div class="container">
<ohos-pattern
radius="{{radius}}"
scope="{{scope}}"
point-color="{{pointColor}}"
point-stroke-color="{{pointStrokeColor}}"
active-color="{{activeColor}}"
line-color="{{lineColor}}"
active-line-color="{{activeLineColor}}"
is-show-line="{{isShowLine}}"
></ohos-pattern>
</div>
實作思路
實作思路比較簡單,使用canvas繪制九個密碼點,然後通過監聽手勢觸摸和9個密碼點的碰撞産生密碼資料
- 建立canvas
- 繪制9個密碼點
- 繪制可控區域(就是密碼點跟随手指一動區域)
- 監聽手勢
實作過程
1. 建立canvas
建立canvas元素,并且綁定touchstart,touchmove,touchend手勢事件
<div class="container">
<canvas
id="canvas"
ref="canvas"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
style="width: {{w}}px;height: {{h}}px"
></canvas>
</div>
在js中初始化canvas對象
onShow(){
this.initCanvas();
},
// 初始化畫布
initCanvas(){
const c = this.$refs.canvas;
this.ctx = c.getContext('2d', { antialias: true });
},
2. 繪制九個密碼點
下面看個草圖
從草圖上可以看出我們的密碼點位置了,這裡使用for循環方法實作,建立3行,每行3個點。
這裡有個注意點,因為我們密碼點有一個需要跟随手勢移動的需求,需要修改point_list的資料,是以使用point_list_copy複制了一份出來,這裡使用es6的展開運算符...來實作深拷貝。
// 建立9個密碼點
createPoint(){
let point_x = Math.ceil((this.w - this.R * 6) / 4 + this.R);
let point_y = Math.ceil((this.h - this.R * 6) / 4 + this.R);
for (var j = 0; j < 3; j++) {
for (var i = 0; i < 3; i++) {
let d = {
x: point_x * (i + 1) + this.R * i,
y: point_y * (j + 1) + this.R * j
}
this.point_list.push(d);
this.point_list_copy = [...this.point_list];
this.drawPoint(d);
}
}
},
下面實作将9個密碼點到畫布上
// 繪制9個密碼點到畫布上
drawPoint(obj) {
let {x, y} = obj;
// 繪制外圓環
this.ctx.beginPath();
this.ctx.lineCap = "round"; //向線條的每個末端添加圓形線帽
this.ctx.lineWidth = (this.R / 2); //繪制圓的邊框
this.ctx.strokeStyle = '#ff9800'; //繪制邊框的顔色
this.ctx.arc(x, y, this.R, 0, 2 * Math.PI);
this.ctx.stroke();
this.ctx.closePath();
// 繪制内圓
this.ctx.beginPath();
this.ctx.arc(x, y, this.R, 0, 2 * Math.PI);
this.ctx.fillStyle = "#383838";
this.ctx.fill();
this.ctx.closePath();
},
下面來看看繪制後的效果
3. 監聽手勢和密碼點的碰撞
我們寫了三個手勢事件
- 觸摸開始 onTouchStart
- 觸摸變化 onTouchMove
- 觸摸結束 onTouchEnd
onTouchStart(e){
let x = e.touches[0].localX;
let y = e.touches[0].localY;
this.isCollision(x, y)
},
onTouchMove(e){
let x = e.touches[0].localX;
let y = e.touches[0].localY;
this.isCollision(x, y);
// 每次觸摸後都需要清除畫布重新繪制
this.ctx.clearRect(0, 0, this.w, this.h);
this.reDraw(x, y, true);
},
onTouchEnd(e){
this.ctx.clearRect(0, 0, this.w, this.h);
this.reDraw(e.touches[0].clientX, e.touches[0].clientY, false);
// 松開手後,記錄所有觸摸的點
lg.log('圖案結果:',this.result)
this.result = [];
},
重繪畫布
// 重繪
reDraw(x, y, bol) {
for (let i in this.point_list) {
// 重新繪制9個密碼點
this.drawPoint(this.point_list[i]);
}
for (let i in this.result) {
// 重新繪制密碼狀态
this.drawStatus(this.result[i]);
}
},
監聽手勢觸摸是否和密碼點發生碰撞
this.scope表示熱區範圍,當觸摸點進入這個熱區範圍,說明已經跟該密碼點發生接觸碰撞了
// 是否跟9個密碼點發生接觸。
isCollision(x, y) {
let c_x, c_y;
for (let i in this.point_list) {
c_x = Math.abs(x - this.point_list[i].x);
c_y = Math.abs(y - this.point_list[i].y);
// 如果發生碰撞,記錄狀态
if (c_x < this.scope && c_y < this.scope) {
lg.warn('發生了碰撞,碰撞點是:', i)
this.drawStatus(i)
return
}
}
},
當觸摸點和密碼點發生碰撞之後,需要給該密碼點繪制觸摸狀态
// 繪制觸摸到密碼點的狀态
drawStatus(index) {
// 繪制狀态内圓
this.ctx.beginPath();
this.ctx.arc(this.point_list[index].x, this.point_list[index].y, (this.R / 2), 0, 2 * Math.PI);
this.ctx.fillStyle = "#ac2dfb";
this.ctx.fill();
this.ctx.closePath();
//記錄緩存碰撞的結果
if(this.result.indexOf(index) === -1){
this.result.push(index)
}
},
下面看看效果
但是這樣還不夠,我們能還需要繪制連接配接的密碼線
4. 繪制密碼線
在重繪方法裡最頂端加入繪制密碼線方法
this.drawLine(this.result, x, y, bol);
繪制密碼線方法
這裡需要繪制兩條線:
- 一條是跟随觸摸移動的線
- 一條是已經存在兩點之間的線
drawLine(arr, x, y, bol) {
if(arr.length === 0){
return;
}
// 當存在已經兩個點的時候,兩點直線形成連線
this.ctx.beginPath();
for (let i in arr) {
if (i == 0) {
this.ctx.moveTo(this.point_list[arr[i]].x, this.point_list[arr[i]].y);
} else {
this.ctx.lineTo(this.point_list[arr[i]].x, this.point_list[arr[i]].y);
}
}
this.ctx.strokeStyle = '#1a73e8';
this.ctx.lineWidth = (this.R / 2);
this.ctx.stroke();
// 跟着手機滑動的線條
if (bol) {
this.ctx.beginPath();
this.ctx.moveTo(this.point_list[arr[arr.length - 1]].x, this.point_list[arr[arr.length - 1]].y);
this.ctx.lineTo(x, y);
this.ctx.strokeStyle = '#04be02';
this.ctx.lineWidth = (this.R / 2);
this.ctx.stroke();
}
},
好了,下面我們看看加上密碼線的效果
5. 實作可移動的密碼點
優化體驗,增加了密碼點跟随手勢一起移動的效果。
在重繪的頂部添加繪制可移動的密碼點方法
// 繪制跟随手勢移動的密碼點
this.pointAn(x,y,bol);
r_x,r_y表示密碼點熱區範圍,在熱區範圍内可以将該密碼點變為觸摸點跟随觸摸點一起移動,當觸摸點離開了熱區之後,密碼點回到原來的中心位置。
// 繪制狀态point範圍内活動
pointAn(x, y, bol){
if(bol){
if(this.result.length === 0){
return;
}
let t = this.result[this.result.length - 1];
let r_x = Math.abs(x - this.point_list_copy[t].x);
let r_y = Math.abs(y - this.point_list_copy[t].y);
if(r_x < this.scope && r_y < this.scope){
this.point_list[t] = {x,y}
} else {
this.point_list = [...this.point_list_copy];
}
} else {
this.point_list = [...this.point_list_copy];
}
},
最後來看看加上熱區的效果
到這裡,整個基本功能已經實作了。
最後再來看一下最終效果
代碼位址
ohos-pattern 基于OpenHarmony JSAPI實作圖案解鎖元件
總結
該元件整體實作邏輯都比較簡單,主要是通過手勢去繪制canvas畫布實作的,這裡需要注意的是,api version需要7以上,因為在api version 6 之前,存在canvas重繪閃屏的情況,在api 7之後修複了這個問題。相比api 6之前的版本,api8真的優化和修複了很多功能。很期待harmongOS 3.0更新,可以在真機下去體驗ets開發的應用。
更多原創内容請關注:中軟國際 HarmonyOS 技術團隊
入門到精通、技巧到案例,系統化分享HarmonyOS開發技術,歡迎投稿和訂閱,讓我們一起攜手前行共建鴻蒙生态。