Android自定義控制元件實現手勢密碼

Android自定義控制元件實現手勢密碼
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

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 開發 最新文章