您的位置:首页 > 移动开发 > Android开发

Android 手势解锁详解(包括一次解锁、二次设置密码)

2016-12-20 16:24 751 查看
最近看到手势解锁功能,网上有一些大牛写了很多源码,不过功能或多或少对自己的项目有些不同,琢磨着自己也写一个,技术还不到家,有些东西是参照网上的demo


主要自定义View如下:

package com.example.androidgesture;
//                  _ooOoo_
//                 o8888888o
//                 88" . "88
//                 (| -_- |)
//                 O\  =  /O
//              ____/`---'\____
//            .'  \\|     |//  `.
//           /  \\|||  :  |||//  \
//          /  _||||| -:- |||||-  \
//          |   | \\\  -  /// |   |
//          | \_|  ''\---/''  |   |
//           \  .-\__  `-`  ___/-. /
//         ___`. .'  /--.--\  `. . __
//      ."" '<  `.___\_<|>_/___.'  >'"".
//    | | :  `- \`.;`\ _ /`;.`/ - ` : | |
//    \  \ `-.   \_ __\ /__ _/   .-` /  /
//=====`-.____`-.___\_____/___.-`____.-'======
//                  `=---='
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//         佛祖保佑       永无BUG

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.RelativeLayout;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

/**
* 作者:huangl on 2016-12-02 14:59
* 邮箱:278168565@qq.com
* <p>
* 类说明:
*/
public class GestureCustomView extends RelativeLayout {
private String TAG = GestureCustomView.class.getSimpleName();
private int LINE_NUMBER;   //行数
private int COLUMN_NUMBER; //列数
private GestureRound gestureViews[][];   //二维数组保存圆点总数
private int gestureViewWidth;       //每个圆点的宽度
private int gestureBetweenWidth;    //相邻圆点的距离
private GestureData gestureData;
private Context context;

private Paint paintLine;  //画连接线的画笔
private Path pathLine; //画连接线的画图
private float moveX; //移动时X点坐标
private float moveY; //移动时Y点坐标
private float startX; //连接线起始点X坐标
private float startY; //连接线起始点Y坐标
private List<GestureRound> list; //保存已经选中的圆点
private StringBuffer orderData; //保存选中圆点的顺序
private String fristData;   //保存第一次选择的数据,用来判断第二次选择的数据,是否跟第一次一样
private boolean isRound; //用来判断第一次按下去的时候,是否在某一个圆点内
private int ID_NUMBER = 130059; //用来设置每个圆点的ID,可随便设置,防止在自定义其他View的时候,ID重复
private int ROUND_NUMBER; //每次最少选中圆点个数
private boolean isAgain = true; //是否需要二次选择
private OnResultListener resultListener; //回调接口
private boolean isClick = true; //是否能够点击选择,用于当第一次选择完成后,直线还没消失的时候,不能直接第二次选择

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

public GestureCustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public GestureCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}

public GestureCustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.Gesture_Styles);
gestureData = new GestureData();
gestureData.setLine_Number(attributes.getInt(R.styleable.Gesture_Styles_Gesture_Line_Number, getResources().getInteger(R.integer.Line_Number)));
gestureData.setColumn_Number(attributes.getInt(R.styleable.Gesture_Styles_Gesture_Column_Number, getResources().getInteger(R.integer.Column_Number)));
gestureData.setBorderWidth(attributes.getFloat(R.styleable.Gesture_Styles_Gesture_BorderWidth, Float.parseFloat(getResources().getString(R.string.BorderWidth))));
gestureData.setFilletWidth(attributes.getFloat(R.styleable.Gesture_Styles_Gesture_FilletWidth, Float.parseFloat(getResources().getString(R.string.FilletWidth))));
gestureData.setBetweenWidth(attributes.getFloat(R.styleable.Gesture_Styles_Gesture_BetweenWidth, Float.parseFloat(getResources().getString(R.string.BetweenWidth))));
gestureData.setAgain(attributes.getBoolean(R.styleable.Gesture_Styles_Gesture_isAgain, getResources().getBoolean(R.bool.isAgain)));
gestureData.setRound_Number(attributes.getInt(R.styleable.Gesture_Styles_Gesture_Round_Number, getResources().getInteger(R.integer.Round_Number)));
gestureData.setDelay(attributes.getInt(R.styleable.Gesture_Styles_Gesture_Delay, getResources().getInteger(R.integer.Delay)));
gestureData.setLineWidth(attributes.getFloat(R.styleable.Gesture_Styles_Gesture_LineWidth, Float.parseFloat(getResources().getString(R.string.LineWidth))));
gestureData.setDefaultBorderColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_DefaultBorderColor, getResources().getColor(R.color.DefaultBorderColor)));
gestureData.setDefaultFilletColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_DefaultFilletColor, getResources().getColor(R.color.DefaultFilletColor)));
gestureData.setDefaultExcircleColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_DefaultExcircleColor, getResources().getColor(R.color.DefaultExcircleColor)));
gestureData.setSelectBorderColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_SelectBorderColor, getResources().getColor(R.color.SelectBorderColor)));
gestureData.setSelectFilletColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_SelectFilletColor, getResources().getColor(R.color.SelectFilletColor)));
gestureData.setSelectExcircleColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_SelectExcircleColor, getResources().getColor(R.color.SelectExcircleColor)));
gestureData.setSelectLineColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_SelectLineColor, getResources().getColor(R.color.SelectLineColor)));
gestureData.setErrorBorderColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_ErrorBorderColor, getResources().getColor(R.color.ErrorBorderColor)));
gestureData.setErrorFilletColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_ErrorFilletColor, getResources().getColor(R.color.ErrorFilletColor)));
gestureData.setErrorExcircleColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_ErrorExcircleColor, getResources().getColor(R.color.ErrorExcircleColor)));
gestureData.setErrorLineColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_ErrorLineColor, getResources().getColor(R.color.ErrorLineColor)));
gestureData.setCorrectBorderColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_CorrectBorderColor, getResources().getColor(R.color.CorrectBorderColor)));
gestureData.setCorrectFilletColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_CorrectFilletColor, getResources().getColor(R.color.CorrectFilletColor)));
gestureData.setCorrectExcircleColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_CorrectExcircleColor, getResources().getColor(R.color.CorrectExcircleColor)));
gestureData.setCorrectLineColor(attributes.getColor(R.styleable.Gesture_Styles_Gesture_CorrectLineColor, getResources().getColor(R.color.CorrectLineColor)));
this.context = context;
paintLine = new Paint(Paint.ANTI_ALIAS_FLAG); //初始化锯齿
paintLine.setStyle(Paint.Style.STROKE); //空心
paintLine.setStrokeCap(Paint.Cap.ROUND); //设置笔刷的图形样式,圆形样式
paintLine.setStrokeJoin(Paint.Join.ROUND);//设置绘制时各图形的结合方式,圆形效果
pathLine = new Path();
list = new ArrayList<>();
ROUND_NUMBER = gestureData.getRound_Number();//每次最少选中圆点个数
isAgain = gestureData.isAgain(); //是否需要二次选择
LINE_NUMBER = gestureData.getLine_Number(); //每行圆点数目
COLUMN_NUMBER = gestureData.getColumn_Number();//每列圆点数目
gestureViews = new GestureRound[LINE_NUMBER][COLUMN_NUMBER];
orderData = new StringBuffer();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
measuredWidth = measuredWidth < measuredHeight ? measuredWidth : measuredHeight;

//每个圆点的宽度
gestureViewWidth = (int) (COLUMN_NUMBER * measuredWidth * 1.0f / ((COLUMN_NUMBER + 1) * COLUMN_NUMBER + 1));
//每行之间的距离
gestureBetweenWidth = (int) (gestureViewWidth * gestureData.getBetweenWidth());
//设置直线的宽度
paintLine.setStrokeWidth(gestureViewWidth * gestureData.getLineWidth());

