Android自定義View手勢密碼
Android 自定義View 當(dāng)然是十分重要的,筆者這兩天寫了一個自定義 View 的手勢密碼,和大家分享分享:

首先,我們來創(chuàng)建一個表示點(diǎn)的類,Point.java:
public class Point {
// 點(diǎn)的三種狀態(tài)
public static final int POINT_STATUS_NORMAL = 0;
public static final int POINT_STATUS_CLICK = 1;
public static final int POINT_STATUS_ERROR = 2;
// 默認(rèn)狀態(tài)
public int state = POINT_STATUS_NORMAL;
// 點(diǎn)的坐標(biāo)
public float mX;
public float mY;
public Point(float x,float y){
this.mX = x;
this.mY = y;
}
// 獲取兩個點(diǎn)的距離
public float getInstance(Point a){
return (float) Math.sqrt((mX-a.mX)*(mX-a.mX)+(mY-a.mY)*(mY-a.mY));
}
}
然后我們創(chuàng)建一個 HandleLock.java 繼承自 View,并重寫其三種構(gòu)造方法(不重寫帶兩個參數(shù)的構(gòu)造方法會導(dǎo)致程序出錯):
首先,我們先把后面需要用的變量寫出來,方便大家明白這些變量是干嘛的:
// 三種畫筆
private Paint mNormalPaint;
private Paint mClickPaint;
private Paint mErrorPaint;
// 點(diǎn)的半徑
private float mRadius;
// 九個點(diǎn),使用二維數(shù)組
private Point[][] mPoints = new Point[3][3];
// 保存手勢劃過的點(diǎn)
private ArrayList<Point> mClickPointsList = new ArrayList<Point>();
// 手勢的 x 坐標(biāo),y 坐標(biāo)
private float mHandleX;
private float mHandleY;
private OnDrawFinishListener mListener;
// 保存滑動路徑
private StringBuilder mRoute = new StringBuilder();
// 是否在畫錯誤狀態(tài)
private boolean isDrawError = false;
接下來我們來初始化數(shù)據(jù):
// 初始化數(shù)據(jù)
private void initData() {
// 初始化三種畫筆,正常狀態(tài)為灰色,點(diǎn)下狀態(tài)為藍(lán)色,錯誤為紅色
mNormalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mNormalPaint.setColor(Color.parseColor("#ABABAB"));
mClickPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mClickPaint.setColor(Color.parseColor("#1296db"));
mErrorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mErrorPaint.setColor(Color.parseColor("#FB0C13"));
// 獲取點(diǎn)間隔
float offset = 0;
if (getWidth() > getHeight()) {
// 橫屏
offset = getHeight() / 7;
mRadius = offset / 2;
mPoints[0][0] = new Point(getWidth() / 2 - offset * 2, offset + mRadius);
mPoints[0][1] = new Point(getWidth() / 2, offset + mRadius);
mPoints[0][2] = new Point(getWidth() / 2 + offset * 2, offset + mRadius);
mPoints[1][0] = new Point(getWidth() / 2 - offset * 2, offset * 3 + mRadius);
mPoints[1][1] = new Point(getWidth() / 2, offset * 3 + mRadius);
mPoints[1][2] = new Point(getWidth() / 2 + offset * 2, offset * 3 + mRadius);
mPoints[2][0] = new Point(getWidth() / 2 - offset * 2, offset * 5 + mRadius);
mPoints[2][1] = new Point(getWidth() / 2, offset * 5 + mRadius);
mPoints[2][2] = new Point(getWidth() / 2 + offset * 2, offset * 5 + mRadius);
} else {
// 豎屏
offset = getWidth() / 7;
mRadius = offset / 2;
mPoints[0][0] = new Point(offset + mRadius, getHeight() / 2 - 2 * offset);
mPoints[0][1] = new Point(offset * 3 + mRadius, getHeight() / 2 - 2 * offset);
mPoints[0][2] = new Point(offset * 5 + mRadius, getHeight() / 2 - 2 * offset);
mPoints[1][0] = new Point(offset + mRadius, getHeight() / 2);
mPoints[1][1] = new Point(offset * 3 + mRadius, getHeight() / 2);
mPoints[1][2] = new Point(offset * 5 + mRadius, getHeight() / 2);
mPoints[2][0] = new Point(offset + mRadius, getHeight() / 2 + 2 * offset);
mPoints[2][1] = new Point(offset * 3 + mRadius, getHeight() / 2 + 2 * offset);
mPoints[2][2] = new Point(offset * 5 + mRadius, getHeight() / 2 + 2 * offset);
}
}
大家可以看到,我來給點(diǎn)定坐標(biāo)是,是按照比較窄的邊的 1/7 作為點(diǎn)的直徑,這樣保證了,不管你怎么定義 handleLock 的寬高,都可以使里面的九個點(diǎn)看起來位置很舒服。
接下來我們就需要寫一些函數(shù),將點(diǎn)、線繪制到控件上,我自己把繪制分成了三部分,一部分是點(diǎn),一部分是點(diǎn)與點(diǎn)之間的線,一部分是手勢的小點(diǎn)和手勢到最新點(diǎn)的線。
// 畫點(diǎn),按照我們選擇的半徑畫九個圓
private void drawPoints(Canvas canvas) {
// 便利所有的點(diǎn),并且判斷這些點(diǎn)的狀態(tài)
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
Point point = mPoints[i][j];
switch (point.state) {
case Point.POINT_STATUS_NORMAL:
canvas.drawCircle(point.mX, point.mY, mRadius, mNormalPaint);
break;
case Point.POINT_STATUS_CLICK:
canvas.drawCircle(point.mX, point.mY, mRadius, mClickPaint);
break;
case Point.POINT_STATUS_ERROR:
canvas.drawCircle(point.mX, point.mY, mRadius, mErrorPaint);
break;
default:
break;
}
}
}
}
// 畫點(diǎn)與點(diǎn)之間的線
private void drawLines(Canvas canvas) {
// 判斷手勢是否已經(jīng)劃過點(diǎn)了
if (mClickPointsList.size() > 0) {
Point prePoint = mClickPointsList.get(0);
// 將所有已選擇點(diǎn)的按順序連線
for (int i = 1; i < mClickPointsList.size(); i++) {
// 判斷已選擇點(diǎn)的狀態(tài)
if (prePoint.state == Point.POINT_STATUS_CLICK) {
mClickPaint.setStrokeWidth(7);
canvas.drawLine(prePoint.mX, prePoint.mY, mClickPointsList.get(i).mX, mClickPointsList.get(i).mY, mClickPaint);
}
if (prePoint.state == Point.POINT_STATUS_ERROR) {
mErrorPaint.setStrokeWidth(7);
canvas.drawLine(prePoint.mX, prePoint.mY, mClickPointsList.get(i).mX, mClickPointsList.get(i).mY, mErrorPaint);
}
prePoint = mClickPointsList.get(i);
}
}
}
// 畫手勢點(diǎn)
private void drawFinger(Canvas canvas) {
// 有選擇點(diǎn)后再出現(xiàn)手勢點(diǎn)
if (mClickPointsList.size() > 0) {
canvas.drawCircle(mHandleX, mHandleY, mRadius / 2, mClickPaint);
}
// 最新點(diǎn)到手指的連線,判斷是否有已選擇的點(diǎn),有才能畫
if (mClickPointsList.size() > 0) {
canvas.drawLine(mClickPointsList.get(mClickPointsList.size() - 1).mX, mClickPointsList.get(mClickPointsList.size() - 1).mY,
mHandleX, mHandleY, mClickPaint);
}
}
上面的代碼我們看到需要使用到手勢劃過的點(diǎn),我們是怎么選擇的呢?
// 獲取手指移動中選取的點(diǎn)
private int[] getPositions() {
Point point = new Point(mHandleX, mHandleY);
int[] position = new int[2];
// 遍歷九個點(diǎn),看手勢的坐標(biāo)是否在九個圓內(nèi),有則返回這個點(diǎn)的兩個下標(biāo)
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (mPoints[i][j].getInstance(point) <= mRadius) {
position[0] = i;
position[1] = j;
return position;
}
}
}
return null;
}
我們需要重寫其 onTouchEvent 來通過手勢動作來提交選擇的點(diǎn),并更新視圖:
// 重寫點(diǎn)擊事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// 獲取手勢的坐標(biāo)
mHandleX = event.getX();
mHandleY = event.getY();
int[] position;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
position = getPositions();
// 判斷點(diǎn)下時是否選擇到點(diǎn)
if (position != null) {
// 添加到已選擇點(diǎn)中,并改變其狀態(tài)
mClickPointsList.add(mPoints[position[0]][position[1]]);
mPoints[position[0]][position[1]].state = Point.POINT_STATUS_CLICK;
// 保存路徑,依次保存其橫縱下標(biāo)
mRoute.append(position[0]);
mRoute.append(position[1]);
}
break;
case MotionEvent.ACTION_MOVE:
position = getPositions();
// 判斷手勢移動時是否選擇到點(diǎn)
if (position != null) {
// 判斷當(dāng)前選擇的點(diǎn)是否已經(jīng)被選擇過
if (!mClickPointsList.contains(mPoints[position[0]][position[1]])) {
// 添加到已選擇點(diǎn)中,并改變其狀態(tài)
mClickPointsList.add(mPoints[position[0]][position[1]]);
mPoints[position[0]][position[1]].state = Point.POINT_STATUS_CLICK;
// 保存路徑,依次保存其橫縱下標(biāo)
mRoute.append(position[0]);
mRoute.append(position[1]);
}
}
break;
case MotionEvent.ACTION_UP:
// 重置數(shù)據(jù)
resetData();
break;
default:
break;
}
// 更新視圖
invalidate();
return true;
}
// 重置數(shù)據(jù)
private void resetData() {
// 將所有選擇過的點(diǎn)的狀態(tài)改為正常
for (Point point :
mClickPointsList) {
point.state = Point.POINT_STATUS_NORMAL;
}
// 清空已選擇點(diǎn)
mClickPointsList.clear();
// 清空保存的路徑
mRoute = new StringBuilder();
// 不再畫錯誤狀態(tài)
isDrawError = false;
}
那我們怎么繪制視圖呢?我們通過重寫其 onDraw() 方法:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 判斷是否畫錯誤狀態(tài),畫錯誤狀態(tài)不需要畫手勢點(diǎn)已經(jīng)于最新選擇點(diǎn)的連線
if (isDrawError) {
drawPoints(canvas);
drawLines(canvas);
} else {
drawPoints(canvas);
drawLines(canvas);
drawFinger(canvas);
}
}
那么這個手勢密碼繪制過程就結(jié)束了,但是整個控件還沒有結(jié)束,我們還需要給它一個監(jiān)聽器,監(jiān)聽其繪制完成,選擇后續(xù)事件:
private OnDrawFinishListener mListener;
// 定義繪制完成的接口
public interface OnDrawFinishListener {
public boolean drawFinish(String route);
}
// 定義繪制完成的方法,傳入接口
public void setOnDrawFinishListener(OnDrawFinishListener listener) {
this.mListener = listener;
}
然后我們就需要在手勢離開的時候 ,來進(jìn)行繪制完成時的事件:
case MotionEvent.ACTION_UP:
// 完成時回調(diào)繪制完成的方法,返回比對結(jié)果,判斷手勢密碼是否正確
mListener.drawFinish(mRoute.toString());
// 返回錯誤,則將所有已選擇點(diǎn)狀態(tài)改為錯誤
if (!mListener.drawFinish(mRoute.toString())) {
for (Point point :
mClickPointsList) {
point.state = Point.POINT_STATUS_ERROR;
}
// 將是否繪制錯誤設(shè)為 true
isDrawError = true;
// 刷新視圖
invalidate();
// 這里我們使用 handler 異步操作,使其錯誤狀態(tài)保持 0.5s
new Thread(new Runnable() {
@Override
public void run() {
if (!mListener.drawFinish(mRoute.toString())) {
Message message = new Message();
message.arg1 = 0;
handler.sendMessage(message);
}
}
}).run();
} else {
resetData();
}
invalidate();
break;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.arg1) {
case 0:
try {
// 沉睡 0.5s
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 重置數(shù)據(jù),并刷新視圖
resetData();
invalidate();
break;
default:
break;
}
}
};
好了,handleLock,整個過程就結(jié)束了,筆者這里定義了一個監(jiān)聽器只是給大家提供一種思路,筆者將保存的大路徑傳給了使用者,是為了保證使用者可以自己保存密碼,并作相關(guān)操作,大家也可以使用 HandleLock 來 保存密碼,不傳給使用者,根據(jù)自己的需求寫出更多更豐富的監(jiān)聽器,而且這里筆者在 MotionEvent.ACTION_UP 中直接回調(diào)了 drawFinish() 方法,就意味著要使用該 HandleLock 就必須給它設(shè)置監(jiān)聽器。
接下來我們說說 HandleLock 的使用,首先是在布局文件中使用:
<com.example.a01378359.testapp.lock.HandleLock android:id="@+id/handlelock_test" android:layout_width="match_parent" android:layout_height="match_parent" />
接下來是代碼中使用:
handleLock = findViewById(R.id.handlelock_test);
handleLock.setOnDrawFinishListener(new HandleLock.OnDrawFinishListener() {
@Override
public boolean drawFinish(String route) {
// 第一次滑動,則保存密碼
if (count == 0){
password = route;
count++;
Toast.makeText(LockTestActivity.this,"已保存密碼",Toast.LENGTH_SHORT).show();
return true;
}else {
// 與保存密碼比較,返回結(jié)果,并且做出相應(yīng)事件
if (password.equals(route)){
Toast.makeText(LockTestActivity.this,"密碼正確",Toast.LENGTH_SHORT).show();
return true;
}else {
Toast.makeText(LockTestActivity.this,"密碼錯誤",Toast.LENGTH_SHORT).show();
return false;
}
}
}
});
項(xiàng)目地址:源代碼
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android自定義實(shí)現(xiàn)轉(zhuǎn)盤菜單
旋轉(zhuǎn)菜單是一種占用空間較大,實(shí)用性稍弱的UI,本文主要為大家詳細(xì)介紹了Android如何自定義實(shí)現(xiàn)轉(zhuǎn)盤菜單,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考下2023-12-12
Android 指紋識別詳解及實(shí)現(xiàn)方法
本文主要介紹Android 指紋識別的知識,這里整理了詳細(xì)的資料和簡單實(shí)現(xiàn)代碼,有開發(fā)這部分的朋友可以參考下2016-09-09
Kotlin自定義實(shí)現(xiàn)支付密碼數(shù)字鍵盤的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Kotlin如何自定義實(shí)現(xiàn)支付密碼數(shù)字鍵盤的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07
使用Chrome瀏覽器調(diào)試Android App詳解
這篇文章主要介紹了使用Chrome瀏覽器調(diào)試Android App詳解,本網(wǎng)講解了使用Facebook開源Stetho實(shí)現(xiàn)在Chrome中調(diào)試Android App中,需要的朋友可以參考下2015-05-05
Android電量優(yōu)化提高手機(jī)續(xù)航
這篇文章主要為大家介紹了Android電量優(yōu)化提高你的手機(jī)續(xù)航示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
Android單片機(jī)與藍(lán)牙模塊通信實(shí)例代碼
這篇文章主要介紹了Android單片機(jī)與藍(lán)牙模塊通信實(shí)例代碼,非常實(shí)用,特此分享給大家,需要的朋友可以參考下2016-05-05

