Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義控件實現手勢密碼

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

編輯:關於Android編程

Android手勢解鎖密碼效果圖 

     首先呢想寫這個手勢密碼的想法呢,完全是憑空而來的,然後筆者就花了一天時間弄出來了。本以為這個東西很簡單,實際上手的時候發現,還有很多邏輯需要處理,稍不注意就容易亂套。寫個UI效果圖大約只花了3個小時,但是處理邏輯就處理了2個小時!廢話不多說,下面開始講解。 
    樓主呢,自己比較自定義控件,什麼東西都掌握在自己的手裡感覺那是相當不錯(對於趕工期的小伙瓣兒們還是別手賤了,非常容易掉坑),一有了這個目標,我就開始構思實現方式。 
    1、整個自定義控件是繼承View還是SurfaceView呢?我的經驗告訴我:需要一直不斷繪制的最好繼承SurfaceView,而需要頻繁與用戶交互的最好就繼承View。(求大神來打臉) 
    2、為了實現控件的屏幕適配性,當然必須重寫onMeasure方法,然後在onDraw方法中進行繪制。 
    3、面向對象性:這個控件其實由兩個對象組成:1、9個圓球;2、圓球之間的連線。 
    4、仔細觀察圓球的特征:普通狀態是白色、touch狀態是藍色、錯誤狀態是紅色、整體分為外圍空心圓和內實心圓、所代表的位置信息(密碼值) 
    5、仔細觀察連線的特征:普通狀態為藍色、錯誤狀態為紅色、始終連接兩個圓的中心、跟隨手指移動而拓展連線、連線之間未點亮的圓球也要點亮。 
    6、通過外露參數來設置圓球的顏色、大小等等 
    7、通過上面的分析,真個控件可模塊化為三個任務:onMeasure計算控件寬高以及小球半徑、onDraw繪制小球與連線、onTouchEvent控制繪制變化。 

    我把整個源碼分為三個類文件:LockView、Circle、Util,其中LockView代表整個控件,Circle代表小圓球、Util封裝工具方法(Path因為太簡單就沒封裝,若有代碼潔癖請自行封裝),下面展示Util類的源代碼。 

public class Util{
 
 private static final String SP_NAME = "LOCKVIEW";
 private static final String SP_KEY = "PASSWORD";

 public static void savePwd(Context mContext ,List<Integer> password){
  SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
  sp.edit().putString(SP_KEY, listToString(password)).commit();
 }
 
 public static String getPwd(Context mContext){
  SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
  return sp.getString(SP_KEY, "");
 }
 
 public static void clearPwd(Context mContext){
  SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
  sp.edit().remove(SP_KEY).commit();
 }
 
 public static String listToString(List<Integer> lists){
  StringBuffer sb = new StringBuffer();
  for(int i = 0; i < lists.size(); i++){
   sb.append(lists.get(i));
  }
  return sb.toString();
 }
 
 public static List<Integer> stringToList(String string){
  List<Integer> lists = new ArrayList<>();
  for(int i = 0; i < string.length(); i++){
   lists.add(Integer.parseInt(string.charAt(i) + ""));
  }
  return lists;
 }
}

     這個工具方法其實很簡單,就是對SharedPreferences的一個讀寫,還有就是List與String類型的互相轉換。這裡就不描述了。下面展示Circle的源碼 

public class Circle{
 //默認值
 public static final int DEFAULT_COLOR = Color.WHITE;
 public static final int DEFAULT_BOUND = 5;
 public static final int DEFAULT_CENTER_BOUND = 15;
 //狀態值
 public static final int STATUS_DEFAULT = 0;
 public static final int STATUS_TOUCH = 1;
 public static final int STATUS_SUCCESS = 2;
 public static final int STATUS_FAILED = 3;
 
