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

Android自定义控件实现手势密码,第1张

概述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 colorDefault,int bound,int centerBound,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; } /**   * @Description:改变圆球当前状态  */ public voID changeStatus(int status){  this.status = status; }  /**   * @Description:绘制这个圆形  */ 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,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(); } /**   * @Description:初始化当前密码的状态 */ public voID initStatus(){  if(TextUtils.isEmpty(Util.getPwd(getContext()))){   status = STATUS_NO_PWD;  }else{   status = STATUS_SAVE_PWD;  } }  public int getCurrentStatus(){  return status; } /**   * @Description:初始化参数,若不调用则使用默认值  * @param padding 圆球之间的间距  * @param colorSuccess 密码正确时圆球的颜色  * @param colorFailed 密码错误时圆球的颜色  * @return LockVIEw */ public LockVIEw initParam(int padding,int minPwdNumber){  this.padding = padding;  this.colorSuccess = colorSuccess;  this.colorFailed = colorFailed;  this.minPwdNumber = minPwdNumber;  init();  return this; }  /**   * @Description:若第一次调用则创建圆球,否则更新圆球 */ 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,colorSuccess,colorFailed,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{  /**    * @Description:没有密码时,第一次录入密码触发器  */  voID onTypeInOnce(String input);  /**   * @Description:已经录入第一次密码,录入第二次密码触发器   */  voID onTypeInTwice(String input,boolean isSuccess);  /**    * @Description:验证密码触发器   */  voID onUnLock(String input,boolean isSuccess);    /**   * @Description:密码长度不够   */  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,circleRadius);   }  } }

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

 private voID createCircles(int radius,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,height); }

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

 @OverrIDe protected voID onDraw(Canvas canvas){  init();  //绘制圆  for(int i = 0; i < circles.size() ;i++){   circles.get(i).draw(canvas,mPaint);  } }

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

 public voID setonLockListener(OnLockListener Listener){  this.Listener = Listener; }  public interface OnLockListener{  /**    * @Description:没有密码时,第一次录入密码触发器  */  voID onTypeInOnce(String input);  /**   * @Description:已经录入第一次密码,录入第二次密码触发器   */  voID onTypeInTwice(String input,boolean isSuccess);    /**   * @Description:密码长度不够   */  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(),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(),DURATION);    break;

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

总结

以上是内存溢出为你收集整理的Android自定义控件实现手势密码全部内容,希望文章能够帮你解决Android自定义控件实现手势密码所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1148824.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存