iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >Android自定义控件实现手势密码
  • 858
分享到

Android自定义控件实现手势密码

手势Android 2022-06-06 08:06:47 858人浏览 泡泡鱼
摘要

Android手势解锁密码效果图       首先呢想写这个手势密码的想法呢,完全是凭空而来的,然后笔者就花了一天时间弄出来

Android手势解密码效果图 

     首先呢想写这个手势密码的想法呢,完全是凭空而来的,然后笔者就花了一天时间弄出来了。本以为这个东西很简单,实际上手的时候发现,还有很多逻辑需要处理,稍不注意就容易乱套。写个UI效果图大约只花了3个小时,但是处理逻辑就处理了2个小时!废话不多说,下面开始讲解。 
    楼主呢,自己比较自定义控件,什么东西都掌握在自己的手里感觉那是相当不错(对于赶工期的小伙瓣儿们还是别手贱了,非常容易掉坑),一有了这个目标,我就开始构思实现方式。 
    1、整个自定义控件是继承View还是SurfaceView呢?我的经验告诉我:需要一直不断绘制的最好继承SurfaceView,而需要频繁与用户交互的最好就继承View。(求大神来打脸) 
    2、为了实现控件的屏幕适配性,当然必须重写onMeasure方法,然后在onDraw方法中进行绘制。 
    3、面向对象性:这个控件其实由两个对象组成:1、9个圆球;2、圆球之间的连线。 
    4、仔细观察圆球的特征:普通状态是白色、touch状态是蓝色、错误状态是红色、整体分为外围空心圆和内实心圆、所代表的位置信息(密码值) 
    5、仔细观察连线的特征:普通状态为蓝色、错误状态为红色、始终连接两个圆的中心、跟随手指移动而拓展连线、连线之间未点亮的圆球也要点亮。 
    6、通过外露参数来设置圆球的颜色、大小等等 
    7、通过上面的分析,真个控件可模块化为三个任务:onMeasure计算控件宽高以及小球半径、onDraw绘制小球与连线、onTouchEvent控制绘制变化。 

    我把整个源码分为三个类文件:LockView、Circle、Util,其中LockView代表整个控件,Circle代表小圆球、Util封装工具方法(Path因为太简单就没封装,若有代码洁癖请自行封装),下面展示Util类的源代码。 


public class Util{
 private static final String SP_NAME = "LOCKVIEW";
 private static final String SP_KEY = "PASSWord";
 public static void savePwd(Context mContext ,List<Integer> password){
  SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
  sp.edit().putString(SP_KEY, listToString(password)).commit();
 }
 public static String getPwd(Context mContext){
  SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
  return sp.getString(SP_KEY, "");
 }
 public static void clearPwd(Context mContext){
  SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
  sp.edit().remove(SP_KEY).commit();
 }
 public static String listToString(List<Integer> lists){
  StringBuffer sb = new StringBuffer();
  for(int i = 0; i < lists.size(); i++){
   sb.append(lists.get(i));
  }
  return sb.toString();
 }
 public static List<Integer> stringToList(String string){
  List<Integer> lists = new ArrayList<>();
  for(int i = 0; i < string.length(); i++){
   lists.add(Integer.parseInt(string.charAt(i) + ""));
  }
  return lists;
 }
}

     这个工具方法其实很简单,就是对SharedPreferences的一个读写,还有就是List与String类型的互相转换。这里就不描述了。下面展示Circle的源码 