 //圓形的中點X、Y坐標
 private int centerX;
 private int centerY;
 //圓形的顏色值
 private int colorDefault = DEFAULT_COLOR;
 private int colorSuccess;
 private int colorFailed;
 //圓形的寬度
 private int bound = DEFAULT_BOUND;
 //中心的寬度
 private int centerBound = DEFAULT_CENTER_BOUND;
 //圓形的半徑
 private int radius;
 //圓形的狀態
 private int status = STATUS_DEFAULT;
 //圓形的位置
 private int position;
 
 public Circle(int centerX, int centerY, int colorSuccess, int colorFailed, int radius, int position){
  super();
  this.centerX = centerX;
  this.centerY = centerY;
  this.colorSuccess = colorSuccess;
  this.colorFailed = colorFailed;
  this.radius = radius;
  this.position = position;
 }

 public Circle(int centerX, int centerY, int colorDefault, int colorSuccess, int colorFailed, int bound,
   int centerBound, int radius, int status, int position){
  super();
  this.centerX = centerX;
  this.centerY = centerY;
  this.colorDefault = colorDefault;
  this.colorSuccess = colorSuccess;
  this.colorFailed = colorFailed;
  this.bound = bound;
  this.centerBound = centerBound;
  this.radius = radius;
  this.status = status;
  this.position = position;
 }

 public int getCenterX(){
  return centerX;
 }

 public void setCenterX(int centerX){
  this.centerX = centerX;
 }

 public int getCenterY(){
  return centerY;
 }

 public void setCenterY(int centerY){
  this.centerY = centerY;
 }

 public int getColorDefault(){
  return colorDefault;
 }

 public void setColorDefault(int colorDefault){
  this.colorDefault = colorDefault;
 }

 public int getColorSuccess(){
  return colorSuccess;
 }

 public void setColorSuccess(int colorSuccess){
  this.colorSuccess = colorSuccess;
 }

 public int getColorFailed(){
  return colorFailed;
 }

 public void setColorFailed(int colorFailed){
  this.colorFailed = colorFailed;
 }

 public int getBound(){
  return bound;
 }

 public void setBound(int bound){
  this.bound = bound;
 }

 public int getCenterBound(){
  return centerBound;
 }

 public void setCenterBound(int centerBound){
  this.centerBound = centerBound;
 }

 public int getRadius(){
  return radius;
 }

 public void setRadius(int radius){
  this.radius = radius;
 }

 public int getStatus(){
  return status;
 }

 public void setStatus(int status){
  this.status = status;
 }

 public int getPosition(){
  return position;
 }

 public void setPosition(int position){
  this.position = position;
 }

 /** 
  * @Description:改變圓球當前狀態 
 */
 public void changeStatus(int status){
  this.status = status;
 }
 
 /** 
  * @Description:繪制這個圓形 
 */
 public void draw(Canvas canvas ,Paint paint){
  switch(status){
   case STATUS_DEFAULT:
    paint.setColor(colorDefault);
    break;
   case STATUS_TOUCH:
   case STATUS_SUCCESS:
    paint.setColor(colorSuccess);
    break;
   case STATUS_FAILED:
    paint.setColor(colorFailed);
    break;
   default:
    paint.setColor(colorDefault);
    break;
  }
  paint.setStyle(Paint.Style.FILL);
  //繪制中心實心圓
  canvas.drawCircle(centerX, centerY, centerBound, paint);
  //繪制空心圓
  paint.setStyle(Paint.Style.STROKE);
  paint.setStrokeWidth(bound);
  canvas.drawCircle(centerX, centerY, radius, paint);
 }
}

     這個Circle其實也非常簡單。上面定義的成員變量一眼便明,並且有注釋。重點在最後的draw方法,首先呢根據當前圓球的不同狀態設置不同的顏色值,然後繪制中心的實心圓,再繪制外圍的空心圓。所有的參數要麼是外界傳遞,要麼是默認值。(ps:面向對象真的非常有用,解耦良好的代碼寫起來也舒服看起來也舒服)。 

    最後的重點來了,LockView的源碼,首先貼源碼,然後再針對性講解。 

