Android 手势解锁详解(包括一次解锁、二次设置密码)
2016-12-20 16:24
751 查看
最近看到手势解锁功能,网上有一些大牛写了很多源码,不过功能或多或少对自己的项目有些不同,琢磨着自己也写一个,技术还不到家,有些东西是参照网上的demo
主要自定义View如下:
像这些功能设置
全部在XML里面就可以设置,非常方便
感觉没漏下什么了
有缺少的,大家可以加上去,注释已经非常详细了,基本上看一眼就能知道是什么逻辑,什么功能,自己修改也很方便
最后 这是代码下载,已经编译过了,可以直接在build-->outputs里面找到APK先安装看一下效果:
http://download.csdn.net/detail/yanmantian/9716762
源码下载
主要自定义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
源码下载
相关文章推荐
- 使用Android UiAutomator解锁手势密码
- 自定义View----Android九宫格手势密码解锁
- Android仿支付宝手势密码解锁功能
- Android手势密码解锁设计
- Android手势密码--设置和校验功能的实现代码
- Android设置手势密码
- pgrep命令_Linux pgrep 命令用法详解:设置用户的认证信息,包括用户密码、密码过期时间等
- 详解Android通过修改配置文件设置wifi密码
- Android自定义View九宫格手势密码解锁
- Android 手势密码开启状态设置
- Android 简易手势密码开源库详解
- 自定义View----Android九宫格手势密码解锁
- Android手势密码解锁
- 中修改密码及访问限制设置详解
- MySQL中修改密码及访问限制设置详解
- Tomcat详解:实现SSL配置,日志配置,登陆用户名与密码设置,路径列表
- MySQL中修改密码及访问限制设置详解
- MySQL中修改密码及访问限制设置详解
- MySQL中修改密码及访问限制设置详解
- MySQL中修改密码及访问限制设置详解