最近寫了個九宮格手勢解鎖,先附上效果圖吧?
上面是效果圖,再附上代碼?行。
Github 戳我
這篇博客講述一下實現這個效果的思路。我覺得知道了這個思路,並且順着這個思路走,每個人都可以自己寫一個出來。但是寫出來的代碼健壯否,就看個人功底了。
創建GestureView繼承自View
在這個代碼裏你只需要做兩件事
- 實現onDraw方法
- 實現onTouchEvent方法
記下來我們肢解一下知識點。
在onDraw方法中,需要做四件事:
- 計算獲取9個點的座標和小箭頭的三個座標點
- 初始化9個點圓
- 繪製選中的圓,以及選中的圓的內圓,還有就是圓和圓之間的連接線,以及小箭頭
- 繪製最後一條線段,也就是最後一個選中點到你正在觸摸移動的線段。
viewImpl.initData(viewWidth, viewHeight); //初始化9個點數據
viewImpl.onDrawInitView(paint, canvas); //初始化View
viewImpl.onDrawSelectView(paint, canvas); //繪製選中的View
viewImpl.onDrawLineView(paint, canvas); //繪製連接線
我們需要重寫onTouchEvent,在onTouchEvent方法中我們可以處理三個事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN : {
viewImpl.touchDown(event);
break;
}
case MotionEvent.ACTION_MOVE : {
viewImpl.touchMove(event);
break;
}
case MotionEvent.ACTION_UP : {
viewImpl.touchUp(event);
break;
}
}
return true;
}
這三個事件中,一個一個說
- ACTION_DOWN事件,這個事件處理比較簡單,我們需要判斷當前按下的點是否是有效點,也就是:當前觸摸的點是否在9個圓內。 如果不在裏面,就不重新繪製View;如果在裏面,需要存儲下當前的圓的中心點座標,表示:選中點。我們代碼中是:selectPointMap;
用到的數學知識:一個點(觸摸點)是否在圓內。 - ACTION_MOVE事件,這個事件內,我們需要獲取當前觸摸移動過程中選中的點,存儲到selectPointMap集合中。用到的數學知識:一個點(圓的座標)的投影是否在線段上;線段是否穿過圓。
- ACTION_UP事件內處理很簡單,就是你是想:校驗密碼,還是設置密碼。都可以在這裏面出來。
總結用到的數學知識點:
- 一個點(觸摸點)是否在圓內?點到中心點的距離小於半徑。如下圖:
- 一個點C(圓的座標)的投影是否在線段上?AB * AB >= BC * BC + AC * AC, 即可認爲:該點的投影在線段上。如下圖:
- 線段是否穿過圓? 圓的中心圓到線段的距離如果小於半徑,即可認爲線段和圓相切。
- 已知一個條高,還有這條高的兩個座標點,以及角度,求另外一點的座標? 這是求小箭頭的三個座標。如下圖:
至此我們的整體流程講完了。接下來說一下我們的這個Demo的功能點:
功能點
屬性設置
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="GestureView">
<!--顏色設置-->
<attr name="bigGraphicalColor" format="color"/>
<attr name="bigSelectGraphicalColor" format="color"/>
<attr name="smallGraphicalColor" format="color"/>
<attr name="smallSelectGraphicalColor" format="color"/>
<attr name="lineColor" format="color"/>
<attr name="arrowColor" format="color"/>
<!--是否需要顯示箭頭-->
<attr name="arrowIsNeed" format="boolean"/>
<!--是否跳過中間的點-->
<attr name="isSkipMiddlePoint" format="boolean"/>
<!--長度-->
<attr name="arrowRadius" format="dimension"/>
<attr name="bigGraphicalRadius" format="dimension"/>
<attr name="smallGraphicalRadius" format="dimension"/>
<!--最低選中點的個數-->
<attr name="needSelectPointNumber" format="integer"/>
<!--選中錯誤顏色-->
<attr name="errorColor" format="color"/>
<!--手勢類型-->
<attr name="gestureType" format="enum">
<!--檢查手勢-->
<enum name="check_gesture" value="3"/>
<!--設置手勢-->
<enum name="set_gesture" value="4"/>
</attr>
</declare-styleable>
</resources>
上述列出了該Demo支持的屬性。
支持自定義大圓,小圓,以及連接的線段
方法 | 參數 |
---|---|
setHandleBigGraphical | IGraphicalView接口 |
setHandleSmallGraphical | IGraphicalView接口 |
setHandleLineGraphical | IGraphicalView接口 |
/**
* FileName:IGraphicalView
* Create By:liumengqiang
* Description:自定義需要實現的接口
*/
public interface IGraphicalView {
/**
* 初始化繪製
* @param paint 畫筆
* @param canvas 畫板
* @param coordinateList 9個點的中心點座標
* @param attrsModel 屬性對象
*/
void onDrawInitView(Paint paint, Canvas canvas, List<ChildGraphicalView> coordinateList, AttrsModel attrsModel);
/**
*
* @param paint
* @param canvas
* @param selectMap 當前選中點的信息
* @param attrsModel
* @param TYPE 當前的類型(成功?錯誤?繪製中?)
*/
void onDrawSelectView(Paint paint, Canvas canvas, LinkedHashMap<Integer, ChildGraphicalView> selectMap, AttrsModel attrsModel, int TYPE);
}
詳細的自定義可見:HandleBigGraphical類。
支持自定義手勢結果處理器
支持自定義手勢結果處理器
支持自定義手勢結果處理器
這個功能點肯定很重要吧,因爲,你想自己處理手勢的判斷邏輯。我們可能是設置密碼,也可能是驗證密碼,這樣的處理邏輯是不一樣的,這時候我們就可以自定義。
方法 | 參數 | 返回值 |
---|---|---|
setProcessor | IProcessor接口 | TYPE_COMPLETE,TYPE_ERROR, TYPE_RESET |
示例詳見:CheckGestureProcessor類,設置手勢示例。
public class CheckGestureProcessor implements IProcessor{
private String gestureValue;
public CheckGestureHandleData() {
}
/**
*
* @param selectIndexArray
* @return
*/
public int handleData(Set<Integer> selectIndexArray) {
if(gestureValue.equals(getGestureValue(selectIndexArray))) { //密碼一致
return GestureViewType.TYPE_COMPLETE;
} else { // 密碼不一致
return GestureViewType.TYPE_ERROR;
}
}
private String getGestureValue(Set<Integer> selectIndexArray) {
String value = "";
for(Integer integer : selectIndexArray) {
value += integer;
}
return value;
}
@Override
public void setGestureValue(String gestureValue) {
if(gestureValue == null) {
throw new IllegalArgumentException("輸入參數GestureValue非法!");
} else {
this.gestureValue = gestureValue;
}
}
}
handleData方法內部處理自定需要自定義的邏輯。
然後設置自定義處理:
gestureView.setiHandleData(new CheckGestureProcessor());
結果回調GestureListener
方法 | 描述 |
---|---|
onStart | 手勢開始時觸發 |
onPointNumberChange | 移動過程中,選中點時觸發 |
onComplete | 設置成功時觸發 |
onFailed | 小於最低設置的點個數觸發 |
valueDisaccord | 輸入手勢值不一樣時觸發 |
transitionStatus | 過渡狀態觸發(需要多次輸入校驗,比如:設置密碼) |
如下示例:
gestureView.setGestureListener(new GestureListener() {
@Override
public void valueDisaccord() {
Toast.makeText(CheckGestureActivity.this, "輸入密碼錯誤", Toast.LENGTH_SHORT).show();
}
@Override
public void transitionStatus() {
}
@Override
public void onStart() {
Log.e(TAG , "初始化完成");
}
@Override
public void onPointNumberChange(int selectIndex) {
Log.e(TAG, "點被選中");
}
@Override
public void onComplete(List<Integer> list) {
Toast.makeText(CheckGestureActivity.this, "校驗成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onFailed() {
Toast.makeText(CheckGestureActivity.this, "個數小於四個", Toast.LENGTH_SHORT).show();
}
});
基本功能點講完了,如果聽的有點迷糊,我覺得下載一下Demo,然後對照 Demo跑一遍,就OK了。
再附上一張整體效果Gif:
這個Demo還是缺點還是有的:
- 繪製調用和Touch耦合度有點高,詳見:GestureViewImpl
- 初始化座標可放入onMeasure方法,未支持自適應佈局;
這些後續再補上。
好了歡迎大家Star。如果BUG歡迎指正。
Github 戳我
Github 戳我
Github 戳我
Github 戳我