欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android自定義View九宮格手勢密碼解鎖

 更新時(shí)間:2021年09月10日 10:12:14   作者:JerryloveEmily  
這篇文章主要為大家詳細(xì)介紹了Android自定義View九宮格手勢密碼解鎖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

由于公司新的項(xiàng)目需要用到九宮格手勢密碼解鎖的功能,于是覺得自己寫一個(gè)。廢話不多說,直接上效果圖:

首選我們來分析下實(shí)現(xiàn)的思路:

1. 繪制出相對于這個(gè)View的居中的九個(gè)點(diǎn),作為默認(rèn)狀態(tài)的點(diǎn)
2. 點(diǎn)擊屏幕的時(shí)候是否點(diǎn)擊在這九個(gè)點(diǎn)上
3. 在屏幕上滑動(dòng)的時(shí)候,繪制兩個(gè)點(diǎn)之間的線條,以及選中狀態(tài)的點(diǎn)
4. 手指離開屏幕的時(shí)候判斷手勢密碼是否正確,如若錯(cuò)誤這把錯(cuò)誤狀態(tài)下的點(diǎn)和線繪制出來。

具體實(shí)現(xiàn):

首先我們得繪制出默認(rèn)正常狀態(tài)下的九個(gè)點(diǎn):

/**
 * 點(diǎn)的bean
 * Created by Administrator on 2015/9/21.
 */
public class Point {

 // 正常狀態(tài)
 public static final int STATE_NORMAL = 1;
 // 按下狀態(tài)
 public static final int STATE_PRESS = 2;
 // 錯(cuò)誤狀態(tài)
 public static final int STATE_ERROR = 3;

 float x;
 float y;
 int state = STATE_NORMAL;

 public Point(float x, float y){
  this.x = x;
  this.y = y;
 }

 /**
  * 計(jì)算兩點(diǎn)間的距離
  * @param a 另外一個(gè)點(diǎn)
  * @return
  */
 public float getInstance(Point a){
  return (float) Math.sqrt((x-a.x)*(x-a.x) + (y-a.y)*(y-a.y));
 }
}

可以看到,給一個(gè)點(diǎn)定義了x、y值以及這個(gè)點(diǎn)的狀態(tài)有三種,默認(rèn)的初始狀態(tài)是正常的狀態(tài)。

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
 super.onSizeChanged(w, h, oldw, oldh);
 init();// 初始化正常狀態(tài)下的九個(gè)點(diǎn),以及三種狀態(tài)所需要用到的圖片資源
}

private void init() {
  mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  mPressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  mErrorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  // 按下狀態(tài)的畫筆
  mPressPaint.setColor(Color.parseColor("#00B7EE"));
  mPressPaint.setStrokeWidth(7);
  // 錯(cuò)誤狀態(tài)的畫筆
  mErrorPaint.setColor(Color.parseColor("#FB0C13"));
  mErrorPaint.setStrokeWidth(7);

  // 加載三種狀態(tài)圖片
  mNormalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lock_point_normal);
  mPressBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lock_point_press);
  mErrorBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lock_point_error);
  mPointRadius = mNormalBitmap.getWidth() / 2;

  // 當(dāng)前視圖的大小
  int width = getWidth();
  int height = getHeight();
  // 九宮格點(diǎn)的偏移量
  int offSet = Math.abs(width - height) / 2;
  // x、y軸上的偏移量
  int offSetX = 0, offSetY = 0;
  int pointItemWidth = 0; // 每個(gè)點(diǎn)所占用方格的寬度
  if (width > height){ // 橫屏的時(shí)候
   offSetX = offSet;
   offSetY = 0;
   pointItemWidth = height / 4;
  }
  if (width < height){ // 豎屏的時(shí)候
   offSetX = 0;
   offSetY = offSet;
   pointItemWidth = width / 4;
  }

  // 初始化九個(gè)點(diǎn)
  mPoints[0][0] = new Point(offSetX + pointItemWidth, offSetY + pointItemWidth);
  mPoints[0][1] = new Point(offSetX + pointItemWidth * 2, offSetY + pointItemWidth);
  mPoints[0][2] = new Point(offSetX + pointItemWidth * 3, offSetY + pointItemWidth);

  mPoints[1][0] = new Point(offSetX + pointItemWidth, offSetY + pointItemWidth * 2);
  mPoints[1][1] = new Point(offSetX + pointItemWidth * 2, offSetY + pointItemWidth * 2);
  mPoints[1][2] = new Point(offSetX + pointItemWidth * 3, offSetY + pointItemWidth * 2);

  mPoints[2][0] = new Point(offSetX + pointItemWidth, offSetY + pointItemWidth * 3);
  mPoints[2][1] = new Point(offSetX + pointItemWidth * 2, offSetY + pointItemWidth * 3);
  mPoints[2][2] = new Point(offSetX + pointItemWidth * 3, offSetY + pointItemWidth * 3);
 }

