Android 自定義View 當然是十分重要的,筆者這兩天寫了一個自定義 View 的手勢密碼,和大家分享分享:
首先,我們來創(chuàng)建一個表示點的類,Point.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public class Point { // 點的三種狀態(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 ; // 默認狀態(tài) public int state = POINT_STATUS_NORMAL; // 點的坐標 public float mX; public float mY; public Point( float x, float y){ this .mX = x; this .mY = y; } // 獲取兩個點的距離 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,并重寫其三種構造方法(不重寫帶兩個參數的構造方法會導致程序出錯):
首先,我們先把后面需要用的變量寫出來,方便大家明白這些變量是干嘛的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
// 三種畫筆 private Paint mNormalPaint; private Paint mClickPaint; private Paint mErrorPaint; // 點的半徑 private float mRadius; // 九個點,使用二維數組 private Point[][] mPoints = new Point[ 3 ][ 3 ]; // 保存手勢劃過的點 private ArrayList<Point> mClickPointsList = new ArrayList<Point>(); // 手勢的 x 坐標,y 坐標 private float mHandleX; private float mHandleY; private OnDrawFinishListener mListener; // 保存滑動路徑 private StringBuilder mRoute = new StringBuilder(); // 是否在畫錯誤狀態(tài) private boolean isDrawError = false ; 接下來我們來初始化數據: // 初始化數據 private void initData() { // 初始化三種畫筆,正常狀態(tài)為灰色,點下狀態(tài)為藍色,錯誤為紅色 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" )); // 獲取點間隔 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); } } |
大家可以看到,我來給點定坐標是,是按照比較窄的邊的 1/7 作為點的直徑,這樣保證了,不管你怎么定義 handleLock 的寬高,都可以使里面的九個點看起來位置很舒服。
接下來我們就需要寫一些函數,將點、線繪制到控件上,我自己把繪制分成了三部分,一部分是點,一部分是點與點之間的線,一部分是手勢的小點和手勢到最新點的線。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
// 畫點,按照我們選擇的半徑畫九個圓 private void drawPoints(Canvas canvas) { // 便利所有的點,并且判斷這些點的狀態(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 ; } } } } // 畫點與點之間的線 private void drawLines(Canvas canvas) { // 判斷手勢是否已經劃過點了 if (mClickPointsList.size() > 0 ) { Point prePoint = mClickPointsList.get( 0 ); // 將所有已選擇點的按順序連線 for ( int i = 1 ; i < mClickPointsList.size(); i++) { // 判斷已選擇點的狀態(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); } } } // 畫手勢點 private void drawFinger(Canvas canvas) { // 有選擇點后再出現(xiàn)手勢點 if (mClickPointsList.size() > 0 ) { canvas.drawCircle(mHandleX, mHandleY, mRadius / 2 , mClickPaint); } // 最新點到手指的連線,判斷是否有已選擇的點,有才能畫 if (mClickPointsList.size() > 0 ) { canvas.drawLine(mClickPointsList.get(mClickPointsList.size() - 1 ).mX, mClickPointsList.get(mClickPointsList.size() - 1 ).mY, mHandleX, mHandleY, mClickPaint); } } |
上面的代碼我們看到需要使用到手勢劃過的點,我們是怎么選擇的呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 獲取手指移動中選取的點 private int [] getPositions() { Point point = new Point(mHandleX, mHandleY); int [] position = new int [ 2 ]; // 遍歷九個點,看手勢的坐標是否在九個圓內,有則返回這個點的兩個下標 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 來通過手勢動作來提交選擇的點,并更新視圖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
// 重寫點擊事件 @Override public boolean onTouchEvent(MotionEvent event) { // 獲取手勢的坐標 mHandleX = event.getX(); mHandleY = event.getY(); int [] position; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: position = getPositions(); // 判斷點下時是否選擇到點 if (position != null ) { // 添加到已選擇點中,并改變其狀態(tài) mClickPointsList.add(mPoints[position[ 0 ]][position[ 1 ]]); mPoints[position[ 0 ]][position[ 1 ]].state = Point.POINT_STATUS_CLICK; // 保存路徑,依次保存其橫縱下標 mRoute.append(position[ 0 ]); mRoute.append(position[ 1 ]); } break ; case MotionEvent.ACTION_MOVE: position = getPositions(); // 判斷手勢移動時是否選擇到點 if (position != null ) { // 判斷當前選擇的點是否已經被選擇過 if (!mClickPointsList.contains(mPoints[position[ 0 ]][position[ 1 ]])) { // 添加到已選擇點中,并改變其狀態(tài) mClickPointsList.add(mPoints[position[ 0 ]][position[ 1 ]]); mPoints[position[ 0 ]][position[ 1 ]].state = Point.POINT_STATUS_CLICK; // 保存路徑,依次保存其橫縱下標 mRoute.append(position[ 0 ]); mRoute.append(position[ 1 ]); } } break ; case MotionEvent.ACTION_UP: // 重置數據 resetData(); break ; default : break ; } // 更新視圖 invalidate(); return true ; } // 重置數據 private void resetData() { // 將所有選擇過的點的狀態(tài)改為正常 for (Point point : mClickPointsList) { point.state = Point.POINT_STATUS_NORMAL; } // 清空已選擇點 mClickPointsList.clear(); // 清空保存的路徑 mRoute = new StringBuilder(); // 不再畫錯誤狀態(tài) isDrawError = false ; } |
那我們怎么繪制視圖呢?我們通過重寫其 onDraw() 方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Override protected void onDraw(Canvas canvas) { super .onDraw(canvas); // 判斷是否畫錯誤狀態(tài),畫錯誤狀態(tài)不需要畫手勢點已經于最新選擇點的連線 if (isDrawError) { drawPoints(canvas); drawLines(canvas); } else { drawPoints(canvas); drawLines(canvas); drawFinger(canvas); } } |
那么這個手勢密碼繪制過程就結束了,但是整個控件還沒有結束,我們還需要給它一個監(jiān)聽器,監(jiān)聽其繪制完成,選擇后續(xù)事件:
1
2
3
4
5
6
7
8
9
10
11
|
private OnDrawFinishListener mListener; // 定義繪制完成的接口 public interface OnDrawFinishListener { public boolean drawFinish(String route); } // 定義繪制完成的方法,傳入接口 public void setOnDrawFinishListener(OnDrawFinishListener listener) { this .mListener = listener; } |
然后我們就需要在手勢離開的時候 ,來進行繪制完成時的事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
case MotionEvent.ACTION_UP: // 完成時回調繪制完成的方法,返回比對結果,判斷手勢密碼是否正確 mListener.drawFinish(mRoute.toString()); // 返回錯誤,則將所有已選擇點狀態(tài)改為錯誤 if (!mListener.drawFinish(mRoute.toString())) { for (Point point : mClickPointsList) { point.state = Point.POINT_STATUS_ERROR; } // 將是否繪制錯誤設為 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(); } // 重置數據,并刷新視圖 resetData(); invalidate(); break ; default : break ; } } }; |
好了,handleLock,整個過程就結束了,筆者這里定義了一個監(jiān)聽器只是給大家提供一種思路,筆者將保存的大路徑傳給了使用者,是為了保證使用者可以自己保存密碼,并作相關操作,大家也可以使用 HandleLock 來 保存密碼,不傳給使用者,根據自己的需求寫出更多更豐富的監(jiān)聽器,而且這里筆者在 MotionEvent.ACTION_UP 中直接回調了 drawFinish() 方法,就意味著要使用該 HandleLock 就必須給它設置監(jiān)聽器。
接下來我們說說 HandleLock 的使用,首先是在布局文件中使用:
1
2
3
4
|
<com.example.a01378359.testapp.lock.HandleLock android:id= "@+id/handlelock_test" android:layout_width= "match_parent" android:layout_height= "match_parent" /> |
接下來是代碼中使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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 { // 與保存密碼比較,返回結果,并且做出相應事件 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 ; } } } }); |
項目地址:源代碼
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/Young_Time/article/details/80856817