Android自定義View——手把手教你九宮格手勢解鎖

最近寫了個九宮格手勢解鎖,先附上效果圖吧?

上面是效果圖,再附上代碼?行。
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;
    }

這三個事件中,一個一個說

  1. ACTION_DOWN事件,這個事件處理比較簡單,我們需要判斷當前按下的點是否是有效點,也就是:當前觸摸的點是否在9個圓內。 如果不在裏面,就不重新繪製View;如果在裏面,需要存儲下當前的圓的中心點座標,表示:選中點。我們代碼中是:selectPointMap;
    用到的數學知識:一個點(觸摸點)是否在圓內。
  2. ACTION_MOVE事件,這個事件內,我們需要獲取當前觸摸移動過程中選中的點,存儲到selectPointMap集合中。用到的數學知識:一個點(圓的座標)的投影是否在線段上;線段是否穿過圓。
  3. ACTION_UP事件內處理很簡單,就是你是想:校驗密碼,還是設置密碼。都可以在這裏面出來。

總結用到的數學知識點:

  1. 一個點(觸摸點)是否在圓內?點到中心點的距離小於半徑。如下圖:
  2. 一個點C(圓的座標)的投影是否在線段上?AB * AB >= BC * BC + AC * AC, 即可認爲:該點的投影在線段上。如下圖:
  3. 線段是否穿過圓? 圓的中心圓到線段的距離如果小於半徑,即可認爲線段和圓相切。
  4. 已知一個條高,還有這條高的兩個座標點,以及角度,求另外一點的座標? 這是求小箭頭的三個座標。如下圖:

至此我們的整體流程講完了。接下來說一下我們的這個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 戳我

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章