這段代碼要注意的是橫豎屏的偏移量,橫屏的時(shí)候計(jì)算X軸的偏移量,豎屏的時(shí)候計(jì)算Y軸的偏移量。計(jì)算出x y的偏移量后,來初始化九個(gè)點(diǎn)的位置。我們要讓九宮格的點(diǎn)繪制的位置在 當(dāng)前這個(gè)自定義視圖View的正中間,那么如上圖顯示,第一個(gè)點(diǎn)的起始點(diǎn)就是x = x軸的偏移量 + 格子寬度, y = y軸的偏移量 + 格子寬度。以此可見第二列的點(diǎn)的x值 = 兩個(gè)格子的寬度 + x軸的偏移量,同理第二行的點(diǎn)的y值 = 兩個(gè)格子的寬度 + y周的偏移量。以此類推初始化九個(gè)點(diǎn)的位置。

九個(gè)點(diǎn)的位置初始化后,我們需要來繪制九個(gè)點(diǎn),這里我用了三種狀態(tài)的圖片來作為頂點(diǎn)。在init()方法中,初始化了三種bitmap圖片對象。以及計(jì)算了點(diǎn)的半徑也就是圖片的一半,當(dāng)然我這里的三張圖片大小是一樣的。如果不一樣,還是要重新計(jì)算過。

接下來就在onDraw方法里繪制出九個(gè)點(diǎn):

@Override
 protected void onDraw(Canvas canvas) {

  // 繪制點(diǎn)
  drawPoints(canvas);

  // 繪制連線
  drawLines(canvas);
 }

 /**
  * 繪制所有的點(diǎn)
  * @param canvas
  */
 private void drawPoints(Canvas canvas){
  for (int i = 0; i < mPoints.length; i++){
   for (int j = 0; j < mPoints[i].length; j++){
    Point point = mPoints[i][j];
    // 不同狀態(tài)繪制點(diǎn)
    switch (point.state){
     case Point.STATE_NORMAL:
      canvas.drawBitmap(mNormalBitmap, point.x - mPointRadius, point.y - mPointRadius, mPaint);
      break;
     case Point.STATE_PRESS:
      canvas.drawBitmap(mPressBitmap, point.x - mPointRadius, point.y - mPointRadius, mPaint);
      break;
     case Point.STATE_ERROR:
      canvas.drawBitmap(mErrorBitmap, point.x - mPointRadius, point.y - mPointRadius, mPaint);
      break;
    }
   }
  }
 }

我們變量初始化好的九個(gè)點(diǎn)對象的狀態(tài),不同狀態(tài)繪制不同的圖片。這里繪制的時(shí)候要注意初始化點(diǎn)的時(shí)候的x、y值是包括了點(diǎn)圓的半徑的,而繪制圖片又是從左上角開始的,所以在繪制的時(shí)候需要減去圖片本身的半徑。

繪制后默認(rèn)的九個(gè)點(diǎn)后,我們接下來處理手勢滑動(dòng),覆寫onTouchEvent方法:

 /**
  * 獲取選擇的點(diǎn)的位置
  * @return
  */
 private int[] getSelectedPointPosition(){
  Point point = new Point(mX, mY);
  for (int i = 0; i < mPoints.length; i++) {
   for (int j = 0; j < mPoints[i].length; j++) {
    // 判斷觸摸的點(diǎn)和遍歷的當(dāng)前點(diǎn)的距離是否小于當(dāng)個(gè)點(diǎn)的半徑
    if(mPoints[i][j].getInstance(point) < mPointRadius){
     // 小于則獲取作為被選中,并返回選中點(diǎn)的位置
     int[] result = new int[2];
     result[0] = i;
     result[1] = j;
     return result;
    }
   }
  }
  return null;
 }

