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(); } }
好了,逐次讲解。
首先是对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; } /** * @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); } } }
上述代码主要根据设置的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{ /** * @Description:没有密码时,第一次录入密码触发器 */ void onTypeInOnce(String input); /** * @Description:已经录入第一次密码,录入第二次密码触发器 */ void onTypeInTwice(String input ,boolean isSuccess); /** * @Description:验证密码触发器 */ void onUnLock(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(), 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自定义View实现随手势滑动控件,包括了Android自定义View实现随手势滑动控件的使用技巧和注意事项,需要的朋友参考一下 本文控件为大家分享了Android随手势滑动控件的具体代码,供大家参考,具体内容如下 1.新建自定义控件类:MyView 上面代码就是一个自定义按钮类,重写onTouchEvent()方法来监听用户滑动,既然说到滑动肯定会存在偏移量的说法。 t
本文向大家介绍Android自定义GestureDetector实现手势ImageView,包括了Android自定义GestureDetector实现手势ImageView的使用技巧和注意事项,需要的朋友参考一下 不说废话了,进入我们今天的主题吧。 先贴上前面内容的地址: Android手势ImageView三部曲(一) Android手势ImageView三部曲(二) Android手势Ima
本文向大家介绍Android实现自定义手势和识别手势的功能,包括了Android实现自定义手势和识别手势的功能的使用技巧和注意事项,需要的朋友参考一下 1. 先完成自定义手势的Activity 1.1 因为需要存储手势文件所以需要声明权限: 1.2 简单写一个布局文件,其中用到了GestureOverlayView,相当于一个绘制组件。其中有一个重要属性gestureStrokeType,值为si
本文向大家介绍Android自定义UI手势密码简单版,包括了Android自定义UI手势密码简单版的使用技巧和注意事项,需要的朋友参考一下 先看看效果图: ImageLockActivity NinePointLineView 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐喊教程。
本文向大家介绍Android自定义UI手势密码改进版,包括了Android自定义UI手势密码改进版的使用技巧和注意事项,需要的朋友参考一下 接着第一个Android UI手势密码设计的基础上继续改进,效果图如下 activity_main.xml MainActivity LockPatterView 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐喊教程。
本文向大家介绍Android自定义UI手势密码终结版,包括了Android自定义UI手势密码终结版的使用技巧和注意事项,需要的朋友参考一下 之前写过3篇手势密码的demo,不过没有集成到真实的企业项目中,这几天正好领到一个手势密码项目,昨天刚好弄完,今天抽空整理下,目前还没有完善,有一些地方需要更改,不过基本的流程都可以跑通了。 源码下载地址:http://xiazai.jb51.net/2016