public class LockView extends View{
 
 private static final int COUNT_PER_RAW = 3;
 private static final int DURATION = 1500;
 private static final int MIN_PWD_NUMBER = 6;
 //@Fields STATUS_NO_PWD : 當前沒有保存密碼
 public static final int STATUS_NO_PWD = 0;
 //@Fields STATUS_RETRY_PWD : 需要再輸入一次密碼
 public static final int STATUS_RETRY_PWD = 1;
 //@Fields STATUS_SAVE_PWD : 成功保存密碼
 public static final int STATUS_SAVE_PWD = 2;
 //@Fields STATUS_SUCCESS_PWD : 成功驗證密碼
 public static final int STATUS_SUCCESS_PWD = 3;
 //@Fields STATUS_FAILED_PWD : 驗證密碼失敗
 public static final int STATUS_FAILED_PWD = 4;
 //@Fields STATUS_ERROR : 輸入密碼長度不夠
 public static final int STATUS_ERROR = 5;
 
 private int width;
 private int height;
 private int padding = 0;
 private int colorSuccess = Color.BLUE;
 private int colorFailed = Color.RED;
 private int minPwdNumber = MIN_PWD_NUMBER;
 private List<Circle> circles = new ArrayList<>();
 private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 private Path mPath = new Path();
 private Path backupsPath = new Path();
 private List<Integer> result = new ArrayList<>();
 private int status = STATUS_NO_PWD;
 private OnLockListener listener;
 private Handler handler = new Handler();

 public LockView(Context context, AttributeSet attrs, int defStyle){
  super(context, attrs, defStyle);
  initStatus();
 }

 public LockView(Context context, AttributeSet attrs){
  super(context, attrs);
  initStatus();
 }

 public LockView(Context context){
  super(context);
  initStatus();
 }

 /** 
  * @Description:初始化當前密碼的狀態
 */
 public void initStatus(){
  if(TextUtils.isEmpty(Util.getPwd(getContext()))){
   status = STATUS_NO_PWD;
  }else{
   status = STATUS_SAVE_PWD;
  }
 }
 
 public int getCurrentStatus(){
  return status;
 }

 /** 
  * @Description:初始化參數,若不調用則使用默認值
  * @param padding 圓球之間的間距
  * @param colorSuccess 密碼正確時圓球的顏色
  * @param colorFailed 密碼錯誤時圓球的顏色
  * @return LockView
 */
 public LockView initParam(int padding ,int colorSuccess ,int colorFailed ,int minPwdNumber){
  this.padding = padding;
  this.colorSuccess = colorSuccess;
  this.colorFailed = colorFailed;
  this.minPwdNumber = minPwdNumber;
  init();
  return this;
 }
 
 /** 
  * @Description:若第一次調用則創建圓球,否則更新圓球
 */
 private void init(){
  int circleRadius = (width - (COUNT_PER_RAW + 1) * padding) / COUNT_PER_RAW /2;
  if(circles.size() == 0){   
   for(int i = 0; i < COUNT_PER_RAW * COUNT_PER_RAW; i++){
    createCircles(circleRadius, i);
   }
  }else{
   for(int i = 0; i < COUNT_PER_RAW * COUNT_PER_RAW; i++){
    updateCircles(circles.get(i), circleRadius);
   }
  }
 }
 
 private void createCircles(int radius, int position){
  int centerX = (position % 3 + 1) * padding + (position % 3 * 2 + 1) * radius;
  int centerY = (position / 3 + 1) * padding + (position / 3 * 2 + 1) * radius;
  Circle circle = new Circle(centerX, centerY, colorSuccess, colorFailed, radius, position);
  circles.add(circle);
 }
 