public class Circle{
 //默认值
 public static final int DEFAULT_COLOR = Color.WHITE;
 public static final int DEFAULT_BOUND = 5;
 public static final int DEFAULT_CENTER_BOUND = 15;
 //状态值
 public static final int STATUS_DEFAULT = 0;
 public static final int STATUS_TOUCH = 1;
 public static final int STATUS_SUCCESS = 2;
 public static final int STATUS_FaiLED = 3;
 //圆形的中点X、Y坐标
 private int centerX;
 private int centerY;
 //圆形的颜色值
 private int colorDefault = DEFAULT_COLOR;
 private int colorSuccess;
 private int colorFailed;
 //圆形的宽度
 private int bound = DEFAULT_BOUND;
 //中心的宽度
 private int centerBound = DEFAULT_CENTER_BOUND;
 //圆形的半径
 private int radius;
 //圆形的状态
 private int status = STATUS_DEFAULT;
 //圆形的位置
 private int position;
 public Circle(int centerX, int centerY, int colorSuccess, int colorFailed, int radius, int position){
  super();
  this.centerX = centerX;
  this.centerY = centerY;
  this.colorSuccess = colorSuccess;
  this.colorFailed = colorFailed;
  this.radius = radius;
  this.position = position;
 }
 public Circle(int centerX, int centerY, int colorDefault, int colorSuccess, int colorFailed, int bound,
   int centerBound, int radius, int status, int position){
  super();
  this.centerX = centerX;
  this.centerY = centerY;
  this.colorDefault = colorDefault;
  this.colorSuccess = colorSuccess;
  this.colorFailed = colorFailed;
  this.bound = bound;
  this.centerBound = centerBound;
  this.radius = radius;
  this.status = status;
  this.position = position;
 }
 public int getCenterX(){
  return centerX;
 }
 public void setCenterX(int centerX){
  this.centerX = centerX;
 }
 public int getCenterY(){
  return centerY;
 }
 public void setCenterY(int centerY){
  this.centerY = centerY;
 }
 public int getColorDefault(){
  return colorDefault;
 }
 public void setColorDefault(int colorDefault){
  this.colorDefault = colorDefault;
 }
 public int getColorSuccess(){
  return colorSuccess;
 }
 public void setColorSuccess(int colorSuccess){
  this.colorSuccess = colorSuccess;
 }
 public int getColorFailed(){
  return colorFailed;
 }
 public void setColorFailed(int colorFailed){
  this.colorFailed = colorFailed;
 }
 public int getBound(){
  return bound;
 }
 public void setBound(int bound){
  this.bound = bound;
 }
 public int getCenterBound(){
  return centerBound;
 }
 public void setCenterBound(int centerBound){
  this.centerBound = centerBound;
 }
 public int getRadius(){
  return radius;
 }
 public void setRadius(int radius){
  this.radius = radius;
 }
 public int getStatus(){
  return status;
 }
 public void setStatus(int status){
  this.status = status;
 }
 public int getPosition(){
  return position;
 }
 public void setPosition(int position){
  this.position = position;
 }
 
 public void changeStatus(int status){
  this.status = status;
 }
 
 public void draw(canvas canvas ,Paint paint){
  switch(status){
   case STATUS_DEFAULT:
    paint.setColor(colorDefault);
    break;
   case STATUS_TOUCH:
   case STATUS_SUCCESS:
    paint.setColor(colorSuccess);
    break;
   case STATUS_FAILED:
    paint.setColor(colorFailed);
    break;
   default:
    paint.setColor(colorDefault);
    break;
  }
  paint.setStyle(Paint.Style.FILL);
  //绘制中心实心圆
  canvas.drawCircle(centerX, centerY, centerBound, paint);
  //绘制空心圆
  paint.setStyle(Paint.Style.STROKE);
  paint.setStrokeWidth(bound);
  canvas.drawCircle(centerX, centerY, radius, paint);
 }
}

     这个Circle其实也非常简单。上面定义的成员变量一眼便明,并且有注释。重点在最后的draw方法,首先呢根据当前圆球的不同状态设置不同的颜色值,然后绘制中心的实心圆,再绘制外围的空心圆。所有的参数要么是外界传递,要么是默认值。(ps:面向对象真的非常有用,解耦良好的代码写起来也舒服看起来也舒服)。 

    最后的重点来了,LockView的源码,首先贴源码,然后再针对性讲解。 


public class LockView extends View{
 private static final int COUNT_PER_RAW = 3;
 private static final int DURATION = 1500;
 private static final int MIN_PWD_NUMBER = 6;
 //@Fields STATUS_NO_PWD : 当前没有保存密码
 public static final int STATUS_NO_PWD = 0;
 //@Fields STATUS_RETRY_PWD : 需要再输入一次密码
 public static final int STATUS_RETRY_PWD = 1;
 //@Fields STATUS_SAVE_PWD : 成功保存密码
 public static final int STATUS_SAVE_PWD = 2;
 //@Fields STATUS_SUCCESS_PWD : 成功验证密码
 public static final int STATUS_SUCCESS_PWD = 3;
 //@Fields STATUS_FAILED_PWD : 验证密码失败
 public static final int STATUS_FAILED_PWD = 4;
 //@Fields STATUS_ERROR : 输入密码长度不够
 public static final int STATUS_ERROR = 5;
 private int width;
 private int height;
 private int padding = 0;
 private int colorSuccess = Color.BLUE;
 private int colorFailed = Color.RED;
 private int minPwdNumber = MIN_PWD_NUMBER;
 private List<Circle> circles = new ArrayList<>();
 private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 private Path mPath = new Path();
 private Path backupsPath = new Path();
 private List<Integer> result = new ArrayList<>();
 private int status = STATUS_NO_PWD;
 private OnLockListener listener;
 private Handler handler = new Handler();
 public LockView(Context context, AttributeSet attrs, int defStyle){
  super(context, attrs, defStyle);
  initStatus();
 }
 public LockView(Context context, AttributeSet attrs){
  super(context, attrs);
  initStatus();
 }
 public LockView(Context context){
  super(context);
  initStatus();
 }
 
