手把手教你寫一個手勢密碼解鎖View(GesturePasswordView)

相信大家在很多的app肯定看到過手勢密碼解鎖View替蔬,但是大家有沒有想過怎么實現(xiàn)這樣一個View告私,哈,接下來承桥,小編手把手教大家教寫一個GesturePasswordView驻粟。

先看一張效果圖

2018-03-22_12_06_12.gif

要實現(xiàn)這樣一個效果,首先需要在屏幕上繪制一個3x3九宮圖,如下圖

nine.png

具體思路:
1蜀撑、要知道每個點在屏幕上位置挤巡。
2、知道各個點的位置酷麦,在去繪制矿卑,調(diào)用drawCircle(float cx, float cy, float radius, @NonNull Paint paint)方法。*

定義一個Point類沃饶,記錄下每個點的位置和狀態(tài)母廷。

public class Point {
//點的圓心的x,y的位置
public float centerX;
public float centerY;
//每個點的索引
private int index;
//正常的狀態(tài)
private int normalState = 0;
//按下的狀態(tài)
private int pressState = 1;
//錯誤的狀態(tài)
private int errorState = 2;
private int state = normalState;
public Point(float centerX, float centerY, int index) {
    this.centerX = centerX;
    this.centerY = centerY;
    this.index = index;
}
public void setErrorState() {
    this.state = errorState;
}
public void setPressState() {
    this.state = pressState;
}
public void setNormalState() {
    this.state = normalState;
}
public void setState(int state) {
    this.state = state;
}
public boolean stateIsPress() {
    return state == pressState;
}
public boolean stateIsNormal() {
    return state == normalState;
}
public boolean stateIsError() {
    return state == errorState;
}

GesturePasswordView類

public class GesturePasswordView extends View {
//3x3的解鎖View
private static final int mRow = 3;
private static final int mColumn = 3;
//顏色
private int mNormalColor = Color.GRAY;
private int mPressedColor = Color.BLUE;
private int mErrorColor = Color.RED;
//畫筆
private Paint mNormalPaint;
private Paint mPressPaint;
private Paint mErrorPaint;
private Paint mLinePaint;
//保存Point的二位數(shù)組
private Point[][] mPoints = new Point[3][3];
private float mDotRadius;
//被選中的點
private List<Point> mSelectPoints = new ArrayList<>();
private boolean mInitOnce;
private boolean mIsTouchPoint;
private boolean mIsErrorStatus;
private GesturePasswordViewListener mGesturePasswordViewListener;

public GesturePasswordView(Context context) {
    this(context, null);
}

public GesturePasswordView(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
}

public GesturePasswordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

private void initPaint() {
    mNormalPaint = getPaint();
    mNormalPaint.setColor(mNormalColor);
    mPressPaint = getPaint();
    mPressPaint.setColor(mPressedColor);
    mErrorPaint = getPaint();
    mErrorPaint.setColor(mErrorColor);
    mLinePaint = getPaint();
    mLinePaint.setColor(mPressedColor);

}

/**
 * 畫筆
 *
 * @return
 */
private Paint getPaint() {
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setDither(true);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(mDotRadius / 9);
    return paint;
}


@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (!mInitOnce) {
        initDot();
        initPaint();
        mInitOnce = true;
    }
    for (int i = 0; i < mPoints.length; i++) {
        for (int j = 0; j < mPoints[i].length; j++) {
            Point point = mPoints[i][j];
            if (point.stateIsNormal()) {
                //先繪制外圓
                canvas.drawCircle(point.centerX, point.centerY, mDotRadius, mNormalPaint);
                //后繪制內(nèi)圓
                canvas.drawCircle(point.centerX, point.centerY, mDotRadius / 6, mNormalPaint);
            } else if (point.stateIsPress()) {
                canvas.drawCircle(point.centerX, point.centerY, mDotRadius, mPressPaint);
                canvas.drawCircle(point.centerX, point.centerY, mDotRadius / 6, mPressPaint);
            } else if (point.stateIsError()) {
                //設(shè)置下線條畫筆的顏色
                canvas.drawCircle(point.centerX, point.centerY, mDotRadius, mErrorPaint);
                canvas.drawCircle(point.centerX, point.centerY, mDotRadius / 6, mErrorPaint);
            }
        }
    }
    //繪制兩個點之間的連線
    drawLineToCanvas(canvas);
}