for (int i = 0; i < LINE_NUMBER; i++) {  //每一行
for (int j = 0; j < COLUMN_NUMBER; j++) { //每一列
int ID = i + j + (COLUMN_NUMBER - 1) * i + ID_NUMBER + 1;
GestureRound gestureRound = new GestureRound(context, gestureData);
gestureRound.setId(ID);  //设置每一个圆点的ID,从ID_NUMBER+1开始
LayoutParams params = new LayoutParams(gestureViewWidth, gestureViewWidth);
// 第一列设置LEFT,其他列不设置。
// 第一行设置TOP,其他行不设置。
// 最后一列不设置RIGHT,其他列设置。
// 最后一行不设置BOTTOM,其他行设置。
params.setMargins((COLUMN_NUMBER == 0) ? gestureBetweenWidth : 0,
(LINE_NUMBER == 0) ? gestureBetweenWidth : 0,
(j == (COLUMN_NUMBER - 1)) ? 0 : gestureBetweenWidth, (i == LINE_NUMBER - 1) ? 0 : gestureBetweenWidth);
params.addRule(RIGHT_OF, (j != 0) ? ID - 1 : 0); //第一列不设置,其他圆点都在上一个圆点右侧
params.addRule(BELOW, (i != 0) ? ID - COLUMN_NUMBER : 0); //第一行不设置,其他圆点都在上一行下面
gestureRound.setLayoutParams(params);
gestureViews[i][j] = gestureRound;
addView(gestureRound);
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (isRound) { //若第一次按下去不在圆点内,则不画直线
if (pathLine != null)
canvas.drawPath(pathLine, paintLine);
if (this.moveX != 0 && this.moveY != 0) {   //移动后才开始划线
canvas.drawLine(this.startX, this.startY, this.moveX, this.moveY, paintLine);
}
}
}

@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: //按下
GestureRound ro = getRound(x, y);
if (ro != null && isClick) { //isRound 判断按下去的时候,是否在圆点内
isClick = false;
paintLine.setColor(gestureData.getSelectLineColor());
ro.setState(1); //改变选中圆点的状态
isRound = true;
list.add(ro); //把选中的圆点保存起来
getStartPosition(ro); //获取直线的起始位置的XY值
pathLine.moveTo(this.startX, this.startY); //设置直线的起始位置
orderData.append(ro.getId() - ID_NUMBER); //保存选中圆点的顺序
} else {
isRound = false;
}
break;
case MotionEvent.ACTION_MOVE: //移动
if (isRound == true) { //能否画直线
this.moveX = x;
this.moveY = y;
GestureRound round = getRound(this.moveX, this.moveY);
if (round != null && !list.contains(round)) {  //判断round是否为空以及round是否已经选择过了
round.setState(1); //改变选中圆点的状态
getStartPosition(round);//重新获取直线的起始位置的XY值
list.add(round); //把选中圆点保存起来
orderData.append(round.getId() - ID_NUMBER); //保存选中圆点的顺序
pathLine.lineTo((round.getLeft() + round.getRight()) / 2, (round.getTop() + round.getBottom()) / 2);
}
}
break;
case MotionEvent.ACTION_UP: //放开
if (isRound == true) {
//将最后的直线位置定位到最后一个圆点
this.moveX = this.startX;
this.moveY = this.startY;
if (list.size() >= ROUND_NUMBER) { //选择的圆点个数是否超过最少选中圆点个数
if (isAgain) { //是否需要二次选择
if (fristData == null) { //是否第一次选择圆点
fristData = orderData.toString();
} else {
if (fristData.equals(orderData.toString())) { //第一次选择圆点顺序跟第二次选择圆点顺序是否相同
changeMode(2); //成功
paintLine.setColor(gestureData.getCorrectLineColor());
} else {
changeMode(3); //错误
paintLine.setColor(gestureData.getErrorLineColor());
}
}
}
resultListener.Result(orderData.toString()); //
} else
Toast.makeText(context, "至少连接" + ROUND_NUMBER + "个点,请重试!", Toast.LENGTH_SHORT).show();
orderData.delete(0, orderData.length());
restoreState();//还原状态
}
break;
default:
break;
}
invalidate(); //刷新界面,才能调用dispatchDraw方法
return true;  //返回true 才能不断调用onTouchEvent方法
}

/**
* 改变已选圆点的状态
*/
private void changeMode(int i) {
for (GestureRound round : list) {
round.setState(i);
}

}

/**
* 获取直线的起始位置的XY值
*/
private void getStartPosition(GestureRound round) {
this.startX = (round.getLeft() + round.getRight()) / 2;
this.startY = (round.getTop() + round.getBottom()) / 2;
}

/**
* 还原状态
*/
private void restoreState() {
new Handler().postDelayed(new Runnable() {  //延迟还原状态
public void run() {
pathLine.reset(); //重置笔画
moveX = 0;
moveY = 0;
for (int i = 0; i < COLUMN_NUMBER; i++) { //循环遍历二维数组,先遍历 列 貌似速度快一些
for (int j = 0; j < LINE_NUMBER; j++) {
GestureRound round = gestureViews[j][i];
if (list.contains(round)) { //判断当前的触摸点是否在圆点内
round.setState(0);
}
}
}
list.clear();
invalidate();
isClick = true;
}
}, gestureData.getDelay());
}

/**
* 获取滑动时选择的圆点
*/
private GestureRound getRound(float x, float y) {
for (int i = 0; i < COLUMN_NUMBER; i++) { //循环遍历二维数组,先遍历 列 貌似速度快一些
for (int j = 0; j < LINE_NUMBER; j++) {
if (judgeRound(gestureViews[j][i], x, y)) { //判断当前的触摸点是否在圆点内
return gestureViews[j][i]; //返回圆点
}
}
}
return null;
}

