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 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 lists){

StringBuffer sb = new StringBuffer();

for(int i = 0; i < lists.size(); i++){

sb.append(lists.get(i));

}

return sb.toString();

}

public static List stringToList(String string){

List 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 circles = new ArrayList<>();

private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

private Path mPath = new Path();

private Path backupsPath = new Path();

private List 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 手势密码 开发,Android自定义控件实现手势密码相关推荐

  1. 拼图android程序,可视化开发Android应用程序:拼图开发模式App Inventor

    可视化开发Android应用程序:拼图开发模式App Inventor 作 者:王向辉 等编著 出版时间:2013 内容简介 <可视化开发Android应用程序:拼图开发模式App Invent ...

  2. [Android]使用Kotlin开发Android(二)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/4829007.html [TOC] 使用Kotlin+OkHtt ...

  3. 【Android 逆向】Android 逆向通用工具开发 ( Android 平台运行的 cmd 程序类型 | Android 平台运行的 cmd 程序编译选项 | 编译 cmd 可执行程序 )

    文章目录 一.Android 平台运行的 cmd 程序类型 二.Android 平台运行的 cmd 程序编译选项 三.编译 Android 平台的 cmd 可执行程序 一.Android 平台运行的 ...

  4. html开发android,使用HTML5开发Android本地应用(一)

    HTML5是现在最热门的技术之一,Android自带的WebKit浏览器对于HTML5有着不错的支持,而各种浏览器的最新版本都开始普遍的支持HTML5.使用HTML5开发手机应用的主要好处有两个:一个 ...

  5. android光度传感器开发,Android开发之光线传感器用法

    释放双眼,带上耳机,听听看~! 获得感应器服务 Android开发中要使用光线传感器,需要先获得系统传感器服务Context.SENSOR_SERVICE,获得方法如下: SensorManager ...

  6. android 汽车gps开发,Android GPS定位开发教程

    要获取精确的位置服务信息需要 GPS 硬件的支持.在应用程序开发阶段,由于模拟器中并没有真正的 GPS 硬件,因此不能获得真实的 GPS 信息.但是可以使用 Eclipse 视图模式的 DDMS 模式 ...

  7. android电视盒子开发,Android TV机顶盒开发之初级接触

    最近接触了点Andiroid 机顶盒开发,简单的写一下,希望我的经验可以给各位带来一点经验.图片我就不上传的,很麻烦的! 首先是Android的apk如何安装到机顶盒上?将TV连接机顶盒,然后找到设置 ...

  8. android 美颜相机开发,Android OpenGL ES从入门到进阶(一)—— 五分钟开发一款美颜相机...

    源码链接:https://github.com/smzhldr/AGLFramework 一.前言 商店里有数十款的美颜相机类产品,以及像抖音,唱吧之类带有视频的软件,功能很强大,其实现原理基本上都是 ...

  9. android签到功能开发,Android应用开发之Android简单实现app每月签到功能

    本文将带你了解Android应用开发Android简单实现app每月签到功能,希望本文对大家学Android有所帮助. 本文实例为大家分享了Android实现app每月签到功能的具体代码,供大家参考, ...

  10. 【Android 逆向】Android 逆向通用工具开发 ( Android 逆向通用工具组成部分 | 各模块间的关联 )

    文章目录 一.Android 逆向通用工具组成部分 二.Android 逆向通用工具 各模块间的关联 一.Android 逆向通用工具组成部分 Android 逆向通用工具 肯定是跨平台的 , 涉及到 ...

最新文章

  1. git创建本地版本仓库及注意事项
  2. 集成Netty|tensorflow实现 聊天AI--PigPig养成记(2)
  3. Java多线程学习十:线程池实现“线程复用”的原理
  4. 使用 vs 2008 宏制作自动注释工具
  5. 视频转换器怎么用?迅捷视频转换器图文操作教程分享
  6. Tbase 源码 (六)
  7. xml建模包括以下_我们的服务|无人机倾斜摄影三维建模
  8. 使用Vue指令实现下拉菜单效果
  9. 【JAVA】根据汉字拼音首字母排序 不同类型处理
  10. Neo4j 做推荐 (2)—— 基于内容的过滤
  11. web移动端安卓和ios兼容问题
  12. XR迈向10亿级必须解决的3个问题
  13. pgsql 常用命令
  14. K210学习笔记(三) K210与STM32进行串口通信,K210发STM收
  15. 锁定计算机后 关机任务不执行,定时关机3000常见问题(一)
  16. ffmpeg 之 dash
  17. 4.1.3.9 监听网络状态,3G,WIFI,
  18. 逐梦旅程学习笔记 DirectX开发入门02:旋转的彩色立方体
  19. linux 下 su - oracle 切换不了
  20. grep -v grep使用说明

热门文章

  1. 爱立信面试前的GitHub项目如何上传
  2. [来料检验]触摸屏来料检验设想
  3. Visio 2003 sp3下载
  4. 基于JavaWeb的3D网上模板商城
  5. testreport
  6. astar插件下载 就行_premiere软件下载安装pr自学视频教程
  7. Python爬虫入门教程 68-100 一招解决爬虫程序卡死问题
  8. 数字电路实验 04 - | 组合逻辑电路的设计与测试
  9. iPad mini Retina越狱小结【2014年02月06日 - 初稿】
  10. Android得到位图方式