自定義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>
示例圖