 public void initStatus(){
  if(TextUtils.isEmpty(Util.getPwd(getContext()))){
   status = STATUS_NO_PWD;
  }else{
   status = STATUS_SAVE_PWD;
  }
 }
 public int getCurrentStatus(){
  return status;
 }
 
 public LockView initParam(int padding ,int colorSuccess ,int colorFailed ,int minPwdNumber){
  this.padding = padding;
  this.colorSuccess = colorSuccess;
  this.colorFailed = colorFailed;
  this.minPwdNumber = minPwdNumber;
  init();
  return this;
 }
 
 private void init(){
  int circleRadius = (width - (COUNT_PER_RAW + 1) * padding) / COUNT_PER_RAW /2;
  if(circles.size() == 0){   
   for(int i = 0; i < COUNT_PER_RAW * COUNT_PER_RAW; i++){
    createCircles(circleRadius, i);
   }
  }else{
   for(int i = 0; i < COUNT_PER_RAW * COUNT_PER_RAW; i++){
    updateCircles(circles.get(i), circleRadius);
   }
  }
 }
 private void createCircles(int radius, int position){
  int centerX = (position % 3 + 1) * padding + (position % 3 * 2 + 1) * radius;
  int centerY = (position / 3 + 1) * padding + (position / 3 * 2 + 1) * radius;
  Circle circle = new Circle(centerX, centerY, colorSuccess, colorFailed, radius, position);
  circles.add(circle);
 }
 private void updateCircles(Circle circle ,int radius){
  int centerX = (circle.getPosition() % 3 + 1) * padding + (circle.getPosition() % 3 * 2 + 1) * radius;
  int centerY = (circle.getPosition() / 3 + 1) * padding + (circle.getPosition() / 3 * 2 + 1) * radius;
  circle.setCenterX(centerX);
  circle.setCenterY(centerY);
  circle.setRadius(radius);
  circle.setColorSuccess(colorSuccess);
  circle.setColorFailed(colorFailed);
 }
 @Override
 protected void onDraw(Canvas canvas){
  init();
  //绘制圆
  for(int i = 0; i < circles.size() ;i++){
   circles.get(i).draw(canvas, mPaint);
  }
  if(result.size() != 0){   
   //绘制Path
   Circle temp = circles.get(result.get(0));
   mPaint.setColor(temp.getStatus() == Circle.STATUS_FAILED ? colorFailed : colorSuccess);
   mPaint.setStrokeWidth(Circle.DEFAULT_CENTER_BOUND);
   canvas.drawPath(mPath, mPaint);
  }
 }
 @Override
 public boolean onTouchEvent(MotionEvent event){
  switch(event.getAction()){
   case MotionEvent.ACTION_DOWN:
    backupsPath.reset();
    for(int i = 0; i < circles.size() ;i++){
     Circle circle = circles.get(i);
     if(event.getX() >= circle.getCenterX() - circle.getRadius()
       && event.getX() <= circle.getCenterX() + circle.getRadius()
       && event.getY() >= circle.getCenterY() - circle.getRadius()
       && event.getY() <= circle.getCenterY() + circle.getRadius()){
      circle.setStatus(Circle.STATUS_TOUCH);
      //将这个点放入Path
      backupsPath.moveTo(circle.getCenterX(), circle.getCenterY());
      //放入结果
      result.add(circle.getPosition());
      break;
     }
    }
    invalidate();
    return true;
   case MotionEvent.ACTION_MOVE:
    for(int i = 0; i < circles.size() ;i++){
     Circle circle = circles.get(i);
     if(event.getX() >= circle.getCenterX() - circle.getRadius()
       && event.getX() <= circle.getCenterX() + circle.getRadius()
       && event.getY() >= circle.getCenterY() - circle.getRadius()
       && event.getY() <= circle.getCenterY() + circle.getRadius()){
      if(!result.contains(circle.getPosition())){       
       circle.setStatus(Circle.STATUS_TOUCH);
       //首先判断是否连线中间也有满足条件的圆
       Circle lastCircle = circles.get(result.get(result.size() - 1));
       int cx = (lastCircle.getCenterX() + circle.getCenterX()) / 2;
       int cy = (lastCircle.getCenterY() + circle.getCenterY()) / 2;
       for(int j = 0; j < circles.size(); j++){
        Circle tempCircle = circles.get(j);
        if(cx >= tempCircle.getCenterX() - tempCircle.getRadius()
          && cx <= tempCircle.getCenterX() + tempCircle.getRadius()
          && cy >= tempCircle.getCenterY() - tempCircle.getRadius()
          && cy <= tempCircle.getCenterY() + tempCircle.getRadius()){
         //处理满足条件的圆
         backupsPath.lineTo(tempCircle.getCenterX(), tempCircle.getCenterY());
         //放入结果
         tempCircle.setStatus(Circle.STATUS_TOUCH);
         result.add(tempCircle.getPosition());
        }
       }
       //处理现在的圆
       backupsPath.lineTo(circle.getCenterX(), circle.getCenterY());
       //放入结果
       circle.setStatus(Circle.STATUS_TOUCH);
       result.add(circle.getPosition());
       break;
      }
     }
    }
    mPath.reset();
    mPath.addPath(backupsPath);
    mPath.lineTo(event.getX(), event.getY());
    invalidate();
    break;
   case MotionEvent.ACTION_UP:
    mPath.reset();
    mPath.addPath(backupsPath);
    invalidate();
    if(result.size() < minPwdNumber){
     if(listener != null){      
      listener.onError();
     }
     if(status == STATUS_RETRY_PWD){
      Util.clearPwd(getContext());
     }
     status = STATUS_ERROR;
     for(int i = 0; i < result.size(); i++){
      circles.get(result.get(i)).setStatus(Circle.STATUS_FAILED);
     }
    }else{
     if(status == STATUS_NO_PWD){ //当前没有密码
      //保存密码,重新录入
      Util.savePwd(getContext(), result);
      status = STATUS_RETRY_PWD;
      if(listener != null){
       listener.onTypeInOnce(Util.listToString(result));
      }
     }else if(status == STATUS_RETRY_PWD){ //需要重新绘制密码
      //判断两次输入是否相等
      if(Util.getPwd(getContext()).equals(Util.listToString(result))){
       status = STATUS_SAVE_PWD;
       if(listener != null){
        listener.onTypeInTwice(Util.listToString(result), true);
       }
       for(int i = 0; i < result.size(); i++){
        circles.get(result.get(i)).setStatus(Circle.STATUS_SUCCESS);
       }
      }else{
       status = STATUS_NO_PWD;
       Util.clearPwd(getContext());
       if(listener != null){
        listener.onTypeInTwice(Util.listToString(result), false);
       }
       for(int i = 0; i < result.size(); i++){
        circles.get(result.get(i)).setStatus(Circle.STATUS_FAILED);
       }
      }
     }else if(status == STATUS_SAVE_PWD){ //验证密码
      //判断密码是否正确
      if(Util.getPwd(getContext()).equals(Util.listToString(result))){
       status = STATUS_SUCCESS_PWD;
       if(listener != null){
        listener.onUnLock(Util.listToString(result), true);
       }
       for(int i = 0; i < result.size(); i++){
        circles.get(result.get(i)).setStatus(Circle.STATUS_SUCCESS);
       }
      }else{
       status = STATUS_FAILED_PWD;
       if(listener != null){
        listener.onUnLock(Util.listToString(result), false);
       }
       for(int i = 0; i < result.size(); i++){
        circles.get(result.get(i)).setStatus(Circle.STATUS_FAILED);
       }
      }
     }
    }
    invalidate();
    handler.postDelayed(new Runnable(){
     @Override
     public void run(){
      result.clear();
      mPath.reset();
      backupsPath.reset();
     //  initStatus();
      // 重置下状态
      if(status == STATUS_SUCCESS_PWD || status == STATUS_FAILED_PWD){
       status = STATUS_SAVE_PWD;
      }else if(status == STATUS_ERROR){
       initStatus();
      }
      for(int i = 0; i < circles.size(); i++){
       circles.get(i).setStatus(Circle.STATUS_DEFAULT);
      }
      invalidate();
     }
    }, DURATION);
    break;
   default:
    break;
  }
  return super.onTouchEvent(event);
 }
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
  width = MeasureSpec.getSize(widthMeasureSpec);
  height = width - getPaddingLeft() - getPaddingRight() + getPaddingTop() + getPaddingBottom();
  setMeasuredDimension(width, height);
 }
 public void setOnLockListener(OnLockListener listener){
  this.listener = listener;
 }
 public interface OnLockListener{
  
  void onTypeInOnce(String input);
  
  void onTypeInTwice(String input ,boolean isSuccess);
  
  void onUnLock(String input ,boolean isSuccess);
  
  void onError();
 }
}

好了,逐次讲解。 

 首先是对status的初始化,其实在static域我已经申明了6个状态,分别是: 


 //当前没有保存密码
 public static final int STATUS_NO_PWD = 0;
 //需要再输入一次密码
 public static final int STATUS_RETRY_PWD = 1;
 //成功保存密码
 public static final int STATUS_SAVE_PWD = 2;
 //成功验证密码
 public static final int STATUS_SUCCESS_PWD = 3;
 //验证密码失败
 public static final int STATUS_FAILED_PWD = 4;
 //输入密码长度不够
 public static final int STATUS_ERROR = 5; 

 在刚初始化的时候,就初始化当前的状态,初始化状态就只有2个状态:有密码、无密码。 


 public void initStatus(){
  if(TextUtils.isEmpty(Util.getPwd(getContext()))){
   status = STATUS_NO_PWD;
  }else{
   status = STATUS_SAVE_PWD;
  }
 }
 public int getCurrentStatus(){
  return status;
 }

     然后就是通过外界的设置初始化一些参数(若不调用initParam方法,则采用默认值): 


 public LockView initParam(int padding ,int colorSuccess ,int colorFailed ,int minPwdNumber){
  this.padding = padding;
  this.colorSuccess = colorSuccess;
  this.colorFailed = colorFailed;
  this.minPwdNumber = minPwdNumber;
  init();
  return this;
 }
 
 private void init(){
  int circleRadius = (width - (COUNT_PER_RAW + 1) * padding) / COUNT_PER_RAW /2;
  if(circles.size() == 0){   
   for(int i = 0; i < COUNT_PER_RAW * COUNT_PER_RAW; i++){
    createCircles(circleRadius, i);
   }
  }else{
   for(int i = 0; i < COUNT_PER_RAW * COUNT_PER_RAW; i++){
    updateCircles(circles.get(i), circleRadius);
   }
  }
 }

