Android自定義控件實(shí)現(xiàn)手勢(shì)密碼
Android手勢(shì)解鎖密碼效果圖

首先呢想寫(xiě)這個(gè)手勢(shì)密碼的想法呢,完全是憑空而來(lái)的,然后筆者就花了一天時(shí)間弄出來(lái)了。本以為這個(gè)東西很簡(jiǎn)單,實(shí)際上手的時(shí)候發(fā)現(xiàn),還有很多邏輯需要處理,稍不注意就容易亂套。寫(xiě)個(gè)UI效果圖大約只花了3個(gè)小時(shí),但是處理邏輯就處理了2個(gè)小時(shí)!廢話(huà)不多說(shuō),下面開(kāi)始講解。
樓主呢,自己比較自定義控件,什么東西都掌握在自己的手里感覺(jué)那是相當(dāng)不錯(cuò)(對(duì)于趕工期的小伙瓣兒們還是別手賤了,非常容易掉坑),一有了這個(gè)目標(biāo),我就開(kāi)始構(gòu)思實(shí)現(xiàn)方式。
1、整個(gè)自定義控件是繼承View還是SurfaceView呢?我的經(jīng)驗(yàn)告訴我:需要一直不斷繪制的最好繼承SurfaceView,而需要頻繁與用戶(hù)交互的最好就繼承View。(求大神來(lái)打臉)
2、為了實(shí)現(xiàn)控件的屏幕適配性,當(dāng)然必須重寫(xiě)onMeasure方法,然后在onDraw方法中進(jìn)行繪制。
3、面向?qū)ο笮裕哼@個(gè)控件其實(shí)由兩個(gè)對(duì)象組成:1、9個(gè)圓球;2、圓球之間的連線(xiàn)。
4、仔細(xì)觀察圓球的特征:普通狀態(tài)是白色、touch狀態(tài)是藍(lán)色、錯(cuò)誤狀態(tài)是紅色、整體分為外圍空心圓和內(nèi)實(shí)心圓、所代表的位置信息(密碼值)
5、仔細(xì)觀察連線(xiàn)的特征:普通狀態(tài)為藍(lán)色、錯(cuò)誤狀態(tài)為紅色、始終連接兩個(gè)圓的中心、跟隨手指移動(dòng)而拓展連線(xiàn)、連線(xiàn)之間未點(diǎn)亮的圓球也要點(diǎn)亮。
6、通過(guò)外露參數(shù)來(lái)設(shè)置圓球的顏色、大小等等
7、通過(guò)上面的分析,真?zhèn)€控件可模塊化為三個(gè)任務(wù):onMeasure計(jì)算控件寬高以及小球半徑、onDraw繪制小球與連線(xiàn)、onTouchEvent控制繪制變化。
我把整個(gè)源碼分為三個(gè)類(lèi)文件:LockView、Circle、Util,其中LockView代表整個(gè)控件,Circle代表小圓球、Util封裝工具方法(Path因?yàn)樘?jiǎn)單就沒(méi)封裝,若有代碼潔癖請(qǐng)自行封裝),下面展示Util類(lèi)的源代碼。
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;
}
}
這個(gè)工具方法其實(shí)很簡(jiǎn)單,就是對(duì)SharedPreferences的一個(gè)讀寫(xiě),還有就是List與String類(lèi)型的互相轉(zhuǎn)換。這里就不描述了。下面展示Circle的源碼
public class Circle{
//默認(rèn)值
public static final int DEFAULT_COLOR = Color.WHITE;
public static final int DEFAULT_BOUND = 5;
public static final int DEFAULT_CENTER_BOUND = 15;
//狀態(tài)值
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;
//圓形的中點(diǎn)X、Y坐標(biāo)
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;
//圓形的狀態(tài)
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:改變圓球當(dāng)前狀態(tài)
*/
public void changeStatus(int status){
this.status = status;
}
/**
* @Description:繪制這個(gè)圓形
*/
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);
//繪制中心實(shí)心圓
canvas.drawCircle(centerX, centerY, centerBound, paint);
//繪制空心圓
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(bound);
canvas.drawCircle(centerX, centerY, radius, paint);
}
}
這個(gè)Circle其實(shí)也非常簡(jiǎn)單。上面定義的成員變量一眼便明,并且有注釋。重點(diǎn)在最后的draw方法,首先呢根據(jù)當(dāng)前圓球的不同狀態(tài)設(shè)置不同的顏色值,然后繪制中心的實(shí)心圓,再繪制外圍的空心圓。所有的參數(shù)要么是外界傳遞,要么是默認(rèn)值。(ps:面向?qū)ο笳娴姆浅S杏?,解耦良好的代碼寫(xiě)起來(lái)也舒服看起來(lái)也舒服)。
最后的重點(diǎn)來(lái)了,LockView的源碼,首先貼源碼,然后再針對(duì)性講解。
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 : 當(dāng)前沒(méi)有保存密碼
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 : 成功驗(yàn)證密碼
public static final int STATUS_SUCCESS_PWD = 3;
//@Fields STATUS_FAILED_PWD : 驗(yàn)證密碼失敗
public static final int STATUS_FAILED_PWD = 4;
//@Fields STATUS_ERROR : 輸入密碼長(zhǎng)度不夠
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:初始化當(dāng)前密碼的狀態(tài)
*/
public void initStatus(){
if(TextUtils.isEmpty(Util.getPwd(getContext()))){
status = STATUS_NO_PWD;
}else{
status = STATUS_SAVE_PWD;
}
}
public int getCurrentStatus(){
return status;
}
/**
* @Description:初始化參數(shù),若不調(diào)用則使用默認(rèn)值
* @param padding 圓球之間的間距
* @param colorSuccess 密碼正確時(shí)圓球的顏色
* @param colorFailed 密碼錯(cuò)誤時(shí)圓球的顏色
* @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:若第一次調(diào)用則創(chuàng)建圓球,否則更新圓球
*/
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);
//將這個(gè)點(diǎn)放入Path
backupsPath.moveTo(circle.getCenterX(), circle.getCenterY());
//放入結(jié)果
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);
//首先判斷是否連線(xiàn)中間也有滿(mǎn)足條件的圓
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()){
//處理滿(mǎn)足條件的圓
backupsPath.lineTo(tempCircle.getCenterX(), tempCircle.getCenterY());
//放入結(jié)果
tempCircle.setStatus(Circle.STATUS_TOUCH);
result.add(tempCircle.getPosition());
}
}
//處理現(xiàn)在的圓
backupsPath.lineTo(circle.getCenterX(), circle.getCenterY());
//放入結(jié)果
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){ //當(dāng)前沒(méi)有密碼
//保存密碼,重新錄入
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){ //驗(yàn)證密碼
//判斷密碼是否正確
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();
// 重置下?tīng)顟B(tài)
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:沒(méi)有密碼時(shí),第一次錄入密碼觸發(fā)器
*/
void onTypeInOnce(String input);
/**
* @Description:已經(jīng)錄入第一次密碼,錄入第二次密碼觸發(fā)器
*/
void onTypeInTwice(String input ,boolean isSuccess);
/**
* @Description:驗(yàn)證密碼觸發(fā)器
*/
void onUnLock(String input ,boolean isSuccess);
/**
* @Description:密碼長(zhǎng)度不夠
*/
void onError();
}
}
好了,逐次講解。
首先是對(duì)status的初始化,其實(shí)在static域我已經(jīng)申明了6個(gè)狀態(tài),分別是:
//當(dāng)前沒(méi)有保存密碼 public static final int STATUS_NO_PWD = 0; //需要再輸入一次密碼 public static final int STATUS_RETRY_PWD = 1; //成功保存密碼 public static final int STATUS_SAVE_PWD = 2; //成功驗(yàn)證密碼 public static final int STATUS_SUCCESS_PWD = 3; //驗(yàn)證密碼失敗 public static final int STATUS_FAILED_PWD = 4; //輸入密碼長(zhǎng)度不夠 public static final int STATUS_ERROR = 5;
在剛初始化的時(shí)候,就初始化當(dāng)前的狀態(tài),初始化狀態(tài)就只有2個(gè)狀態(tài):有密碼、無(wú)密碼。
public void initStatus(){
if(TextUtils.isEmpty(Util.getPwd(getContext()))){
status = STATUS_NO_PWD;
}else{
status = STATUS_SAVE_PWD;
}
}
public int getCurrentStatus(){
return status;
}
然后就是通過(guò)外界的設(shè)置初始化一些參數(shù)(若不調(diào)用initParam方法,則采用默認(rèn)值):
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:若第一次調(diào)用則創(chuàng)建圓球,否則更新圓球
*/
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);
}
}
}
上述代碼主要根據(jù)設(shè)置的padding值,計(jì)算出小球的大小,然后判斷是否是初始化小球,還是更新小球。
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);
}
別忘了上面的方法依賴(lài)一個(gè)width值,這個(gè)值是在onMeasure中計(jì)算出來(lái)的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
width = MeasureSpec.getSize(widthMeasureSpec);
height = width - getPaddingLeft() - getPaddingRight() + getPaddingTop() + getPaddingBottom();
setMeasuredDimension(width, height);
}
然后就是繪制方法了,因?yàn)槲覀兊母叨冉怦钚?,本?yīng)該非常復(fù)雜的onDraw方法,卻如此簡(jiǎn)單。就只繪制了小球和路徑。
@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);
}
}
控件是需要和外界進(jìn)行交互的,我喜歡的方法就是自定義監(jiān)聽(tīng)器,然后接口回調(diào)。
public void setOnLockListener(OnLockListener listener){
this.listener = listener;
}
public interface OnLockListener{
/**
* @Description:沒(méi)有密碼時(shí),第一次錄入密碼觸發(fā)器
*/
void onTypeInOnce(String input);
/**
* @Description:已經(jīng)錄入第一次密碼,錄入第二次密碼觸發(fā)器
*/
void onTypeInTwice(String input ,boolean isSuccess);
/**
* @Description:驗(yàn)證密碼觸發(fā)器
*/
void onUnLock(String input ,boolean isSuccess);
/**
* @Description:密碼長(zhǎng)度不夠
*/
void onError();
}
最后最最最重要的一個(gè)部分來(lái)了,onTouchEvent方法,這個(gè)方法其實(shí)也可以分為三個(gè)部分講解: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);
//將這個(gè)點(diǎn)放入Path
backupsPath.moveTo(circle.getCenterX(), circle.getCenterY());
//放入結(jié)果
result.add(circle.getPosition());
break;
}
}
invalidate();
return true;
也就是對(duì)按下的x、y坐標(biāo)進(jìn)行判斷,是否屬于我們的小球范圍內(nèi),若屬于,則放入路徑集合、更改狀態(tài)、加入密碼結(jié)果集。這里別忘了return true,大家都知道吧。
然后是move事件,move事件主要做三件事情:變更小球的狀態(tài)、添加到路徑集合、對(duì)路徑覆蓋的未點(diǎn)亮小球進(jìn)行點(diǎn)亮。代碼有詳細(xì)注釋就不過(guò)多講解了。
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);
//首先判斷是否連線(xiàn)中間也有滿(mǎn)足條件的圓
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()){
//處理滿(mǎn)足條件的圓
backupsPath.lineTo(tempCircle.getCenterX(), tempCircle.getCenterY());
//放入結(jié)果
tempCircle.setStatus(Circle.STATUS_TOUCH);
result.add(tempCircle.getPosition());
}
}
//處理現(xiàn)在的圓
backupsPath.lineTo(circle.getCenterX(), circle.getCenterY());
//放入結(jié)果
circle.setStatus(Circle.STATUS_TOUCH);
result.add(circle.getPosition());
break;
}
}
}
mPath.reset();
mPath.addPath(backupsPath);
mPath.lineTo(event.getX(), event.getY());
invalidate();
break;
這里我用了兩個(gè)Path對(duì)象,backupsPath用于只存放小球的中點(diǎn)坐標(biāo),mPath不僅要存儲(chǔ)小球的中點(diǎn)坐標(biāo),還要存儲(chǔ)當(dāng)前手指觸碰坐標(biāo),為了實(shí)現(xiàn)連線(xiàn)跟隨手指運(yùn)動(dòng)的效果。
最后是up事件,這里有太多復(fù)雜的狀態(tài)轉(zhuǎn)換,我估計(jì)文字講解是描述不清的,大家還是看源代碼吧。
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){ //當(dāng)前沒(méi)有密碼
//保存密碼,重新錄入
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){ //驗(yàn)證密碼
//判斷密碼是否正確
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();
// 重置下?tīng)顟B(tài)
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;
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android手勢(shì)密碼的實(shí)現(xiàn)
- Android 簡(jiǎn)易手勢(shì)密碼開(kāi)源庫(kù)詳解
- Android自定義UI手勢(shì)密碼終結(jié)版
- Android仿支付寶手勢(shì)密碼解鎖功能
- Android手勢(shì)密碼實(shí)現(xiàn)實(shí)例代碼
- Android九宮格手勢(shì)密碼代碼設(shè)計(jì)
- Android實(shí)現(xiàn)手勢(shì)密碼功能
- Android自定義UI手勢(shì)密碼改進(jìn)版源碼下載
- Android自定義UI手勢(shì)密碼簡(jiǎn)單版
- 純android代碼實(shí)現(xiàn)九宮格手勢(shì)密碼
相關(guān)文章
Android自定義View實(shí)現(xiàn)QQ運(yùn)動(dòng)積分轉(zhuǎn)盤(pán)抽獎(jiǎng)功能
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)QQ運(yùn)動(dòng)積分轉(zhuǎn)盤(pán)抽獎(jiǎng)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
Android開(kāi)發(fā)中使用mms模塊收發(fā)單卡和雙卡短信的教程
這篇文章主要介紹了Android開(kāi)發(fā)中使用mms模塊收發(fā)單卡和雙卡短信的教程,文中舉了MOTO XT800手機(jī)(估計(jì)已經(jīng)落伍很久了--)的例子來(lái)說(shuō)明如何解決雙卡雙待時(shí)的短信異常問(wèn)題,需要的朋友可以參考下2016-02-02
Android編程實(shí)現(xiàn)設(shè)置按鈕背景透明與半透明及圖片背景透明的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)設(shè)置按鈕背景透明與半透明及圖片背景透明的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Button及ImageButton的背景屬性設(shè)置技巧,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-12-12
Android實(shí)現(xiàn)頁(yè)面翻轉(zhuǎn)和自動(dòng)翻轉(zhuǎn)功能
這篇文章主要介紹了Android中簡(jiǎn)單實(shí)現(xiàn)頁(yè)面翻轉(zhuǎn)和自動(dòng)翻轉(zhuǎn)的功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10
Android實(shí)現(xiàn)圖片自動(dòng)輪播并且支持手勢(shì)左右無(wú)限滑動(dòng)
這篇文章給大家介紹android實(shí)現(xiàn)圖片自動(dòng)輪播并且支持手勢(shì)左右無(wú)限滑動(dòng),代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-10-10
Android png透明圖片轉(zhuǎn)jpg時(shí)背景變黑的解決方法
這篇文章主要介紹了Android png透明圖片轉(zhuǎn)jpg時(shí)背景變黑的解決方法,需要的朋友可以參考下2017-12-12
ExpandableListView實(shí)現(xiàn)手風(fēng)琴效果
這篇文章主要為大家詳細(xì)介紹了ExpandableListView實(shí)現(xiàn)手風(fēng)琴效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
Android應(yīng)用UI開(kāi)發(fā)中Fragment的常見(jiàn)用法小結(jié)
這篇文章主要介紹了Android應(yīng)用UI開(kāi)發(fā)中Fragment的常見(jiàn)用法小結(jié),Fragment的存在是為了解決不同屏幕分辯率的動(dòng)態(tài)和靈活UI設(shè)計(jì),需要的朋友可以參考下2016-02-02
Android筆記之:深入ViewStub的應(yīng)用
本篇文章是對(duì)Android中ViewStub的應(yīng)用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05