 private void updateCircles(Circle circle ,int radius){
  int centerX = (circle.getPosition() % 3 + 1) * padding + (circle.getPosition() % 3 * 2 + 1) * radius;
  int centerY = (circle.getPosition() / 3 + 1) * padding + (circle.getPosition() / 3 * 2 + 1) * radius;
  circle.setCenterX(centerX);
  circle.setCenterY(centerY);
  circle.setRadius(radius);
  circle.setColorSuccess(colorSuccess);
  circle.setColorFailed(colorFailed);
 }

 @Override
 protected void onDraw(Canvas canvas){
  init();
  //繪制圓
  for(int i = 0; i < circles.size() ;i++){
   circles.get(i).draw(canvas, mPaint);
  }
  if(result.size() != 0){   
   //繪制Path
   Circle temp = circles.get(result.get(0));
   mPaint.setColor(temp.getStatus() == Circle.STATUS_FAILED ? colorFailed : colorSuccess);
   mPaint.setStrokeWidth(Circle.DEFAULT_CENTER_BOUND);
   canvas.drawPath(mPath, mPaint);
  }
 }
 
 @Override
 public boolean onTouchEvent(MotionEvent event){
  switch(event.getAction()){
   case MotionEvent.ACTION_DOWN:
    backupsPath.reset();
    for(int i = 0; i < circles.size() ;i++){
     Circle circle = circles.get(i);
     if(event.getX() >= circle.getCenterX() - circle.getRadius()
       && event.getX() <= circle.getCenterX() + circle.getRadius()
       && event.getY() >= circle.getCenterY() - circle.getRadius()
       && event.getY() <= circle.getCenterY() + circle.getRadius()){
      circle.setStatus(Circle.STATUS_TOUCH);
      //將這個點放入Path
      backupsPath.moveTo(circle.getCenterX(), circle.getCenterY());
      //放入結果
      result.add(circle.getPosition());
      break;
     }
    }
    invalidate();
    return true;
    
   case MotionEvent.ACTION_MOVE:
    for(int i = 0; i < circles.size() ;i++){
     Circle circle = circles.get(i);
     if(event.getX() >= circle.getCenterX() - circle.getRadius()
       && event.getX() <= circle.getCenterX() + circle.getRadius()
       && event.getY() >= circle.getCenterY() - circle.getRadius()
       && event.getY() <= circle.getCenterY() + circle.getRadius()){
      if(!result.contains(circle.getPosition())){       
       circle.setStatus(Circle.STATUS_TOUCH);
       //首先判斷是否連線中間也有滿足條件的圓
       Circle lastCircle = circles.get(result.get(result.size() - 1));
       int cx = (lastCircle.getCenterX() + circle.getCenterX()) / 2;
       int cy = (lastCircle.getCenterY() + circle.getCenterY()) / 2;
       for(int j = 0; j < circles.size(); j++){
        Circle tempCircle = circles.get(j);
        if(cx >= tempCircle.getCenterX() - tempCircle.getRadius()
          && cx <= tempCircle.getCenterX() + tempCircle.getRadius()
          && cy >= tempCircle.getCenterY() - tempCircle.getRadius()
          && cy <= tempCircle.getCenterY() + tempCircle.getRadius()){
         //處理滿足條件的圓
         backupsPath.lineTo(tempCircle.getCenterX(), tempCircle.getCenterY());
         //放入結果
         tempCircle.setStatus(Circle.STATUS_TOUCH);
         result.add(tempCircle.getPosition());
        }
       }
       //處理現在的圓
       backupsPath.lineTo(circle.getCenterX(), circle.getCenterY());
       //放入結果
       circle.setStatus(Circle.STATUS_TOUCH);
       result.add(circle.getPosition());
       break;
      }
     }
    }
    mPath.reset();
    mPath.addPath(backupsPath);
    mPath.lineTo(event.getX(), event.getY());
    invalidate();
    break;
    
   case MotionEvent.ACTION_UP:
    mPath.reset();
    mPath.addPath(backupsPath);
    invalidate();
    if(result.size() < minPwdNumber){
     if(listener != null){      
      listener.onError();
     }
     if(status == STATUS_RETRY_PWD){
      Util.clearPwd(getContext());
     }
     status = STATUS_ERROR;
     for(int i = 0; i < result.size(); i++){
      circles.get(result.get(i)).setStatus(Circle.STATUS_FAILED);
     }
    }else{
     if(status == STATUS_NO_PWD){ //當前沒有密碼
      //保存密碼,重新錄入
      Util.savePwd(getContext(), result);
      status = STATUS_RETRY_PWD;
      if(listener != null){
       listener.onTypeInOnce(Util.listToString(result));
      }
     }else if(status == STATUS_RETRY_PWD){ //需要重新繪制密碼
      //判斷兩次輸入是否相等
      if(Util.getPwd(getContext()).equals(Util.listToString(result))){
       status = STATUS_SAVE_PWD;
       if(listener != null){
        listener.onTypeInTwice(Util.listToString(result), true);
       }
       for(int i = 0; i < result.size(); i++){
        circles.get(result.get(i)).setStatus(Circle.STATUS_SUCCESS);
       }
      }else{
       status = STATUS_NO_PWD;
       Util.clearPwd(getContext());
       if(listener != null){
        listener.onTypeInTwice(Util.listToString(result), false);
       }
       for(int i = 0; i < result.size(); i++){
        circles.get(result.get(i)).setStatus(Circle.STATUS_FAILED);
       }
      }
     }else if(status == STATUS_SAVE_PWD){ //驗證密碼
      //判斷密碼是否正確
      if(Util.getPwd(getContext()).equals(Util.listToString(result))){
       status = STATUS_SUCCESS_PWD;
       if(listener != null){
        listener.onUnLock(Util.listToString(result), true);
       }
       for(int i = 0; i < result.size(); i++){
        circles.get(result.get(i)).setStatus(Circle.STATUS_SUCCESS);
       }
      }else{
       status = STATUS_FAILED_PWD;
       if(listener != null){
        listener.onUnLock(Util.listToString(result), false);
       }
       for(int i = 0; i < result.size(); i++){
        circles.get(result.get(i)).setStatus(Circle.STATUS_FAILED);
       }
      }
     }
    }
    invalidate();
    handler.postDelayed(new Runnable(){
     
     @Override
     public void run(){
      result.clear();
      mPath.reset();
      backupsPath.reset();
     //  initStatus();
      // 重置下狀態
      if(status == STATUS_SUCCESS_PWD || status == STATUS_FAILED_PWD){
       status = STATUS_SAVE_PWD;
      }else if(status == STATUS_ERROR){
       initStatus();
      }
      for(int i = 0; i < circles.size(); i++){
       circles.get(i).setStatus(Circle.STATUS_DEFAULT);
      }
      invalidate();
     }
    }, DURATION);
    break;
   default:
    break;
  }
  return super.onTouchEvent(event);
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
  width = MeasureSpec.getSize(widthMeasureSpec);
  height = width - getPaddingLeft() - getPaddingRight() + getPaddingTop() + getPaddingBottom();
  setMeasuredDimension(width, height);
 }
 
 public void setOnLockListener(OnLockListener listener){
  this.listener = listener;
 }
 
 public interface OnLockListener{
  /** 
   * @Description:沒有密碼時,第一次錄入密碼觸發器
  */
  void onTypeInOnce(String input);
  /**
   * @Description:已經錄入第一次密碼,錄入第二次密碼觸發器
   */
  void onTypeInTwice(String input ,boolean isSuccess);
  /** 
   * @Description:驗證密碼觸發器 
  */
  void onUnLock(String input ,boolean isSuccess);
  
  /**
   * @Description:密碼長度不夠
   */
  void onError();
 }
}

好了,逐次講解。 

 首先是對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;

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved