首页 > 系统 > Android > 正文

Android自定义View手势密码

2019-10-21 21:48:33
字体:
来源:转载
供稿:网友

 Android 自定义View 当然是十分重要的,笔者这两天写了一个自定义 View 的手势密码,和大家分享分享:

Android,自定义View,手势密码

首先,我们来创建一个表示点的类,Point.java:

public class Point {  // 点的三种状态 public static final int POINT_STATUS_NORMAL = 0; public static final int POINT_STATUS_CLICK = 1; public static final int POINT_STATUS_ERROR = 2;  // 默认状态 public int state = POINT_STATUS_NORMAL;  // 点的坐标 public float mX; public float mY;  public Point(float x,float y){  this.mX = x;  this.mY = y; }  // 获取两个点的距离 public float getInstance(Point a){  return (float) Math.sqrt((mX-a.mX)*(mX-a.mX)+(mY-a.mY)*(mY-a.mY)); } }

然后我们创建一个 HandleLock.java 继承自 View,并重写其三种构造方法(不重写带两个参数的构造方法会导致程序出错):

首先,我们先把后面需要用的变量写出来,方便大家明白这些变量是干嘛的:

// 三种画笔 private Paint mNormalPaint; private Paint mClickPaint; private Paint mErrorPaint;  // 点的半径 private float mRadius;  // 九个点,使用二维数组 private Point[][] mPoints = new Point[3][3];  // 保存手势划过的点 private ArrayList<Point> mClickPointsList = new ArrayList<Point>(); // 手势的 x 坐标,y 坐标 private float mHandleX; private float mHandleY;  private OnDrawFinishListener mListener;  // 保存滑动路径 private StringBuilder mRoute = new StringBuilder(); // 是否在画错误状态 private boolean isDrawError = false; 接下来我们来初始化数据:// 初始化数据 private void initData() {   // 初始化三种画笔,正常状态为灰色,点下状态为蓝色,错误为红色  mNormalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  mNormalPaint.setColor(Color.parseColor("#ABABAB"));  mClickPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  mClickPaint.setColor(Color.parseColor("#1296db"));  mErrorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  mErrorPaint.setColor(Color.parseColor("#FB0C13"));   // 获取点间隔  float offset = 0;  if (getWidth() > getHeight()) {   // 横屏   offset = getHeight() / 7;   mRadius = offset / 2;   mPoints[0][0] = new Point(getWidth() / 2 - offset * 2, offset + mRadius);   mPoints[0][1] = new Point(getWidth() / 2, offset + mRadius);   mPoints[0][2] = new Point(getWidth() / 2 + offset * 2, offset + mRadius);   mPoints[1][0] = new Point(getWidth() / 2 - offset * 2, offset * 3 + mRadius);   mPoints[1][1] = new Point(getWidth() / 2, offset * 3 + mRadius);   mPoints[1][2] = new Point(getWidth() / 2 + offset * 2, offset * 3 + mRadius);   mPoints[2][0] = new Point(getWidth() / 2 - offset * 2, offset * 5 + mRadius);   mPoints[2][1] = new Point(getWidth() / 2, offset * 5 + mRadius);   mPoints[2][2] = new Point(getWidth() / 2 + offset * 2, offset * 5 + mRadius);  } else {   // 竖屏   offset = getWidth() / 7;   mRadius = offset / 2;   mPoints[0][0] = new Point(offset + mRadius, getHeight() / 2 - 2 * offset);   mPoints[0][1] = new Point(offset * 3 + mRadius, getHeight() / 2 - 2 * offset);   mPoints[0][2] = new Point(offset * 5 + mRadius, getHeight() / 2 - 2 * offset);   mPoints[1][0] = new Point(offset + mRadius, getHeight() / 2);   mPoints[1][1] = new Point(offset * 3 + mRadius, getHeight() / 2);   mPoints[1][2] = new Point(offset * 5 + mRadius, getHeight() / 2);   mPoints[2][0] = new Point(offset + mRadius, getHeight() / 2 + 2 * offset);   mPoints[2][1] = new Point(offset * 3 + mRadius, getHeight() / 2 + 2 * offset);   mPoints[2][2] = new Point(offset * 5 + mRadius, getHeight() / 2 + 2 * offset);  }   }

大家可以看到,我来给点定坐标是,是按照比较窄的边的 1/7 作为点的直径,这样保证了,不管你怎么定义 handleLock 的宽高,都可以使里面的九个点看起来位置很舒服。

接下来我们就需要写一些函数,将点、线绘制到控件上,我自己把绘制分成了三部分,一部分是点,一部分是点与点之间的线,一部分是手势的小点和手势到最新点的线。

// 画点,按照我们选择的半径画九个圆 private void drawPoints(Canvas canvas) {  // 便利所有的点,并且判断这些点的状态  for (int i = 0; i < 3; i++) {   for (int j = 0; j < 3; j++) {    Point point = mPoints[i][j];    switch (point.state) {     case Point.POINT_STATUS_NORMAL:      canvas.drawCircle(point.mX, point.mY, mRadius, mNormalPaint);      break;     case Point.POINT_STATUS_CLICK:      canvas.drawCircle(point.mX, point.mY, mRadius, mClickPaint);      break;     case Point.POINT_STATUS_ERROR:      canvas.drawCircle(point.mX, point.mY, mRadius, mErrorPaint);      break;     default:      break;     }   }  } } // 画点与点之间的线 private void drawLines(Canvas canvas) {  // 判断手势是否已经划过点了  if (mClickPointsList.size() > 0) {   Point prePoint = mClickPointsList.get(0);   // 将所有已选择点的按顺序连线   for (int i = 1; i < mClickPointsList.size(); i++) {    // 判断已选择点的状态    if (prePoint.state == Point.POINT_STATUS_CLICK) {     mClickPaint.setStrokeWidth(7);     canvas.drawLine(prePoint.mX, prePoint.mY, mClickPointsList.get(i).mX, mClickPointsList.get(i).mY, mClickPaint);    }    if (prePoint.state == Point.POINT_STATUS_ERROR) {     mErrorPaint.setStrokeWidth(7);     canvas.drawLine(prePoint.mX, prePoint.mY, mClickPointsList.get(i).mX, mClickPointsList.get(i).mY, mErrorPaint);    }    prePoint = mClickPointsList.get(i);   }   }  } // 画手势点 private void drawFinger(Canvas canvas) {  // 有选择点后再出现手势点  if (mClickPointsList.size() > 0) {   canvas.drawCircle(mHandleX, mHandleY, mRadius / 2, mClickPaint);  }  // 最新点到手指的连线,判断是否有已选择的点,有才能画  if (mClickPointsList.size() > 0) {   canvas.drawLine(mClickPointsList.get(mClickPointsList.size() - 1).mX, mClickPointsList.get(mClickPointsList.size() - 1).mY,     mHandleX, mHandleY, mClickPaint);  } }

上面的代码我们看到需要使用到手势划过的点,我们是怎么选择的呢?

// 获取手指移动中选取的点private int[] getPositions() { Point point = new Point(mHandleX, mHandleY); int[] position = new int[2]; // 遍历九个点,看手势的坐标是否在九个圆内,有则返回这个点的两个下标 for (int i = 0; i < 3; i++) {  for (int j = 0; j < 3; j++) {   if (mPoints[i][j].getInstance(point) <= mRadius) {    position[0] = i;    position[1] = j;    return position;   }  }  } return null;}

我们需要重写其 onTouchEvent 来通过手势动作来提交选择的点,并更新视图:

// 重写点击事件 @Override public boolean onTouchEvent(MotionEvent event) {  // 获取手势的坐标  mHandleX = event.getX();  mHandleY = event.getY();  int[] position;  switch (event.getAction()) {   case MotionEvent.ACTION_DOWN:    position = getPositions();    // 判断点下时是否选择到点    if (position != null) {     // 添加到已选择点中,并改变其状态     mClickPointsList.add(mPoints[position[0]][position[1]]);     mPoints[position[0]][position[1]].state = Point.POINT_STATUS_CLICK;     // 保存路径,依次保存其横纵下标     mRoute.append(position[0]);     mRoute.append(position[1]);    }    break;   case MotionEvent.ACTION_MOVE:    position = getPositions();    // 判断手势移动时是否选择到点    if (position != null) {     // 判断当前选择的点是否已经被选择过     if (!mClickPointsList.contains(mPoints[position[0]][position[1]])) {      // 添加到已选择点中,并改变其状态      mClickPointsList.add(mPoints[position[0]][position[1]]);      mPoints[position[0]][position[1]].state = Point.POINT_STATUS_CLICK;      // 保存路径,依次保存其横纵下标      mRoute.append(position[0]);      mRoute.append(position[1]);     }    }    break;   case MotionEvent.ACTION_UP:    // 重置数据    resetData();    break;   default:    break;  }  // 更新视图  invalidate();   return true; }// 重置数据 private void resetData() {  // 将所有选择过的点的状态改为正常  for (Point point :    mClickPointsList) {   point.state = Point.POINT_STATUS_NORMAL;  }  // 清空已选择点  mClickPointsList.clear();  // 清空保存的路径  mRoute = new StringBuilder();  // 不再画错误状态  isDrawError = false; }

那我们怎么绘制视图呢?我们通过重写其 onDraw() 方法:

@Override protected void onDraw(Canvas canvas) {  super.onDraw(canvas);  // 判断是否画错误状态,画错误状态不需要画手势点已经于最新选择点的连线  if (isDrawError) {   drawPoints(canvas);   drawLines(canvas);  } else {   drawPoints(canvas);   drawLines(canvas);   drawFinger(canvas);  } }

那么这个手势密码绘制过程就结束了,但是整个控件还没有结束,我们还需要给它一个监听器,监听其绘制完成,选择后续事件:

private OnDrawFinishListener mListener;  // 定义绘制完成的接口 public interface OnDrawFinishListener {  public boolean drawFinish(String route); }  // 定义绘制完成的方法,传入接口 public void setOnDrawFinishListener(OnDrawFinishListener listener) {  this.mListener = listener; }

然后我们就需要在手势离开的时候 ,来进行绘制完成时的事件:

case MotionEvent.ACTION_UP:    // 完成时回调绘制完成的方法,返回比对结果,判断手势密码是否正确    mListener.drawFinish(mRoute.toString());    // 返回错误,则将所有已选择点状态改为错误    if (!mListener.drawFinish(mRoute.toString())) {     for (Point point :       mClickPointsList) {      point.state = Point.POINT_STATUS_ERROR;     }     // 将是否绘制错误设为 true     isDrawError = true;     // 刷新视图     invalidate();     // 这里我们使用 handler 异步操作,使其错误状态保持 0.5s     new Thread(new Runnable() {      @Override      public void run() {       if (!mListener.drawFinish(mRoute.toString())) {        Message message = new Message();        message.arg1 = 0;        handler.sendMessage(message);       }      }     }).run();    } else {     resetData();    }    invalidate();     break;private Handler handler = new Handler() {  @Override  public void handleMessage(Message msg) {   switch (msg.arg1) {    case 0:     try {      // 沉睡 0.5s      Thread.sleep(500);     } catch (InterruptedException e) {      e.printStackTrace();     }     // 重置数据,并刷新视图     resetData();     invalidate();     break;    default:     break;   }   } };

 好了,handleLock,整个过程就结束了,笔者这里定义了一个监听器只是给大家提供一种思路,笔者将保存的大路径传给了使用者,是为了保证使用者可以自己保存密码,并作相关操作,大家也可以使用 HandleLock 来  保存密码,不传给使用者,根据自己的需求写出更多更丰富的监听器,而且这里笔者在 MotionEvent.ACTION_UP 中直接回调了 drawFinish() 方法,就意味着要使用该 HandleLock 就必须给它设置监听器。

接下来我们说说 HandleLock 的使用,首先是在布局文件中使用:

<com.example.a01378359.testapp.lock.HandleLock  android:id="@+id/handlelock_test"  android:layout_width="match_parent"  android:layout_height="match_parent" />

接下来是代码中使用:

handleLock = findViewById(R.id.handlelock_test);  handleLock.setOnDrawFinishListener(new HandleLock.OnDrawFinishListener() {   @Override   public boolean drawFinish(String route) {    // 第一次滑动,则保存密码    if (count == 0){     password = route;     count++;     Toast.makeText(LockTestActivity.this,"已保存密码",Toast.LENGTH_SHORT).show();     return true;    }else {     // 与保存密码比较,返回结果,并且做出相应事件     if (password.equals(route)){      Toast.makeText(LockTestActivity.this,"密码正确",Toast.LENGTH_SHORT).show();      return true;     }else {      Toast.makeText(LockTestActivity.this,"密码错误",Toast.LENGTH_SHORT).show();      return false;     }    }   }  });

项目地址:源代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持VEVB武林网。


注:相关教程知识阅读请移步到Android开发频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表