您好,登錄后才能下訂單哦!
Android 自定義View 當(dāng)然是十分重要的,筆者這兩天寫(xiě)了一個(gè)自定義 View 的手勢(shì)密碼,和大家分享分享:
首先,我們來(lái)創(chuàng)建一個(gè)表示點(diǎn)的類(lèi),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; } // 獲取兩個(gè)點(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)建一個(gè) HandleLock.java 繼承自 View,并重寫(xiě)其三種構(gòu)造方法(不重寫(xiě)帶兩個(gè)參數(shù)的構(gòu)造方法會(huì)導(dǎo)致程序出錯(cuò)):
首先,我們先把后面需要用的變量寫(xiě)出來(lái),方便大家明白這些變量是干嘛的:
// 三種畫(huà)筆 private Paint mNormalPaint; private Paint mClickPaint; private Paint mErrorPaint; // 點(diǎn)的半徑 private float mRadius; // 九個(gè)點(diǎn),使用二維數(shù)組 private Point[][] mPoints = new Point[3][3]; // 保存手勢(shì)劃過(guò)的點(diǎn) private ArrayList<Point> mClickPointsList = new ArrayList<Point>(); // 手勢(shì)的 x 坐標(biāo),y 坐標(biāo) private float mHandleX; private float mHandleY; private OnDrawFinishListener mListener; // 保存滑動(dòng)路徑 private StringBuilder mRoute = new StringBuilder(); // 是否在畫(huà)錯(cuò)誤狀態(tài) private boolean isDrawError = false; 接下來(lái)我們來(lái)初始化數(shù)據(jù): // 初始化數(shù)據(jù) private void initData() { // 初始化三種畫(huà)筆,正常狀態(tài)為灰色,點(diǎn)下?tīng)顟B(tài)為藍(lán)色,錯(cuò)誤為紅色 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); } }
大家可以看到,我來(lái)給點(diǎn)定坐標(biāo)是,是按照比較窄的邊的 1/7 作為點(diǎn)的直徑,這樣保證了,不管你怎么定義 handleLock 的寬高,都可以使里面的九個(gè)點(diǎn)看起來(lái)位置很舒服。
接下來(lái)我們就需要寫(xiě)一些函數(shù),將點(diǎn)、線(xiàn)繪制到控件上,我自己把繪制分成了三部分,一部分是點(diǎn),一部分是點(diǎn)與點(diǎn)之間的線(xiàn),一部分是手勢(shì)的小點(diǎn)和手勢(shì)到最新點(diǎn)的線(xiàn)。
// 畫(huà)點(diǎn),按照我們選擇的半徑畫(huà)九個(gè)圓 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; } } } } // 畫(huà)點(diǎn)與點(diǎn)之間的線(xiàn) private void drawLines(Canvas canvas) { // 判斷手勢(shì)是否已經(jīng)劃過(guò)點(diǎn)了 if (mClickPointsList.size() > 0) { Point prePoint = mClickPointsList.get(0); // 將所有已選擇點(diǎn)的按順序連線(xià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); } } } // 畫(huà)手勢(shì)點(diǎn) private void drawFinger(Canvas canvas) { // 有選擇點(diǎn)后再出現(xiàn)手勢(shì)點(diǎn) if (mClickPointsList.size() > 0) { canvas.drawCircle(mHandleX, mHandleY, mRadius / 2, mClickPaint); } // 最新點(diǎn)到手指的連線(xiàn),判斷是否有已選擇的點(diǎn),有才能畫(huà) if (mClickPointsList.size() > 0) { canvas.drawLine(mClickPointsList.get(mClickPointsList.size() - 1).mX, mClickPointsList.get(mClickPointsList.size() - 1).mY, mHandleX, mHandleY, mClickPaint); } }
上面的代碼我們看到需要使用到手勢(shì)劃過(guò)的點(diǎn),我們是怎么選擇的呢?
// 獲取手指移動(dòng)中選取的點(diǎn) private int[] getPositions() { Point point = new Point(mHandleX, mHandleY); int[] position = new int[2]; // 遍歷九個(gè)點(diǎn),看手勢(shì)的坐標(biāo)是否在九個(gè)圓內(nèi),有則返回這個(gè)點(diǎn)的兩個(gè)下標(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; }
我們需要重寫(xiě)其 onTouchEvent 來(lái)通過(guò)手勢(shì)動(dòng)作來(lái)提交選擇的點(diǎn),并更新視圖:
// 重寫(xiě)點(diǎn)擊事件 @Override public boolean onTouchEvent(MotionEvent event) { // 獲取手勢(shì)的坐標(biāo) mHandleX = event.getX(); mHandleY = event.getY(); int[] position; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: position = getPositions(); // 判斷點(diǎn)下時(shí)是否選擇到點(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(); // 判斷手勢(shì)移動(dòng)時(shí)是否選擇到點(diǎn) if (position != null) { // 判斷當(dāng)前選擇的點(diǎn)是否已經(jīng)被選擇過(guò) 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() { // 將所有選擇過(guò)的點(diǎn)的狀態(tài)改為正常 for (Point point : mClickPointsList) { point.state = Point.POINT_STATUS_NORMAL; } // 清空已選擇點(diǎn) mClickPointsList.clear(); // 清空保存的路徑 mRoute = new StringBuilder(); // 不再畫(huà)錯(cuò)誤狀態(tài) isDrawError = false; }
那我們?cè)趺蠢L制視圖呢?我們通過(guò)重寫(xiě)其 onDraw() 方法:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 判斷是否畫(huà)錯(cuò)誤狀態(tài),畫(huà)錯(cuò)誤狀態(tài)不需要畫(huà)手勢(shì)點(diǎn)已經(jīng)于最新選擇點(diǎn)的連線(xiàn) if (isDrawError) { drawPoints(canvas); drawLines(canvas); } else { drawPoints(canvas); drawLines(canvas); drawFinger(canvas); } }
那么這個(gè)手勢(shì)密碼繪制過(guò)程就結(jié)束了,但是整個(gè)控件還沒(méi)有結(jié)束,我們還需要給它一個(gè)監(jiān)聽(tīng)器,監(jiān)聽(tīng)其繪制完成,選擇后續(xù)事件:
private OnDrawFinishListener mListener; // 定義繪制完成的接口 public interface OnDrawFinishListener { public boolean drawFinish(String route); } // 定義繪制完成的方法,傳入接口 public void setOnDrawFinishListener(OnDrawFinishListener listener) { this.mListener = listener; }
然后我們就需要在手勢(shì)離開(kāi)的時(shí)候 ,來(lái)進(jìn)行繪制完成時(shí)的事件:
case MotionEvent.ACTION_UP: // 完成時(shí)回調(diào)繪制完成的方法,返回比對(duì)結(jié)果,判斷手勢(shì)密碼是否正確 mListener.drawFinish(mRoute.toString()); // 返回錯(cuò)誤,則將所有已選擇點(diǎn)狀態(tài)改為錯(cuò)誤 if (!mListener.drawFinish(mRoute.toString())) { for (Point point : mClickPointsList) { point.state = Point.POINT_STATUS_ERROR; } // 將是否繪制錯(cuò)誤設(shè)為 true isDrawError = true; // 刷新視圖 invalidate(); // 這里我們使用 handler 異步操作,使其錯(cuò)誤狀態(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,整個(gè)過(guò)程就結(jié)束了,筆者這里定義了一個(gè)監(jiān)聽(tīng)器只是給大家提供一種思路,筆者將保存的大路徑傳給了使用者,是為了保證使用者可以自己保存密碼,并作相關(guān)操作,大家也可以使用 HandleLock 來(lái) 保存密碼,不傳給使用者,根據(jù)自己的需求寫(xiě)出更多更豐富的監(jiān)聽(tīng)器,而且這里筆者在 MotionEvent.ACTION_UP 中直接回調(diào)了 drawFinish() 方法,就意味著要使用該 HandleLock 就必須給它設(shè)置監(jiān)聽(tīng)器。
接下來(lái)我們說(shuō)說(shuō) HandleLock 的使用,首先是在布局文件中使用:
<com.example.a01378359.testapp.lock.HandleLock android:id="@+id/handlelock_test" android:layout_width="match_parent" android:layout_height="match_parent" />
接下來(lái)是代碼中使用:
handleLock = findViewById(R.id.handlelock_test); handleLock.setOnDrawFinishListener(new HandleLock.OnDrawFinishListener() { @Override public boolean drawFinish(String route) { // 第一次滑動(dòng),則保存密碼 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,"密碼錯(cuò)誤",Toast.LENGTH_SHORT).show(); return false; } } } });
項(xiàng)目地址:源代碼
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。