图案解锁应用的越来越广泛,因为好奇所以,查了些资料自己也模拟了一个,有不对的地方,欢迎吐槽。
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>
示例图