首先我們要判斷手指點(diǎn)擊的位置是否是在點(diǎn)上,獲取屏幕觸摸的點(diǎn)的位置mX、mY,初始化一個(gè)點(diǎn),然后遍歷所有的點(diǎn)與觸摸點(diǎn)的距離 是否 小于 一個(gè)點(diǎn)的圖片的半徑,如果小于表示觸摸的位置在這九個(gè)點(diǎn)中的一個(gè)上。getInstance(point)是計(jì)算兩點(diǎn)之間的距離的方法。公式是:distance = Math.sqrt((x1 - x2)(x1 - x2) + (y1 - y2) (y1 - y2))。如果是觸摸的位置在點(diǎn)上,那就返回這個(gè)點(diǎn)的在九宮格數(shù)組中的下標(biāo)位置。

我們來看onTouchEvent方法:

@Override
 public boolean onTouchEvent(MotionEvent event) {
  // 獲取手指觸摸的xy位置
  mX = event.getX();
  mY = event.getY();
  int[] position;
  int i, j;
  switch (event.getAction()){
   case MotionEvent.ACTION_DOWN:
    // 重置所有的點(diǎn)
    resetPoints();
    // 獲取選擇的點(diǎn)的位置
    position = getSelectedPointPosition();
    if (position != null){
     isDraw = true; // 標(biāo)記為繪制狀態(tài)
     i = position[0];
     j = position[1];
     mPoints[i][j].state = Point.STATE_PRESS;
     // 被選擇的點(diǎn)存入一個(gè)集合中
     mSelectedPoints.add(mPoints[i][j]);
     mPassPositions.add(i * 3 + j); // 把選中的點(diǎn)的路徑轉(zhuǎn)換成一位數(shù)組存儲(chǔ)起來
    }
    break;
   case MotionEvent.ACTION_MOVE:
    if (isDraw){
     position = getSelectedPointPosition();
     if (position != null){
      i = position[0];
      j = position[1];
      if (!mSelectedPoints.contains(mPoints[i][j])){
       mPoints[i][j].state = Point.STATE_PRESS;
       mSelectedPoints.add(mPoints[i][j]);
       mPassPositions.add(i * 3 + j);
      }
     }
    }
    break;
   case MotionEvent.ACTION_UP:
    // 標(biāo)記為不在繪制
    isDraw = false;
    break;
  }
  // 更新繪制視圖
  invalidate();
  return true;
 }

按下的時(shí)候堅(jiān)持到觸摸的位置就在點(diǎn)上的時(shí)候,就把這個(gè)點(diǎn)的狀態(tài)改成被按下的狀態(tài),同時(shí)存入到mSelectedPoints被選中點(diǎn)的集合中。并標(biāo)記現(xiàn)在是繪制的狀態(tài)下。同樣在移動(dòng)手指的時(shí)候也是把檢測到的點(diǎn)存儲(chǔ)起來,修改狀態(tài)為按下。當(dāng)手指離開屏幕的時(shí)候,把標(biāo)記改為不在繪制。然后通過invalidate()方法更新視圖(會(huì)去調(diào)用onDraw方法繪制)。
通過上面的步驟,我們把選中的點(diǎn)都收集了起來,接下來就是繪制兩個(gè)點(diǎn)連接線:

/**
  * 繪制兩點(diǎn)之間的線
  * @param canvas
  * @param a
  * @param b
*/
 private void drawLine(Canvas canvas, Point a, Point b){
  if (a.state == Point.STATE_PRESS){
   canvas.drawLine(a.x, a.y, b.x, b.y, mPressPaint);
  }
  if (a.state == Point.STATE_ERROR){
   canvas.drawLine(a.x, a.y, b.x, b.y, mErrorPaint);
  }
 }

繪制兩點(diǎn)的連接線比較簡單,我們只繪制按下和錯(cuò)誤時(shí)候的連線。這是繪制單條連接線的。而我們的手勢密碼路徑是有多條的,繼續(xù)看:

 /**
  * 繪制所有的線
  * @param canvas
  */
 private void drawLines(Canvas canvas){
  if (mSelectedPoints.size() > 0){
   // 從第一個(gè)被選中的點(diǎn)開始繪制
   Point a = mSelectedPoints.get(0);
   for (int i = 1; i < mSelectedPoints.size(); i++){
    Point b = mSelectedPoints.get(i);
    drawLine(canvas, a, b); // 連接兩個(gè)點(diǎn)
    a = b; // 把下一個(gè)點(diǎn)作為下一次繪制的第一個(gè)點(diǎn)
   }
   if (isDraw){// 如果還在繪制狀態(tài),那就繼續(xù)繪制連接線
    drawLine(canvas, a, new Point(mX, mY));
   }
  }
 }