 /**
   * 初始化每個點
   */

private void initDot() {
int width = this.getWidth();
int height = this.getHeight();
int offsetX = 0;
int offsetY = 0;
//兼容下橫豎屏
if (height > width) {
  offsetY = (height - width) / 2;
 } else {
  offsetX = (width - height) / 2;
 }
int squareWidth = width / 3;
 //外圓的半徑
 mDotRadius = width / 12;
//mPoints[0][0] = new Point(offsetX + squareWidth / 2, offsetY + squareWidth / 2, 0);
//mPoints[0][1] = new Point(offsetX + squareWidth * 3 / 2, offsetY + squareWidth / 2, 1);
//mPoints[0][2] = new Point(offsetX + squareWidth * 5 / 2, offsetY + squareWidth / 2, 2);
//mPoints[1][0] = new Point(offsetX + squareWidth / 2, offsetY + squareWidth * 3 / 2, 3);
//mPoints[1][1] = new Point(offsetX + squareWidth * 3 / 2, offsetY + squareWidth * 3 / 2, 4);
//mPoints[1][2] = new Point(offsetX + squareWidth * 5 / 2, offsetY + squareWidth * 3 / 2, 5);
//mPoints[2][0] = new Point(offsetX + squareWidth / 2, offsetY + squareWidth * 5 / 2, 6);
//mPoints[2][1] = new Point(offsetX + squareWidth * 3 / 2, offsetY + squareWidth * 5 / 2, 7);
//mPoints[2][2] = new Point(offsetX + squareWidth * 5 / 2, offsetY + squareWidth * 5 / 2, 8);
//為了簡便糊肤,用for循環(huán)
        for (int i = 0; i < mRow; i++) {
            for (int j = 0; j < mColumn; j++) {
                mPoints[i][j] = new Point(offsetX + squareWidth * (j * 2 + 1) / 2,
                 offsetY + squareWidth * (i * 2 + 1) / 2, i * mPoints.length + j);
            }
        }
    }

寫到這里3x3九宮圖繪制完了琴昆,接下來是手指觸摸時繪制兩個點之間的連線了。如下圖

CIJ6_4AYF8Y6YEI3PUOHS7B.png

兩個點之間連線是內(nèi)圓之外與內(nèi)圓之外的連線馆揉,怎么計算這兩個點的位置呢业舍?如下圖

m9.png
private void drawLineToCanvas(Canvas canvas) {
    if (mSelectPoints.size() >= 1) {
        Point lastPoint = mSelectPoints.get(0);
        for (int i = 1; i < mSelectPoints.size(); i++) {
            drawLine(canvas, lastPoint, mSelectPoints.get(i));
            lastPoint = mSelectPoints.get(i);
        }
        //觸摸的時候繪制
        if (mIsTouchPoint) {
            drawLine(canvas, lastPoint, new Point(mMovingX, mMovingY, -1));
        }
    }
}

/**
 * 繪制兩個點之間的連線
 *
 * @param canvas
 * @param start
 * @param end
 */
private void drawLine(Canvas canvas, Point start, Point end) {
    //兩點之間的距離
    double pointDistance = MathUtil.distance(end.centerX, end.centerY, start.centerX, start.centerY);
    float dx = end.centerX - start.centerX;
    float dy = end.centerY - start.centerY;
    float rx = (float) ((dx / pointDistance) * (mDotRadius / 6));
    float ry = (float) ((dy / pointDistance) * (mDotRadius / 6));
    canvas.drawLine(start.centerX + rx, start.centerY + ry,
            end.centerX - rx, end.centerY - ry, mLinePaint);
}

處理下手指的Touch事件

float mMovingX;
float mMovingY;

@Override
public boolean onTouchEvent(MotionEvent event) {
    //顯示錯誤時有個時間,是錯誤的狀態(tài)是手指觸摸是不能繪制的
    if (mIsErrorStatus) {
        return false;
    }
    mMovingX = event.getX();
    mMovingY = event.getY();
    Point point = getPressPoint();
    switch (event.getAction()) {
        //手指按下
        case MotionEvent.ACTION_DOWN:
            if (point != null) {
                mIsTouchPoint = true;
                mSelectPoints.add(point);
                point.setPressState();
            }
            break;
        //手指移動
        case MotionEvent.ACTION_MOVE:
            if (point != null) {
                if (!mSelectPoints.contains(point)) {
                    mSelectPoints.add(point);
                    point.setPressState();
                }
            }
            break;
        //手指抬起
        case MotionEvent.ACTION_UP:
            mIsTouchPoint = false;
            if (mGesturePasswordViewListener != null) {
                if (mSelectPoints.size() < 4) {
                    showSelectError();
                } else {
                    clearSelectPoints();
                }
            }
            break;

    }
    invalidate();
    return true;
}

用戶可能會解鎖錯誤把介,處理下解鎖View錯誤的情況勤讽,每個點和點與點之間的連線顯示紅色

/**
 * 顯示錯誤
 */
private void showSelectError() {
    for (int i = 0; i < mPoints.length; i++) {
        for (int j = 0; j < mPoints[i].length; j++) {
            Point point = mPoints[i][j];
            //把所有選中的點的狀態(tài)設(shè)置為Error
            if (mSelectPoints.contains(point)) {
                point.setErrorState();
                mIsErrorStatus = true;
                mLinePaint.setColor(mErrorColor);
            }
        }
    }
    postDelayed(new Runnable() {
        @Override
        public void run() {
            clearSelectPoints();
            mIsErrorStatus = false;
            invalidate();
            mLinePaint.setColor(mPressedColor);
        }
    }, 1000);

}

顯示錯誤完畢之后,需要恢復(fù)下點的正常狀態(tài)

/**
 * 清空所有選中的點
 */
private void clearSelectPoints() {
    for (int i = 0; i < mPoints.length; i++) {
        for (int j = 0; j < mPoints[i].length; j++) {
            Point point = mPoints[i][j];
            //把所有選中的點的狀態(tài)設(shè)置為Normal
            if (mSelectPoints.contains(point)) {
                point.setNormalState();
            }
        }
    }
    mSelectPoints.clear();
}

獲取手指觸摸的是哪個點拗踢,根據(jù)點的圓心位置到手指觸摸的位置的距離小于外圓的半徑脚牍。如圖

m6.png

只要在藍(lán)色區(qū)域內(nèi)就可以,之外的話手指肯定不在這個圓內(nèi)巢墅。

private Point getPressPoint() {
    for (int i = 0; i < mPoints.length; i++) {
        for (int j = 0; j < mPoints[i].length; j++) {
            Point point = mPoints[i][j];
            if (point != null) {
                if (MathUtil.checkInRound(point.centerX, point.centerY, mDotRadius, mMovingX, mMovingY)) {
                    return point;
                }
            }
        }
    }
    return null;
}

最后說下index的作用诸狭,index是記錄了每個的點的索引,這樣做可以知道用戶連了哪些點君纫。整個3x3的九宮圖解鎖到這繪制完畢驯遇,只粘貼了部分關(guān)鍵代碼,完整代碼github地址:https://github.com/StevenYan88/GesturePasswordView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蓄髓,一起剝皮案震驚了整個濱河市叉庐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌会喝,老刑警劉巖陡叠,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異肢执,居然都是意外死亡枉阵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門预茄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兴溜,“玉大人,你說我怎么就攤上這事∽净眨” “怎么了刨沦?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斋攀。 經(jīng)常有香客問我已卷,道長,這世上最難降的妖魔是什么淳蔼? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任侧蘸,我火速辦了婚禮,結(jié)果婚禮上鹉梨,老公的妹妹穿的比我還像新娘讳癌。我一直安慰自己,他們只是感情好存皂,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布晌坤。 她就那樣靜靜地躺著,像睡著了一般旦袋。 火紅的嫁衣襯著肌膚如雪骤菠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天疤孕,我揣著相機(jī)與錄音商乎,去河邊找鬼。 笑死祭阀,一個胖子當(dāng)著我的面吹牛鹉戚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播专控,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼抹凳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了伦腐?” 一聲冷哼從身側(cè)響起赢底,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎柏蘑,沒想到半個月后颖系,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辩越,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了信粮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黔攒。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出督惰,到底是詐尸還是另有隱情不傅,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布赏胚,位于F島的核電站访娶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏觉阅。R本人自食惡果不足惜崖疤,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望典勇。 院中可真熱鬧劫哼,春花似錦、人聲如沸割笙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伤溉。三九已至般码,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乱顾,已是汗流浹背板祝。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留糯耍,地道東北人扔字。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像温技,于是被迫代替她去往敵國和親革为。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容