Android手势密码
一、简介
1、本篇博文给大家介绍手势密码的绘制,首先看一下效果,如下:分为设置手势密码与验证手势密码;
二、结构分析:
如上图所示,我们在创建View之前,需将基本思路理清,我大致介绍一下我的理解:
1、首先大家要有一个思路就是,要创建这个View,必须得有3X3一共9个圆,然后就是排列这9个圆;
2、要进行绘制9个圆的话,要获取整个控件的宽高,(在本列中我是直接获取布局控件的宽高,没有限制最小值,这个大家看自己的需求,为了追求更完美可以限制绘制的大小)
3、通过获取的宽高,设置单个圆的半径,已经每个圆的中心点;
如此,可将9个3X3的圆画出,后面绘制部分我将以代码的形式列出;
三、代码分析;
1、控件的参数属性:
private int startX;//手指开始点击位置X private int startY;//手指开始点击位置Y private int endX;//手指滑动点X坐标 private int endY;//手指滑动点Y坐标 private Paint linePaint;//画线笔 private Paint circlePaint;//画圆笔 private int viewWidth;//控件宽度 private int viewHeight;//控件高度 private int radius;//圆半径 private PointF[][] centerCxCy;//各个圆的圆心二维数组 private int[][] data;//对应各个圆的密码数,二维数组 private boolean[][] selected;//对应各个圆是否已选中 private List<PointF> selPointFs;//已选中圆的中心点集合 private String lockPwd = "";//密码字符串 private String password = null; private boolean isPressedDown = false;//是否有手指按下 private int red;//颜色值 private int green;//颜色值 private int blue;//颜色值 private final int flag = 3;//矩阵大小 private boolean isError = false;//在验证密码时,判断输入密码是否错误 private OnGesturePwdListener gesturePwdListener;//自定义回调监听 private Thread errorThread;// 输错手势密码后延时线程
2、构造函数、初始化参数
public GesturePwdView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); }
private void init() { linePaint = new Paint(); linePaint.setStrokeWidth(5);// 画线笔宽度 linePaint.setStyle(Style.FILL);// 画笔样式铺满 linePaint.setAntiAlias(true);// 抗锯齿 circlePaint = new Paint(); circlePaint.setStrokeWidth(ScreenUtil.dip2px(getContext(), 1)); circlePaint.setAntiAlias(true); circlePaint.setStyle(Style.STROKE); // 画笔样式空心 centerCxCy = new PointF[flag][flag]; data = new int[flag][flag]; selected = new boolean[flag][flag]; selPointFs = new ArrayList<PointF>(); initData(); }
// 赋值data数组 private void initData() { // TODO Auto-generated method stub int num = 1; for (int i = 0; i < flag; i++) { for (int j = 0; j < flag; j++) { data[j][i] = num; num++; } } }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (changed) { viewWidth = getWidth(); viewHeight = getHeight(); setRadius(); } super.onLayout(changed, left, top, right, bottom); }
// 设置半径和圆心 private void setRadius() { int w = viewWidth / 3; int h = viewHeight / 3; if (w <= h) { radius = w / 3; } else { radius = h / 3; } for (int i = 0; i < flag; i++) { for (int j = 0; j < flag; j++) { PointF cPointF = new PointF(); cPointF.x = (i * w) + w / 2; cPointF.y = (j * h) + h / 2; centerCxCy[i][j] = cPointF; } } }
三、配置方法(判断点是否在圆内、获取该圆所代表的值、清除选中记录、不齐两圆中间的圆等)
// 还原为初始状态 private void setIsErrorDelayOver() { lockPwd = ""; selPointFs.clear(); clearSelected(); invalidate(); } // 清除选中记录 private void clearSelected() { for (int i = 0; i < flag; i++) { for (int j = 0; j < flag; j++) { selected[i][j] = false; } } } // 判断是否在某个圆内 公式 :根号(x1-x2)(x1-x2)+(y1-y2)(y1-y2) private boolean isInCircle(PointF p, int x, int y) { int distance = (int) Math.sqrt((p.x - x) * (p.x - x) + (p.y - y) * (p.y - y)); if (distance <= radius) { return true; } return false; } // 获取选中的单个点的值 private int getLockSinglePwdData(int x, int y) { for (int i = 0; i < flag; i++) { for (int j = 0; j < flag; j++) { PointF cPointF = centerCxCy[i][j]; if (isInCircle(cPointF, x, y)) { // 判断是否在圆内 if (!selected[i][j]) { selected[i][j] = true; selPointFs.add(cPointF); return data[i][j]; } } } } return 0; }
public String getPassword() { return password; } // 设置原手势密码 public void setPassword(String password) { this.password = password; }// 补充圆,在一个横线内的3个点,其中两边的俩个点选中时,中间的点也默认选中
//大家可根据自己的需求来决定是否使用这个方法 private String setReplenish(String s) { if (!s.contains("5")) { if (s.contains("46")) { selected[1][1] = true; return addString(s, '4', "5"); } else if (s.contains("64")) { selected[1][1] = true; return addString(s, '6', "5"); } else if (s.contains("28")) { selected[1][1] = true; return addString(s, '2', "5"); } else if (s.contains("82")) { selected[1][1] = true; return addString(s, '8', "5"); } else if (s.contains("19")) { selected[1][1] = true; return addString(s, '1', "5"); } else if (s.contains("91")) { selected[1][1] = true; return addString(s, '9', "5"); } else if (s.contains("37")) { selected[1][1] = true; return addString(s, '3', "5"); } else if (s.contains("73")) { selected[1][1] = true; return addString(s, '7', "5"); } } if (!s.contains("2")) { if (s.contains("13")) { selected[1][0] = true; return addString(s, '1', "2"); } else if (s.contains("31")) { selected[1][0] = true; return addString(s, '3', "2"); } } if (!s.contains("8")) { if (s.contains("79")) { selected[1][2] = true; return addString(s, '7', "8"); } else if (s.contains("97")) { selected[1][2] = true; return addString(s, '9', "8"); } } if (!s.contains("4")) { if (s.contains("17")) { selected[0][1] = true; return addString(s, '1', "4"); } else if (s.contains("71")) { selected[0][1] = true; return addString(s, '7', "4"); } } if (!s.contains("6")) { if (s.contains("39")) { selected[2][1] = true; return addString(s, '3', "6"); } else if (s.contains("93")) { selected[2][1] = true; return addString(s, '9', "6"); } } return s; } private String addString(String s, char f, String d) { char[] a = s.toCharArray(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < a.length; i++) { sb.append(a[i]); if (a[i] == f) { sb.append(d); } } return sb.toString(); }四、绘制图形;
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); linePaint.setStrokeWidth(2 * radius / 3); setDraw(red, green, blue, canvas); } // 绘制图形 private void setDraw(int red, int green, int blue, Canvas canvas) { for (int i = 0; i < flag; i++) { for (int j = 0; j < flag; j++) { PointF cPointF = centerCxCy[i][j]; if (selected[i][j]) {// 选中 circlePaint.setColor(Color.rgb(red, green, blue)); linePaint.setColor(Color.argb(255, red, green, blue)); canvas.drawCircle(cPointF.x, cPointF.y, radius / 3, linePaint);// 画实心圆 } else { circlePaint.setColor(Color.argb(180, 255, 255, 255));// 没有选中外圆颜色 } canvas.drawCircle(cPointF.x, cPointF.y, radius, circlePaint);// 画外圆 } } linePaint.setColor(Color.argb(96, red, green, blue));// 画线 if (isPressedDown) { for (int i = 0; i < selPointFs.size() - 1; i++) {// 画已选中圆之间的路径 PointF prePointF = selPointFs.get(i); PointF curPointF = selPointFs.get(i + 1); canvas.drawLine(prePointF.x, prePointF.y, curPointF.x, curPointF.y, linePaint); } if (!isError) { if (selPointFs.size() > 0) { PointF cPointF = selPointFs.get(selPointFs.size() - 1); // 最后一个选中圆中点 canvas.drawLine(cPointF.x, cPointF.y, endX, endY, linePaint); } } } }五、设置监听:
/** * 设置监听 * * @author yang.shen */ public interface OnGesturePwdListener { // 当密码改变 public void onChangePin(GesturePwdView gesturePwdView, String pwd); // 比较密码成功 public void onComparePwdSuccess(GesturePwdView gesturePwdView, String pwd); // 比较密码失败 public void onComparePwdFail(GesturePwdView gesturePwdView, String pwd); } public void setOnGesturePwdListener(OnGesturePwdListener gesturePwdListener) { this.gesturePwdListener = gesturePwdListener; }
六、设置滑动状态
@Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub int pin = 0; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (isError) { errorThread.interrupt(); } endX = (int) event.getX(); endY = (int) event.getY(); lockPwd = ""; selPointFs.clear(); isPressedDown = true; startX = (int) event.getX(); startY = (int) event.getY(); pin = getLockSinglePwdData(startX, startY); red = 167; green = 169; blue = 175; if (pin > 0) { isError = false; lockPwd += pin; invalidate(); } break; case MotionEvent.ACTION_MOVE: endX = (int) event.getX(); endY = (int) event.getY(); red = 167; green = 169; blue = 175; pin = getLockSinglePwdData(endX, endY); if (pin > 0) { isError = false; lockPwd += pin; } lockPwd = setReplenish(lockPwd); invalidate(); break; case MotionEvent.ACTION_UP: endX = (int) event.getX(); endY = (int) event.getY(); isPressedDown = false; if (password == null || "".equals(password)) { red = 167; green = 169; blue = 175; gesturePwdListener.onChangePin(this, lockPwd); clearSelected(); invalidate(); } else if (password.equals(lockPwd)) { red = 167; green = 169; blue = 175; gesturePwdListener.onComparePwdSuccess(this, lockPwd); clearSelected(); invalidate(); } else if (!password.equals(lockPwd)) { isPressedDown = true; isError = true; red = 255; green = 0; blue = 0; invalidate(); gesturePwdListener.onComparePwdFail(this, lockPwd); inputError(); } break; case MotionEvent.ACTION_CANCEL: break; default: break; } return true; }
七、判断密码的错对,显示错误状态3秒
private Handler mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { // TODO Auto-generated method stub setIsErrorDelayOver(); return false; } }); // 输错密码后延时3秒清空状态 private void inputError() { errorThread = new Thread() { @Override public void run() { try { sleep(3000); mHandler.sendEmptyMessage(0); } catch (InterruptedException e) { // TODO Auto-generated catch block mHandler.sendEmptyMessage(0); e.printStackTrace(); } } }; errorThread.start(); }
以上,代报错功能的手势密码就可以使用了,稍后我会将代码上传,谢谢大家