/**
* 判断当前的触摸点是否在圆点内
*/
private boolean judgeRound(GestureRound gestureRound, float x, float y) {
float DIFFER = gestureViewWidth * 0.2f;   //DIFFER是误差点
if (x >= gestureRound.getLeft() + DIFFER && x <= gestureRound.getRight() - DIFFER
&& y >= gestureRound.getTop() + DIFFER && y <= gestureRound.getBottom() - DIFFER)
return true;
else
return false;
}

/**
* 设置是否二次选择
*/
public void setAgain(boolean b) {
this.isAgain = b;
}

public void setOnResultListener(OnResultListener listener) {
this.resultListener = listener;
}

public interface OnResultListener {
void Result(String result);
}
}

像这些功能设置

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Gesture_Styles">
<attr name="Gesture_Line_Number" format="integer"></attr><!--每行圆点的个数-->
<attr name="Gesture_Column_Number" format="integer"></attr><!--每列圆点的个数-->
<attr name="Gesture_BorderWidth" format="float"></attr> <!--边框大小-->
<attr name="Gesture_FilletWidth" format="float"></attr> <!--内圆大小-->
<attr name="Gesture_BetweenWidth" format="float"></attr> <!--每行之间的距离大小-->
<attr name="Gesture_LineWidth" format="float"></attr> <!--直线的宽度大小-->
<attr name="Gesture_Round_Number" format="integer"></attr><!--每次最少选中圆点个数-->
<attr name="Gesture_isAgain" format="boolean"></attr><!--是否需要二次选择-->
<attr name="Gesture_Delay" format="integer"></attr><!--延迟还原状态的时间,单位 秒-->

<attr name="Gesture_DefaultBorderColor" format="color|reference"></attr> <!--默认边框颜色-->
<attr name="Gesture_DefaultFilletColor" format="color|reference"></attr> <!--默认内圆颜色-->
<attr name="Gesture_DefaultExcircleColor" format="color|reference"></attr> <!--默认外圆颜色-->

<attr name="Gesture_SelectBorderColor" format="color|reference"></attr> <!--选择边框颜色-->
<attr name="Gesture_SelectFilletColor" format="color|reference"></attr> <!--选择内圆颜色-->
<attr name="Gesture_SelectExcircleColor" format="color|reference"></attr> <!--选择外圆颜色-->
<attr name="Gesture_SelectLineColor" format="color|reference"></attr> <!--选择线条颜色-->

<attr name="Gesture_ErrorBorderColor" format="color|reference"></attr> <!--错误边框颜色-->
<attr name="Gesture_ErrorFilletColor" format="color|reference"></attr> <!--错误内圆颜色-->
<attr name="Gesture_ErrorExcircleColor" format="color|reference"></attr> <!--错误外圆颜色-->
<attr name="Gesture_ErrorLineColor" format="color|reference"></attr> <!--错误线条颜色-->

<attr name="Gesture_CorrectBorderColor" format="color|reference"></attr> <!--正确边框颜色-->
<attr name="Gesture_CorrectFilletColor" format="color|reference"></attr> <!--正确内圆颜色-->
<attr name="Gesture_CorrectExcircleColor" format="color|reference"></attr> <!--正确外圆颜色-->
<attr name="Gesture_CorrectLineColor" format="color|reference"></attr> <!--正确线条颜色-->

</declare-styleable>
<string name="BorderWidth" format="float" type="dimen">2</string><!--默认边框大小-->
<string name="FilletWidth" format="float" type="dimen">0.3</string><!--默认内圆大小-->
<string name="BetweenWidth" format="float" type="dimen">0.25</string><!--默认每行之间的距离大小-->
<string name="LineWidth" format="float" type="dimen">0.3</string><!--默认直线的宽度大小-->
<bool name="isAgain">true</bool>
<integer name="Round_Number">4</integer><!--默认每次最少选中圆点个数-->
<integer name="Delay">500</integer><!--延迟还原状态的时间,单位 秒-->
<integer name="Line_Number">3</integer><!--每行圆点的个数-->
<integer name="Column_Number">3</integer><!--每列圆点的个数-->
</resources>


全部在XML里面就可以设置,非常方便



感觉没漏下什么了

有缺少的,大家可以加上去,注释已经非常详细了,基本上看一眼就能知道是什么逻辑,什么功能,自己修改也很方便

最后 这是代码下载,已经编译过了,可以直接在build-->outputs里面找到APK先安装看一下效果: 
http://download.csdn.net/detail/yanmantian/9716762
源码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: