1. 程式人生 > >自定義View之圖案解鎖(手勢密碼)

自定義View之圖案解鎖(手勢密碼)

圖案解鎖應用的越來越廣泛,因為好奇所以,查了些資料自己也模擬了一個,有不對的地方,歡迎吐槽。

  • 1、首先我們可以知道我們沒有這個現成的View, 所以需要自定義一個View:

    建立一個類繼承View並實現構造方法

  • 2、建立一個Point的類,為啥不用系統的,因為:

    我們的這個點有三種狀態:正常(即未繪製的時候)、已選中、不符合條件的狀態,還有每個點有個屬於自己的編號,為了將手勢密碼轉為字串去儲存。

  • 3、進行初始化工作(我都在三個引數的構造方法中寫的,並且一個和兩個引數的都引用了三個引數的構造方法):

    1)要畫介面肯定少不了畫筆,那麼我們可以初始化兩個畫筆,一個是畫點,一個是畫線
    2)初始化圖案的九個點


    3)初始化,繪製點需要的圖片(因為點我用圖片了,線就是線)

  • 4、重寫onMeasure、onLayout、onDraw方法,我這裡只用到了onMeasure和onDraw

    在onMeasure方法中計算一下View的大小,我這裡沒有按照全屏去處理,我覺得這樣更靈活佈局,onDraw肯定要重寫的,自定義View還不寫onDraw那幹啥

  • 5、在onDraw中畫點和線,那麼線是根據我們的手指滑動來畫的,那麼我們就要重寫onTouchEvent方法

    onDraw方法中就是繪製點和線
    onTouchEvent方法處理down、move和up的三個動作,這裡返回的是true,如果不是true,後續的move和up是不會執行的

  • 6、當手指按下的那一刻,我們怎麼知道有沒有點在點上即點在圖片上

    藉助兩點之間的距離小於半徑來判斷

  • 7、零碎的知識點:
    我們要有個點的集合存放選中的點,如果選中過的就不要新增進去,沒有選中的就新增,
    當手指擡起意味著繪製結束,所以判斷是否符合要求(根據需求來定),我這裡是4個以上的滿足,要判斷是正確的還是錯誤的,錯誤的就改變點的狀態,正確就記錄他們點的code.
    需要注意的是每次動作後要重新整理介面,呼叫postInvalidate方法來呼叫onDraw.

大體說了一下,剩下的看程式碼吧:

import android.content.Context;
import android.graphics.Bitmap;
import
android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.CountDownTimer; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.Toast; import com.example.a_0102.mylearn.R; import java.util.ArrayList; import java.util.List; /** * 圖案解鎖即手勢密碼 */ public class GesturePwdView extends View { private Context context; private int interval = 50;//間隔圖片之間的間隔 private int bitmapR = 100;//圖片的半徑 private Point points[][] = new Point[3][3]; private Bitmap bitmapNormal, bitmapPass, bitmapError;//三種狀態的圖片 private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//畫圈的畫筆(不要在onDraw方法中建立畫筆) private Paint paintLine = new Paint(Paint.ANTI_ALIAS_FLAG);//畫線的畫筆 private Point point;//當前點選的point private List<Point> pointList = new ArrayList<>();//選中的點 private boolean isFinish;//是否結束繪畫 private int moveX, moveY;//手指移動的點 private StringBuilder code = new StringBuilder(); public GesturePwdView(Context context) { this(context, null); } public GesturePwdView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public GesturePwdView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; //設定化畫線的畫筆 paintLine.setColor(Color.parseColor("#ff8500")); paintLine.setStrokeWidth(10); //初始化點和資源 initPoints(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = 4 * interval + 6 * bitmapR; //設定View的寬高 setMeasuredDimension(width, width); } @Override protected void onDraw(Canvas canvas) { //畫點 drawBitmap(canvas); //畫線 drawLine(canvas); } //畫線,兩個點兩個點的畫 private void drawLine(Canvas canvas) { if (pointList.size() > 0) { for (int i = 0; i < pointList.size(); i++) { if (i != pointList.size() - 1) { Point a = pointList.get(i); Point b = pointList.get(i + 1); canvas.drawLine(a.getX(), a.getY(), b.getX(), b.getY(), paintLine); } else { Point last = pointList.get(i); canvas.drawLine(last.getX(), last.getY(), moveX, moveY, paintLine); } } } } /** * 畫bitmap * * @param canvas */ private void drawBitmap(Canvas canvas) { for (int i = 0; i < points.length; i++) { for (int j = 0; j < points[i].length; j++) { Point pp = points[i][j]; if (pp.getStatus() == Point.STATUS_NORMAL) { //-bitmapR的目的是使繪畫的起點為圖片的圓點(如interval+bitmap=150,繪製的起點即左上角是(150,150),而我們想要的是(150,150)是圖片的中點) canvas.drawBitmap(bitmapNormal, pp.x - bitmapR, pp.y - bitmapR, paint); } else if (pp.getStatus() == Point.STATUS_PASS) { canvas.drawBitmap(bitmapPass, pp.x - bitmapR, pp.y - bitmapR, paint); } else { canvas.drawBitmap(bitmapError, pp.x - bitmapR, pp.y - bitmapR, paint); } } } } @Override public boolean onTouchEvent(MotionEvent event) { moveX = (int) event.getX(); moveY = (int) event.getY(); isFinish = false; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: reset(); point = checkSelectPoint(moveX, moveY); if (point != null && !checkPointAdd(point)) { pointList.add(point); } break; case MotionEvent.ACTION_MOVE: point = checkSelectPoint(moveX, moveY); if (point != null && !checkPointAdd(point)) { pointList.add(point); } break; case MotionEvent.ACTION_UP: //手指擡起繪製結束 isFinish = true; break; } if (isFinish && pointList.size() <= 9 && pointList.size() > 0) { //去除最後一個點的後面的線 moveX = pointList.get(pointList.size() - 1).getX(); moveY = pointList.get(pointList.size() - 1).getY(); if (pointList.size() == 1) { Toast.makeText(context, "不構成圖案重新開始", Toast.LENGTH_LONG).show(); setError(); } else if (pointList.size() > 1 && pointList.size() < 4) { Toast.makeText(context, "至少選4個不同的點", Toast.LENGTH_LONG).show(); pointList.get(pointList.size() - 1).getY(); setError(); }else {//成功的圖案,記錄編號,方便儲存 for(Point point:pointList){ code.append(point.getCode()); } } } //重新整理介面(切記切記) postInvalidate(); //一定是true return true; } /** * 重置介面 */ private void reset() { paintLine.setColor(Color.parseColor("#ff8533")); for (int i = 0; i < points.length; i++) { for (int j = 0; j < points[i].length; j++) { points[i][j].setStatus(Point.STATUS_NORMAL); } } pointList.clear(); } /** * 不符合條件設定為error狀態 */ private void setError() { paintLine.setColor(Color.parseColor("#eeeeee")); for (int i = 0; i < points.length; i++) { for (int j = 0; j < points[i].length; j++) { if(pointList.contains(points[i][j])){ points[i][j].setStatus(Point.STATUS_ERROR); } } } //錯誤後1秒恢復狀態 CountDownTimer countDownTimer = new CountDownTimer(1 * 1000, 1000) { @Override public void onTick(long millisUntilFinished) { } @Override public void onFinish() { reset(); postInvalidate(); } }.start(); } /** * 檢測點是否已經被新增過 * * @param point * @return */ private boolean checkPointAdd(Point point) { for (Point p : pointList) { if (pointList.contains(point)) { return true; } } return false; } /** * 是否選中返回選中的點 * @param downX * @param downY * @return */ private Point checkSelectPoint(int downX, int downY) { for (int i = 0; i < points.length; i++) { for (int j = 0; j < points[i].length; j++) { if (isSelect(downX, downY, points[i][j])) { points[i][j].setStatus(Point.STATUS_PASS); return points[i][j]; } } } return null; } /** * 判斷是否選中點 * * @param downX 手指點下的X座標 * @param downY 手指點下的Y的座標 * @param point 9個點中的一個 * @return true選中了點 */ private boolean isSelect(int downX, int downY, Point point) { if (Math.sqrt((double) ((downX - point.x) * (downX - point.x) + (downY - point.y) * (downY - point.y))) < bitmapR) { return true; } return false; } //初始化各個點,座標根據自己的需求計算 private void initPoints() { //第一排的三個點 points[0][0] = new Point(interval + bitmapR, (int) interval + bitmapR); points[0][0].setCode(0); points[0][1] = new Point((int) (interval * 2 + bitmapR * 3), (int) interval + bitmapR); points[0][1].setCode(1); points[0][2] = new Point((int) (interval * 3 + bitmapR * 5), (int) interval + bitmapR); points[0][2].setCode(2); //第二排的三個點 points[1][0] = new Point((int) interval + bitmapR, (int) (bitmapR * 3 + interval * 2)); points[1][0].setCode(3); points[1][1] = new Point((int) (interval * 2 + bitmapR * 3), (int) (bitmapR * 3 + interval * 2)); points[1][1].setCode(4); points[1][2] = new Point((int) (interval * 3 + bitmapR * 5), (int) (bitmapR * 3 + interval * 2)); points[1][2].setCode(5); //第三排的三個點 points[2][0] = new Point((int) interval + bitmapR, (int) (bitmapR * 5 + interval * 3)); points[2][0].setCode(6); points[2][1] = new Point((int) (interval * 2 + bitmapR * 3), (int) (bitmapR * 5 + interval * 3)); points[2][1].setCode(7); points[2][2] = new Point((int) (interval * 3 + bitmapR * 5), (int) (bitmapR * 5 + interval * 3)); points[2][2].setCode(8); bitmapNormal = BitmapFactory.decodeResource(getResources(), R.drawable.gesture_node_normal); bitmapPass = BitmapFactory.decodeResource(getResources(), R.drawable.img_tab_course_s); bitmapError = BitmapFactory.decodeResource(getResources(), R.drawable.img_tab_course_n); } //自定義一個點的類,這個類有點的三種狀態,正常,錯誤,和選中 class Point { private static final int STATUS_NORMAL = 0; private static final int STATUS_PASS = 1; private static final int STATUS_ERROR = 2; private int status;//狀態,是正常的還是選中的還是錯誤的 private int code;//每個點有個編號,將手勢密碼轉為數字密碼儲存方便 private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } } }

佈局檔案

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.a_0102.mylearn.gesture.GesturePwdActivity">

    <com.example.a_0102.mylearn.gesture.GesturePwdView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/blue"
        android:layout_gravity="center">

    </com.example.a_0102.mylearn.gesture.GesturePwdView>

</LinearLayout>

示例圖這裡寫圖片描述