软件编程
位置:首页>> 软件编程>> Android编程>> Android自定义控件实现手势密码

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

作者:cc_lova_wxf  发布时间:2023-12-23 20:45:57 

标签:Android,手势密码,手势解锁

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;
}

/**
 * @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, 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();
}

/**
 * @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 colorSuccess ,int colorFailed ,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, 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{
 /**
  * @Description:没有密码时,第一次录入密码触发器
 */
 void onTypeInOnce(String input);
 /**
  * @Description:已经录入第一次密码,录入第二次密码触发器
  */
 void onTypeInTwice(String input ,boolean isSuccess);
 /**
  * @Description:验证密码触发器
 */
 void onUnLock(String input ,boolean isSuccess);

/**
  * @Description:密码长度不够
  */
 void onError();
}
}

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com