輕松實(shí)現(xiàn)安卓(Android)九宮格解鎖
效果圖

思路
首先我們來(lái)分析一下實(shí)現(xiàn)九宮格解鎖的思路:當(dāng)用戶(hù)的手指觸摸到某一個(gè)點(diǎn)時(shí),先判斷該點(diǎn)是否在九宮格的某一格范圍之內(nèi),若在范圍內(nèi),則該格變成選中的狀態(tài);之后用戶(hù)手指滑動(dòng)的時(shí)候,以該格的圓心為中心,用戶(hù)手指為終點(diǎn),兩點(diǎn)連線。最后當(dāng)用戶(hù)手指抬起時(shí),判斷劃過(guò)的九宮格密碼是否和原先的密碼匹配。
大致的思路流程就是上面這樣的了,下面我們可以來(lái)實(shí)踐一下。
Point 類(lèi)
我們先來(lái)創(chuàng)建一個(gè) Point 類(lèi),用來(lái)表示九宮格鎖的九個(gè)格子。除了坐標(biāo) x ,y 之外,還有三種模式:正常模式、按下模式和錯(cuò)誤模式。根據(jù)模式不同該格子的顏色會(huì)有所不同,這會(huì)在下面中說(shuō)明。
public class Point {
private float x;
private float y;
// 正常模式
public static final int NORMAL_MODE = 1;
// 按下模式
public static final int PRESSED_MODE = 2;
// 錯(cuò)誤模式
public static final int ERROR_MODE = 3;
private int state = NORMAL_MODE;
// 表示該格的密碼,比如“1”、“2”等
private String mark;
public String getMark() {
return mark;
}
public void setMark(String mark) {
this.mark = mark;
}
public Point(float x, float y, String mark) {
this.x = x;
this.y = y;
this.mark = mark;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
}
RotateDegrees類(lèi)
有了上面的 Point 類(lèi)之后,我們還要?jiǎng)?chuàng)建一個(gè) RotateDegrees 類(lèi),主要作用是計(jì)算兩個(gè) Point 坐標(biāo)之間的角度:
public class RotateDegrees {
/**
* 根據(jù)傳入的point計(jì)算出它們之間的角度
* @param a
* @param b
* @return
*/
public static float getDegrees(Point a, Point b) {
float degrees = 0;
float aX = a.getX();
float aY = a.getY();
float bX = b.getX();
float bY = b.getY();
if (aX == bX) {
if (aY < bY) {
degrees = 90;
} else {
degrees = 270;
}
} else if (bY == aY) {
if (aX < bX) {
degrees = 0;
} else {
degrees = 180;
}
} else {
if (aX > bX) {
if (aY > bY) { // 第三象限
degrees = 180 + (float) (Math.atan2(aY - bY, aX - bX) * 180 / Math.PI);
} else { // 第二象限
degrees = 180 - (float) (Math.atan2(bY - aY, aX - bX) * 180 / Math.PI);
}
} else {
if (aY > bY) { // 第四象限
degrees = 360 - (float) (Math.atan2(aY - bY, bX - aX) * 180 / Math.PI);
} else { // 第一象限
degrees = (float) (Math.atan2(bY - aY, bX - aX) * 180 / Math.PI);
}
}
}
return degrees;
}
/**
* 根據(jù)point和(x,y)計(jì)算出它們之間的角度
* @param a
* @param bX
* @param bY
* @return
*/
public static float getDegrees(Point a, float bX, float bY) {
Point b = new Point(bX, bY, null);
return getDegrees(a, b);
}
}
ScreenLockView 類(lèi)
然后我們要先準(zhǔn)備好關(guān)于九宮格的幾張圖片,比如在九宮格的格子中,NORMAL_MODE 模式下是藍(lán)色的,被手指按住時(shí)九宮格的格子是綠色的,也就是對(duì)應(yīng)著上面 Point 類(lèi)的中 PRESSED_MODE 模式,還有 ERROR_MODE 模式下是紅色的。另外還有圓點(diǎn)之間的連線,也是根據(jù)模式不同顏色也會(huì)不同。在這里我就不把圖片貼出來(lái)了。
有了圖片資源之后,我們要做的就是先在構(gòu)造器中加載圖片:
public class ScreenLockView extends View {
private static final String TAG = "ScreenLockView";
// 錯(cuò)誤格子的圖片
private Bitmap errorBitmap;
// 正常格子的圖片
private Bitmap normalBitmap;
// 手指按下時(shí)格子的圖片
private Bitmap pressedBitmap;
// 錯(cuò)誤時(shí)連線的圖片
private Bitmap lineErrorBitmap;
// 手指按住時(shí)連線的圖片
private Bitmap linePressedBitmap;
// 偏移量,使九宮格在屏幕中央
private int offset;
// 九宮格的九個(gè)格子是否已經(jīng)初始化
private boolean init;
// 格子的半徑
private int radius;
// 密碼
private String password = "123456";
// 九個(gè)格子
private Point[][] points = new Point[3][3];
private int width;
private int height;
private Matrix matrix = new Matrix();
private float moveX = -1;
private float moveY = -1;
// 是否手指在移動(dòng)
private boolean isMove;
// 是否可以觸摸,當(dāng)用戶(hù)抬起手指,劃出九宮格的密碼不正確時(shí)為不可觸摸
private boolean isTouch = true;
// 用來(lái)存儲(chǔ)記錄被按下的點(diǎn)
private List<Point> pressedPoint = new ArrayList<>();
// 屏幕解鎖監(jiān)聽(tīng)器
private OnScreenLockListener listener;
public ScreenLockView(Context context) {
this(context, null);
}
public ScreenLockView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ScreenLockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
errorBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap_error);
normalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap_normal);
pressedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap_pressed);
lineErrorBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.line_error);
linePressedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.line_pressed);
radius = normalBitmap.getWidth() / 2;
}
...
}
在構(gòu)造器中我們主要就是把圖片加載完成,并且得到了格子的半徑,即圖片寬度的一半。
之后我們來(lái)看看 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthSize > heightSize) {
offset = (widthSize - heightSize) / 2;
} else {
offset = (heightSize - widthSize) / 2;
}
setMeasuredDimension(widthSize, heightSize);
}
在 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法中,主要得到對(duì)應(yīng)的偏移量,以便在下面的 onDraw(Canvas canvas) 把九宮格繪制在屏幕中央。
下面就是 onDraw(Canvas canvas) 方法:
@Override
protected void onDraw(Canvas canvas) {
if (!init) {
width = getWidth();
height = getHeight();
initPoint();
init = true;
}
// 畫(huà)九宮格的格子
drawPoint(canvas);
if (moveX != -1 && moveY != -1) {
// 畫(huà)直線
drawLine(canvas);
}
}
首先判斷了是否為第一次調(diào)用 onDraw(Canvas canvas) 方法,若為第一次則對(duì) points 進(jìn)行初始化:
// 初始化點(diǎn)
private void initPoint() {
points[0][0] = new Point(width / 4, offset + width / 4, "0");
points[0][1] = new Point(width / 2, offset + width / 4, "1");
points[0][2] = new Point(width * 3 / 4, offset + width / 4, "2");
points[1][0] = new Point(width / 4, offset + width / 2, "3");
points[1][1] = new Point(width / 2, offset + width / 2, "4");
points[1][2] = new Point(width * 3 / 4, offset + width / 2, "5");
points[2][0] = new Point(width / 4, offset + width * 3 / 4, "6");
points[2][1] = new Point(width / 2, offset + width * 3 / 4, "7");
points[2][2] = new Point(width * 3 / 4, offset + width * 3 / 4, "8");
}
在 initPoint() 方法中主要?jiǎng)?chuàng)建了九個(gè)格子,并設(shè)置了相應(yīng)的位置和密碼。初始化完成之后把 init 置為 false ,下次不會(huì)再調(diào)用。
回過(guò)頭再看看 onDraw(Canvas canvas) 中其他的邏輯,接下來(lái)調(diào)用了 drawPoint(canvas) 來(lái)繪制格子:
// 畫(huà)九宮格的格子
private void drawPoint(Canvas canvas) {
for (int i = 0; i < points.length; i++) {
for (int j = 0; j < points[i].length; j++) {
int state = points[i][j].getState();
if (state == Point.NORMAL_MODE) {
canvas.drawBitmap(normalBitmap, points[i][j].getX() - radius, points[i][j].getY() - radius, null);
} else if (state == Point.PRESSED_MODE) {
canvas.drawBitmap(pressedBitmap, points[i][j].getX() - radius, points[i][j].getY() - radius, null);
} else {
canvas.drawBitmap(errorBitmap, points[i][j].getX() - radius, points[i][j].getY() - radius, null);
}
}
}
}
在繪制格子還是很簡(jiǎn)單的,主要分為了三種:普通模式下的格子、按下模式下的格子以及錯(cuò)誤模式下的格子。
onTouchEvent
在繪制好了格子之后,我們先不看最后的 drawLine(canvas) 方法,因?yàn)槔L制直線是和用戶(hù)手指的觸摸事件息息相關(guān)的,所以我們先把目光轉(zhuǎn)向 onTouchEvent(MotionEvent event) 方法:
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isTouch) {
float x = event.getX();
float y = event.getY();
Point point;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 判斷用戶(hù)觸摸的點(diǎn)是否在九宮格的任意一個(gè)格子之內(nèi)
point = isPoint(x, y);
if (point != null) {
point.setState(Point.PRESSED_MODE); // 切換為按下模式
pressedPoint.add(point);
}
break;
case MotionEvent.ACTION_MOVE:
if (pressedPoint.size() > 0) {
point = isPoint(x, y);
if (point != null) {
if (!crossPoint(point)) {
point.setState(Point.PRESSED_MODE);
pressedPoint.add(point);
}
}
moveX = x;
moveY = y;
isMove = true;
}
break;
case MotionEvent.ACTION_UP:
isMove = false;
String tempPwd = "";
for (Point p : pressedPoint) {
tempPwd += p.getMark();
}
if (listener != null) {
listener.getStringPassword(tempPwd);
}
if (tempPwd.equals(password)) {
if (listener != null) {
listener.isPassword(true);
}
} else {
for (Point p : pressedPoint) {
p.setState(Point.ERROR_MODE);
}
isTouch = false;
this.postDelayed(runnable, 1000);
if (listener != null) {
listener.isPassword(false);
}
}
break;
}
invalidate();
}
return true;
}
public interface OnScreenLockListener {
public void getStringPassword(String password);
public void isPassword(boolean flag);
}
public void setOnScreenLockListener(OnScreenLockListener listener) {
this.listener = listener;
}
在 MotionEvent.ACTION_DOWN 中,先在 isPoint(float x, float y) 方法內(nèi)判斷了用戶(hù)觸摸事件的坐標(biāo)點(diǎn)是否在九宮格的任意一格之內(nèi)。如果是,則需要把該九宮格的格子添加到 pressedPoint 中:
// 該觸摸點(diǎn)是否為格子
private Point isPoint(float x, float y) {
Point point;
for (int i = 0; i < points.length; i++) {
for (int j = 0; j < points[i].length; j++) {
point = points[i][j];
if (isContain(point, x, y)) {
return point;
}
}
}
return null;
}
// 該點(diǎn)(x,y)是否被包含
private boolean isContain(Point point, float x, float y) {
// 該點(diǎn)的(x,y)與格子圓心的距離若小于半徑就是被包含了
return Math.sqrt(Math.pow(x - point.getX(), 2f) + Math.pow(y - point.getY(), 2f)) <= radius;
}
接下來(lái)就是要看 MotionEvent.ACTION_MOVE 的邏輯了。一開(kāi)始判斷了用戶(hù)觸摸的點(diǎn)是否為九宮格的某個(gè)格子。但是比 MotionEvent.ACTION_DOWN 還多了一個(gè)步驟:若用戶(hù)觸摸了某個(gè)格子,還要判斷該格子是否已經(jīng)被包含在 pressedPoint 里面了。
// 是否該格子已經(jīng)被包含在pressedPoint里面了
private boolean crossPoint(Point point) {
if (pressedPoint.contains(point)) {
return true;
}
return false;
}
最后來(lái)看看 MotionEvent.ACTION_UP ,把 pressedPoint 里保存的格子遍歷后得到用戶(hù)劃出的密碼,再和預(yù)先設(shè)置的密碼比較,若相同則回調(diào) OnScreenLockListener 監(jiān)聽(tīng)器;不相同則把 pressedPoint 中的所有格子的模式設(shè)置為錯(cuò)誤模式,并在 runnable 中調(diào)用 reset() 清空 pressedPoint ,重繪視圖,再回調(diào)監(jiān)聽(tīng)器。
private Runnable runnable = new Runnable() {
@Override
public void run() {
isTouch = true;
reset();
invalidate();
}
};
// 重置格子
private void reset(){
for (int i = 0; i < points.length; i++) {
for (int j = 0; j < points[i].length; j++) {
points[i][j].setState(Point.NORMAL_MODE);
}
}
pressedPoint.clear();
}
現(xiàn)在我們回過(guò)頭來(lái)看看之前在 onDraw(Canvas canvas) 里面的 drawLine(Canvas canvas) 方法:
// 畫(huà)直線
private void drawLine(Canvas canvas) {
// 將pressedPoint中的所有格子依次遍歷,互相連線
for (int i = 0; i < pressedPoint.size() - 1; i++) {
// 得到當(dāng)前格子
Point point = pressedPoint.get(i);
// 得到下一個(gè)格子
Point nextPoint = pressedPoint.get(i + 1);
// 旋轉(zhuǎn)畫(huà)布
canvas.rotate(RotateDegrees.getDegrees(point, nextPoint), point.getX(), point.getY());
matrix.reset();
// 根據(jù)距離設(shè)置拉伸的長(zhǎng)度
matrix.setScale(getDistance(point, nextPoint) / linePressedBitmap.getWidth(), 1f);
// 進(jìn)行平移
matrix.postTranslate(point.getX(), point.getY() - linePressedBitmap.getWidth() / 2);
if (point.getState() == Point.PRESSED_MODE) {
canvas.drawBitmap(linePressedBitmap, matrix, null);
} else {
canvas.drawBitmap(lineErrorBitmap, matrix, null);
}
// 把畫(huà)布旋轉(zhuǎn)回來(lái)
canvas.rotate(-RotateDegrees.getDegrees(point, nextPoint), point.getX(), point.getY());
}
// 如果是手指在移動(dòng)的情況
if (isMove) {
Point lastPoint = pressedPoint.get(pressedPoint.size() - 1);
canvas.rotate(RotateDegrees.getDegrees(lastPoint, moveX, moveY), lastPoint.getX(), lastPoint.getY());
matrix.reset();
Log.i(TAG, "the distance : " + getDistance(lastPoint, moveX, moveY) / linePressedBitmap.getWidth());
matrix.setScale(getDistance(lastPoint, moveX, moveY) / linePressedBitmap.getWidth(), 1f);
matrix.postTranslate(lastPoint.getX(), lastPoint.getY() - linePressedBitmap.getWidth() / 2);
canvas.drawBitmap(linePressedBitmap, matrix, null);
canvas.rotate(-RotateDegrees.getDegrees(lastPoint, moveX, moveY), lastPoint.getX(), lastPoint.getY());
}
}
// 根據(jù)point和坐標(biāo)點(diǎn)計(jì)算出之間的距離
private float getDistance(Point point, float moveX, float moveY) {
Point b = new Point(moveX,moveY,null);
return getDistance(point,b);
}
// 根據(jù)兩個(gè)point計(jì)算出之間的距離
private float getDistance(Point point, Point nextPoint) {
return (float) Math.sqrt(Math.pow(nextPoint.getX() - point.getX(), 2f) + Math.pow(nextPoint.getY() - point.getY(), 2f));
}
drawLine(Canvas canvas) 整體的邏輯并不復(fù)雜,首先將 pressedPoint 中的所有格子依次遍歷,將它們連線。之后若是用戶(hù)的手指還有滑動(dòng)的話,把最后一個(gè)格子和用戶(hù)手指觸摸的點(diǎn)連線。
總結(jié)
ScreenLockView 中的代碼差不多就是這些了,實(shí)現(xiàn)效果還算不錯(cuò)吧,當(dāng)然你也可以自己設(shè)置喜歡的九宮格圖片,只要替換一下就可以了。如果對(duì)本篇文章有疑問(wèn)可以留言。希望本文的內(nèi)容對(duì)大家開(kāi)發(fā)Android能有所幫助。
- Android 九宮格的實(shí)現(xiàn)方法
- android 九宮格滑動(dòng)解鎖開(kāi)機(jī)實(shí)例源碼學(xué)習(xí)
- Android實(shí)現(xiàn)九宮格(GridView中各項(xiàng)平分空間)的方法
- Android打造流暢九宮格抽獎(jiǎng)活動(dòng)效果
- 輕松實(shí)現(xiàn)Android自定義九宮格圖案解鎖
- Android實(shí)現(xiàn)九宮格解鎖
- Android實(shí)現(xiàn)九宮格橫向左右滑動(dòng)
- Android開(kāi)發(fā)之實(shí)現(xiàn)GridView支付寶九宮格
- Android編程之九宮格實(shí)現(xiàn)方法實(shí)例分析
- Android實(shí)現(xiàn)圖片九宮格
相關(guān)文章
Android自定義扇形倒計(jì)時(shí)實(shí)例代碼
最近工作中需要做一個(gè)倒計(jì)時(shí),是那種一個(gè)圓,慢慢的被吃掉的動(dòng)畫(huà)倒計(jì)時(shí),由于自己是android小白,效果還不是多滿(mǎn)意,先給大家分享實(shí)例代碼,僅供大家參考2017-03-03
Android中利用zxing實(shí)現(xiàn)自己的二維碼掃描識(shí)別詳解
這篇文章主要給大家介紹了關(guān)于Android中利用zxing實(shí)現(xiàn)自己的二維碼掃描識(shí)別的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用zxing具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-09-09
Android使用gallery和imageSwitch制作可左右循環(huán)滑動(dòng)的圖片瀏覽器
本文主要介紹了android使用gallery和imageSwitch制作可左右循環(huán)滑動(dòng)的圖片瀏覽器的示例代碼。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04
Android ListView中動(dòng)態(tài)添加RaidoButton的實(shí)例詳解
這篇文章主要介紹了Android ListView中動(dòng)態(tài)添加RaidoButton的實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-08-08
Android DrawLayout結(jié)合ListView用法實(shí)例
這篇文章主要介紹了Android DrawLayout結(jié)合ListView用法實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
Android視頻處理之動(dòng)態(tài)時(shí)間水印效果
這篇文章主要A為大家詳細(xì)介紹了Android視頻處理之動(dòng)態(tài)時(shí)間水印效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
以一個(gè)著色游戲展開(kāi)講解Android中區(qū)域圖像填色的方法
這篇文章主要介紹了Android中實(shí)現(xiàn)區(qū)域圖像顏色填充的方法,文中以一個(gè)著色游戲?yàn)槔v解了邊界的填充等各種填色操作,需要的朋友可以參考下2016-02-02