上述代码主要根据设置的padding值,计算出小球的大小,然后判断是否是初始化小球,还是更新小球。 


 private void createCircles(int radius, int position){
  int centerX = (position % 3 + 1) * padding + (position % 3 * 2 + 1) * radius;
  int centerY = (position / 3 + 1) * padding + (position / 3 * 2 + 1) * radius;
  Circle circle = new Circle(centerX, centerY, colorSuccess, colorFailed, radius, position);
  circles.add(circle);
 }
 private void updateCircles(Circle circle ,int radius){
  int centerX = (circle.getPosition() % 3 + 1) * padding + (circle.getPosition() % 3 * 2 + 1) * radius;
  int centerY = (circle.getPosition() / 3 + 1) * padding + (circle.getPosition() / 3 * 2 + 1) * radius;
  circle.setCenterX(centerX);
  circle.setCenterY(centerY);
  circle.setRadius(radius);
  circle.setColorSuccess(colorSuccess);
  circle.setColorFailed(colorFailed);
 }

别忘了上面的方法依赖一个width值,这个值是在onMeasure中计算出来的 


 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
  width = MeasureSpec.getSize(widthMeasureSpec);
  height = width - getPaddingLeft() - getPaddingRight() + getPaddingTop() + getPaddingBottom();
  setMeasuredDimension(width, height);
 }

然后就是绘制方法了,因为我们的高度解耦性,本应该非常复杂的onDraw方法,却如此简单。就只绘制了小球和路径。 


 @Override
 protected void onDraw(Canvas canvas){
  init();
  //绘制圆
  for(int i = 0; i < circles.size() ;i++){
   circles.get(i).draw(canvas, mPaint);
  }
  if(result.size() != 0){   
   //绘制Path
   Circle temp = circles.get(result.get(0));
   mPaint.setColor(temp.getStatus() == Circle.STATUS_FAILED ? colorFailed : colorSuccess);
   mPaint.setStrokeWidth(Circle.DEFAULT_CENTER_BOUND);
   canvas.drawPath(mPath, mPaint);
  }
 }

控件是需要和外界进行交互的,我喜欢的方法就是自定义监听器,然后接口回调。 


 public void setOnLockListener(OnLockListener listener){
  this.listener = listener;
 }
 public interface OnLockListener{
  
  void onTypeInOnce(String input);
  
  void onTypeInTwice(String input ,boolean isSuccess);
  
  void onUnLock(String input ,boolean isSuccess);
  
  void onError();
 }

最后最最最重要的一个部分来了,onTouchEvent方法,这个方法其实也可以分为三个部分讲解:down事件、move事件和up事件。首先贴出down事件代码 


 case MotionEvent.ACTION_DOWN:
    backupsPath.reset();
    for(int i = 0; i < circles.size() ;i++){
     Circle circle = circles.get(i);
     if(event.getX() >= circle.getCenterX() - circle.getRadius()
       && event.getX() <= circle.getCenterX() + circle.getRadius()
       && event.getY() >= circle.getCenterY() - circle.getRadius()
       && event.getY() <= circle.getCenterY() + circle.getRadius()){
      circle.setStatus(Circle.STATUS_TOUCH);
      //将这个点放入Path
      backupsPath.moveTo(circle.getCenterX(), circle.getCenterY());
      //放入结果
      result.add(circle.getPosition());
      break;
     }
    }
    invalidate();
    return true;

也就是对按下的x、y坐标进行判断,是否属于我们的小球范围内,若属于,则放入路径集合、更改状态、加入密码结果集。这里别忘了return true,大家都知道吧。 
然后是move事件,move事件主要做三件事情:变更小球的状态、添加到路径集合、对路径覆盖的未点亮小球进行点亮。代码有详细注释就不过多讲解了。 


case MotionEvent.ACTION_MOVE:
    for(int i = 0; i < circles.size() ;i++){
     Circle circle = circles.get(i);
     if(event.getX() >= circle.getCenterX() - circle.getRadius()
       && event.getX() <= circle.getCenterX() + circle.getRadius()
       && event.getY() >= circle.getCenterY() - circle.getRadius()
       && event.getY() <= circle.getCenterY() + circle.getRadius()){
      if(!result.contains(circle.getPosition())){       
       circle.setStatus(Circle.STATUS_TOUCH);
       //首先判断是否连线中间也有满足条件的圆
       Circle lastCircle = circles.get(result.get(result.size() - 1));
       int cx = (lastCircle.getCenterX() + circle.getCenterX()) / 2;
       int cy = (lastCircle.getCenterY() + circle.getCenterY()) / 2;
       for(int j = 0; j < circles.size(); j++){
        Circle tempCircle = circles.get(j);
        if(cx >= tempCircle.getCenterX() - tempCircle.getRadius()
          && cx <= tempCircle.getCenterX() + tempCircle.getRadius()
          && cy >= tempCircle.getCenterY() - tempCircle.getRadius()
          && cy <= tempCircle.getCenterY() + tempCircle.getRadius()){
         //处理满足条件的圆
         backupsPath.lineTo(tempCircle.getCenterX(), tempCircle.getCenterY());
         //放入结果
         tempCircle.setStatus(Circle.STATUS_TOUCH);
         result.add(tempCircle.getPosition());
        }
       }
       //处理现在的圆
       backupsPath.lineTo(circle.getCenterX(), circle.getCenterY());
       //放入结果
       circle.setStatus(Circle.STATUS_TOUCH);
       result.add(circle.getPosition());
       break;
      }
     }
    }
    mPath.reset();
    mPath.addPath(backupsPath);
    mPath.lineTo(event.getX(), event.getY());
    invalidate();
    break;

这里我用了两个Path对象,backupsPath用于只存放小球的中点坐标,mPath不仅要存储小球的中点坐标,还要存储当前手指触碰坐标,为了实现连线跟随手指运动的效果。 
最后是up事件,这里有太多复杂的状态转换,我估计文字讲解是描述不清的,大家还是看源代码吧。           


 case MotionEvent.ACTION_UP:
    mPath.reset();
    mPath.addPath(backupsPath);
    invalidate();
    if(result.size() < minPwdNumber){
     if(listener != null){      
      listener.onError();
     }
     if(status == STATUS_RETRY_PWD){
      Util.clearPwd(getContext());
     }
     status = STATUS_ERROR;
     for(int i = 0; i < result.size(); i++){
      circles.get(result.get(i)).setStatus(Circle.STATUS_FAILED);
     }
    }else{
     if(status == STATUS_NO_PWD){ //当前没有密码
      //保存密码,重新录入
      Util.savePwd(getContext(), result);
      status = STATUS_RETRY_PWD;
      if(listener != null){
       listener.onTypeInOnce(Util.listToString(result));
      }
     }else if(status == STATUS_RETRY_PWD){ //需要重新绘制密码
      //判断两次输入是否相等
      if(Util.getPwd(getContext()).equals(Util.listToString(result))){
       status = STATUS_SAVE_PWD;
       if(listener != null){
        listener.onTypeInTwice(Util.listToString(result), true);
       }
       for(int i = 0; i < result.size(); i++){
        circles.get(result.get(i)).setStatus(Circle.STATUS_SUCCESS);
       }
      }else{
       status = STATUS_NO_PWD;
       Util.clearPwd(getContext());
       if(listener != null){
        listener.onTypeInTwice(Util.listToString(result), false);
       }
       for(int i = 0; i < result.size(); i++){
        circles.get(result.get(i)).setStatus(Circle.STATUS_FAILED);
       }
      }
     }else if(status == STATUS_SAVE_PWD){ //验证密码
      //判断密码是否正确
      if(Util.getPwd(getContext()).equals(Util.listToString(result))){
       status = STATUS_SUCCESS_PWD;
       if(listener != null){
        listener.onUnLock(Util.listToString(result), true);
       }
       for(int i = 0; i < result.size(); i++){
        circles.get(result.get(i)).setStatus(Circle.STATUS_SUCCESS);
       }
      }else{
       status = STATUS_FAILED_PWD;
       if(listener != null){
        listener.onUnLock(Util.listToString(result), false);
       }
       for(int i = 0; i < result.size(); i++){
        circles.get(result.get(i)).setStatus(Circle.STATUS_FAILED);
       }
      }
     }
    }
    invalidate();
    handler.postDelayed(new Runnable(){
     @Override
     public void run(){
      result.clear();
      mPath.reset();
      backupsPath.reset();
     //  initStatus();
      // 重置下状态
      if(status == STATUS_SUCCESS_PWD || status == STATUS_FAILED_PWD){
       status = STATUS_SAVE_PWD;
      }else if(status == STATUS_ERROR){
       initStatus();
      }
      for(int i = 0; i < circles.size(); i++){
       circles.get(i).setStatus(Circle.STATUS_DEFAULT);
      }
      invalidate();
     }
    }, DURATION);
    break;
您可能感兴趣的文章:Android手势密码的实现Android 简易手势密码开源库详解Android自定义UI手势密码终结版Android仿支付宝手势密码解锁功能Android手势密码实现实例代码Android九宫格手势密码代码设计Android实现手势密码功能Android自定义UI手势密码改进版源码下载Android自定义UI手势密码简单版纯android代码实现九宫格手势密码


--结束END--

本文标题: Android自定义控件实现手势密码

本文链接: https://www.lsjlt.com/news/24537.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Android自定义实现日历控件
    本文实例为大家分享了Android自定义实现日历控件的具体代码,供大家参考,具体内容如下 1. Calendar类 2. 布局 创建calendar_layout.xml <...
    99+
    2024-04-02
  • Android自定义控件实现时间轴
    本文实例为大家分享了Android自定义控件实现时间轴的具体代码,供大家参考,具体内容如下 由于项目中有需求,就简单的封装一个,先记录一下,有时间上传到github。 1、先增加自定...
    99+
    2024-04-02
  • Android 实现自定义折线图控件
    目录前言概述原点计算Y轴宽度计算X轴高度X轴绘制轴线X轴刻度间隔网格线、文本Y轴计算Y轴分布刻度间隔、网格线、文本折线代码前言 日前,有一个“折现图”的需求,...
    99+
    2024-04-02
  • Android如何自定义实现日历控件
    这篇文章主要介绍Android如何自定义实现日历控件,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!具体内容如下1. Calendar类2. 布局创建calendar_layout.xml<LinearLayou...
    99+
    2023-06-25
  • Android怎么实现自定义密码输入框
    本篇内容主要讲解“Android怎么实现自定义密码输入框”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android怎么实现自定义密码输入框”吧!一、实现效果及方案预期效果图:如上图所示,要实现...
    99+
    2023-06-25
  • Android自定义控件实现雷达图效果
    本文实例为大家分享了Android自定义控件实现雷达图的具体代码,供大家参考,具体内容如下 学习了大神的源代码(奈何不知大神的博客地址),觉得必须记录一下,方便以后再次学习。 效果如...
    99+
    2024-04-02
  • Android实现一个倒计时自定义控件
    目录(一)前言(二)效果展示(三)实现思路(三)代码地址总结(一)前言 Android 其实提供了一个倒计时控件叫做CountDownTimer,这个倒计时控件用起来也很简单,但是要...
    99+
    2024-04-02
  • Android怎么实现自定义折线图控件
    这篇“Android怎么实现自定义折线图控件”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Android怎么实现自定义折线图...
    99+
    2023-07-02
  • Android自定义控件实现九宫格解锁
    关于九宫格解锁,我看了不少博客,但是都感觉很复杂,可能我的功夫还不到,所以很多东西我不了解,但是我还是打算写一个自己的九宫格。我相信我的九宫格大家都能很快的理解,当然如果需要实现更复...
    99+
    2024-04-02
  • Android实现九宫格手势密码
    本文实例为大家分享了Android实现九宫格手势密码的具体代码,供大家参考,具体内容如下 介绍下自己编写的九宫格手势密码。先见图 思路:首先是9个格子,接着是格子连线;那么我们的步...
    99+
    2024-04-02
  • android自定义控件实现简易时间轴(2)
    这篇做了一个简单的时间轴控件。右侧的数据就是一个简单的字符串。问题还是有的,当右侧的文字长度不一样的时候就会有问题了。现在可以修改一下适配右侧的文字。 效果如下: 代码: priv...
    99+
    2024-04-02
  • android自定义控件实现简易时间轴(1)
    本文实例为大家分享了android自定义控件实现简易时间轴的具体代码,供大家参考,具体内容如下 之前项目需要写一个消费记录,类似于时间轴似的控件,自身在自定义控件这里不咋地(&hel...
    99+
    2024-04-02
  • 基于Android自定义控件实现雷达效果
    如何制作出类似雷达扫描的效果,具体方法如下一、效果图二、实现思路 自定义控件RadarView用来画雷达的效果图,可以自定义属性包括 backgroundColor:背景颜色 circleNum:圆的数量 startColor:开始颜色 e...
    99+
    2023-05-30
    android 雷达 roi
  • 怎么在Android中实现一个自定义控件
    今天就跟大家聊聊有关怎么在Android中实现一个自定义控件,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。首先定义一个layout实现按钮内部布局:<xml vers...
    99+
    2023-05-31
    android
  • WPF自定义控件的实现
    方式一:基于现有控件进行扩展,如基于button进行扩展,UI可直接用xmal进行编辑设计,逻辑用xaml.cs进行编辑 方法二:直接创建wpf自定义控件 本文用方法二开展自定义...
    99+
    2023-03-03
    WPF自定义控件
  • android 自定义控件 使用declare
    在Android中,可以使用`declare-styleable`来定义和使用自定义控件的属性。下面是一个简单的示例:1. 在res...
    99+
    2023-09-21
    Android
  • Android运动健康睡眠自定义控件的实现
    目录效果图代码效果图 代码 class SleepDayChart(context: Context, attrs: AttributeSet?) : View(co...
    99+
    2024-04-02
  • Android自定义view实现滚动选择控件详解
    目录前言需求编写代码主要问题前言 上篇文章通过一个有header和footer的滚动控件(Viewgroup)学了下MeasureSpec、onMeasure以及onLayout,接...
    99+
    2022-11-13
    Android滚动选择 Android滚动选择控件 Android自定义view
  • android自定义控件如何实现简易时间轴
    这篇“android自定义控件如何实现简易时间轴”除了程序员外大部分人都不太理解,今天小编为了让大家更加理解“android自定义控件如何实现简易时间轴”,给大家总结了以下内容,具有一定借鉴价值,内容详细步骤清晰,细节处理妥当,希望大家通过...
    99+
    2023-06-28
  • Android开发自定义实时图表控件实现示例
    目录概述演示环境实现第一步:新建项目RealTimeChartDemo第二步:新建RealTimeChart类第三步:添加自定义变量第四步:初始化基础参数第五步:初始化宽高等参数第六...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作