如果被選中點(diǎn)的集合不是空的,那我們選擇從第一個(gè)被選中點(diǎn)開始繪制連接線,遍歷所有被選中點(diǎn)的時(shí)候就要從第二個(gè)點(diǎn)開始也就是index為1的時(shí)候,繪制完一個(gè)點(diǎn),就要把下一次繪制連接線的起點(diǎn)改為這一次的連接線的終點(diǎn),也是 a=b;這句的作用。所有被選中的點(diǎn)繪制完后,如果當(dāng)前還處在繪制狀態(tài)(手機(jī)沒有離開屏幕),那我們就new一個(gè)手指觸摸位置作為連接線的終點(diǎn)。好了所有的線都繪制完了,那我們只要在onDraw調(diào)用就好了:

@Override
 protected void onDraw(Canvas canvas) {

  // 繪制點(diǎn)
  drawPoints(canvas);

  // 繪制連線
  drawLines(canvas);
 }

這樣繪制的工作基本就完成了,接下來我們需要做一個(gè)用來監(jiān)聽手勢滑動(dòng)完后的接口:

public interface OnDrawFinishedListener{
 boolean onDrawFinished(List<Integer> passPositions);
}

回調(diào)的方法里的passPositions是手勢滑動(dòng)的時(shí)候存儲(chǔ)的九宮格的路徑,對于九宮格路徑的定義如圖:

在onTouchEvent中,當(dāng)Action動(dòng)作是Up的時(shí)候(手指離開屏幕):就會(huì)觸發(fā)手勢密碼繪制完成的接口:

case MotionEvent.ACTION_UP:
    boolean valid = false;
    if (mListener != null && isDraw){
     // 獲取繪制路徑是否正確
     valid = mListener.onDrawFinished(mPassPositions);
    }
    if (!valid){// 判斷繪制路徑不正確的所有被選中的點(diǎn)的狀態(tài)改為出錯(cuò)
     for (Point p: mSelectedPoints){
      p.state = Point.STATE_ERROR;
     }
    }
    isDraw = false;
    break;

當(dāng)設(shè)置了監(jiān)聽接口,并且還處于繪制狀態(tài)的時(shí)候,回調(diào)接口把路徑傳出去給實(shí)現(xiàn)這個(gè)接口的使用者,然后在實(shí)現(xiàn)這個(gè)接口方法的地方判斷和之前設(shè)置存儲(chǔ)的手勢密碼是否一致,如果不一致返回為false。然后去修改所有的被選中的點(diǎn)的狀態(tài)為錯(cuò)誤的。然后invalidate()去更新視圖。

路徑給出去了,在最初設(shè)定的時(shí)候可以用md5等不可逆的加密方式存儲(chǔ)在手機(jī)中。在需要解鎖的時(shí)候,拿到這個(gè)md5值和解鎖時(shí)候繪制的路徑的md5值做比較就可以了:

// 這個(gè)自定義視圖的使用方法:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"  android:layout_width="match_parent"
 android:layout_height="match_parent"
 tools:context="com.jerry.testproject.ui.ScreenLockActivity">

 <com.jerry.testproject.widget.lockview.LockView
  android:id="@+id/lockView"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="#2B2B2B"
  />

</FrameLayout>

LockView lockView = (LockView) findViewById(R.id.lockView);
  lockView.setOnDrawFinishedListener(new LockView.OnDrawFinishedListener() {
   @Override
   public boolean onDrawFinished(List<Integer> passPositions) {
    StringBuilder sb = new StringBuilder();
    for (Integer i :
      passPositions) {
     sb.append(i.intValue());
    }
    // 把字符串md5
    String md5Str = MD5Utils.getMD5String(sb.toString());
    // 比較路徑是否一致
    boolean valid = comparePath(sb.toString());
    if (valid){
     Toast.makeText(ScreenLockActivity.this, "手勢密碼正確!", Toast.LENGTH_SHORT).show();
    } else {
     Toast.makeText(ScreenLockActivity.this, "手勢密碼錯(cuò)誤,請重試!", Toast.LENGTH_SHORT).show();
    }

    return valid;
   }
  });

至此自定義九宮格手勢密碼View介紹就結(jié)束了。

下面附上控件的源碼和所用到的資源:Android九宮格手勢密碼解鎖

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 功能強(qiáng)大的Android滾動(dòng)控件RecyclerView

    功能強(qiáng)大的Android滾動(dòng)控件RecyclerView

    這篇文章主要為大家詳細(xì)介紹了功能強(qiáng)大的Android滾動(dòng)控件RecyclerView,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • Android實(shí)現(xiàn)斷點(diǎn)下載的方法

    Android實(shí)現(xiàn)斷點(diǎn)下載的方法

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)斷點(diǎn)下載的方法,感興趣的小伙伴們可以參考一下
    2016-03-03
  • Android RxJava異步數(shù)據(jù)處理庫使用詳解

    Android RxJava異步數(shù)據(jù)處理庫使用詳解

    RxJava是一種異步數(shù)據(jù)處理庫,也是一種擴(kuò)展的觀察者模式。對于Android開發(fā)者來說,使用RxJava時(shí)也會(huì)搭配RxAndroid,它是RxJava針對Android平臺(tái)的一個(gè)擴(kuò)展,用于Android 開發(fā),它提供了響應(yīng)式擴(kuò)展組件,使用RxAndroid的調(diào)度器可以解決Android多線程問題
    2022-11-11
  • Okhttp、Retrofit進(jìn)度獲取的方法(一行代碼搞定)

    Okhttp、Retrofit進(jìn)度獲取的方法(一行代碼搞定)

    本篇文章主要介紹了Okhttp、Retrofit進(jìn)度獲取的方法(一行代碼搞定),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-04-04
  • Jetpack Compose按鈕組件使用實(shí)例詳細(xì)講解

    Jetpack Compose按鈕組件使用實(shí)例詳細(xì)講解

    這篇文章主要介紹了Jetpack Compose按鈕組件使用實(shí)例,按鈕組件Button是用戶和系統(tǒng)交互的重要組件之一,它按照Material Design風(fēng)格實(shí)現(xiàn),我們先看下Button的參數(shù)列表,通過參數(shù)列表了解下Button的整體功能
    2023-04-04
  • Android實(shí)現(xiàn)長圖展開與收起效果

    Android實(shí)現(xiàn)長圖展開與收起效果

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)長圖展開與收起效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • Android?ViewModel創(chuàng)建不受橫豎屏切換影響原理詳解

    Android?ViewModel創(chuàng)建不受橫豎屏切換影響原理詳解

    這篇文章主要為大家介紹了Android?ViewModel創(chuàng)建不受橫豎屏切換影響原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • 詳細(xì)分析Android中onTouch事件傳遞機(jī)制

    詳細(xì)分析Android中onTouch事件傳遞機(jī)制

    相信不少朋友在剛開始學(xué)習(xí)Android的時(shí)候,對于onTouch相關(guān)的事件一頭霧水。分不清onTouch(),onTouchEvent()和OnClick()之間的關(guān)系和先后順序,所以覺得有必要搞清onTouch事件傳遞的原理。經(jīng)過一段時(shí)間的琢磨以及相關(guān)博客的介紹,這篇文章就給大家詳細(xì)的分析介紹下。
    2016-10-10
  • Android使用VideoView播放本地視頻和網(wǎng)絡(luò)視頻的方法

    Android使用VideoView播放本地視頻和網(wǎng)絡(luò)視頻的方法

    本文將講解如何使用Android視頻播放器VideoView來播放本地視頻和網(wǎng)絡(luò)視頻,實(shí)現(xiàn)起來還是比較簡單的,有需要的可以參考借鑒。
    2016-08-08
  • 在Android中使用SQLite數(shù)據(jù)庫及其操作詳解

    在Android中使用SQLite數(shù)據(jù)庫及其操作詳解

    在?Android?開發(fā)中,使用?SQLite?數(shù)據(jù)庫是一種常見的持久化數(shù)據(jù)存儲(chǔ)方式,本文將通過代碼示例詳細(xì)講解如何在?Android?中創(chuàng)建數(shù)據(jù)庫表、插入數(shù)據(jù)、執(zhí)行查詢操作以及驗(yàn)證查詢結(jié)果,需要的朋友可以參考下
    2024-08-08

最新評論