編輯:關於Android編程
本文目的如下:
1、加一個設置初始密碼的功能
2、讓手勢單點生效
3、讓繪制路徑的中間點自動加入軌跡(例如選中第一排的1位和3位時2位也能自動選中)
4、一些其它方面的優化
一、加入設置初始密碼的功能
設置初始密碼時,需要用戶先輸入一個合法的密碼(例如長度不小於4),然後提示輸入確認密碼,請求用戶再次輸入,直到再次輸入正確。
也就是說,第一次只做長度判斷,長度足夠就 setAnswer(),第二次就進入正常判斷流程 checkAnswer() ,因此我們需要一個 boolean 型來標識是否是初次設置密碼。
邏輯比較簡單,在 ACTION_UP 事件的回調裡判斷 firstSet 標志,為 false 時 checkAnswer(),為 true 時 setAnswer()。
首先我們需要一個 firstSet 標識和 set 方法:
// 默認false private boolean isFirstSet = false; // 是否是初次設置密碼 public void isFirstSet(boolean isFirstSet) { this.isFirstSet = isFirstSet; }
接下來我們需要一個回調接口,在OnGestureLockViewListener接口裡新增一個onFirstSetPattern():
// listener public interface OnGestureLockViewListener { // 單獨選中的元素的id void onBlockSelected(int cId); // 是否匹配 void onGestureEvent(boolean matched); // 超過嘗試次數 void onUnmatchedExceedBoundary(); // 首次設置密碼 void onFirstSetPattern(boolean patternOk); }
我為了看起來方便就沒有遵循編碼規范,大家不要學我:P
另外這個onBlockSelected()個人感覺實際用處不大,刪掉也行。
ACTION_UP事件裡原來的回調部分是這樣的:
case MotionEvent.ACTION_UP: mPaint.setColor(mFingerUpColor); mPaint.setAlpha(255); this.mTryTimes--; // 回調是否成功 if (mOnGestureLockViewListener != null && mChoose.size() > 0) { isAnswerRight = checkAnswer(); mOnGestureLockViewListener.onGestureEvent(isAnswerRight); setViewColor(isAnswerRight); if (this.mTryTimes == 0) { mOnGestureLockViewListener.onUnmatchedExceedBoundary(); } } ...
現在我們既然有setViewColor()方法那麼原來的mPaint.setColor和setAlpha就可以刪掉了,而且我想要密碼輸錯時再this.mTryTimes--(這樣可以避免最後一次輸對密碼時調用onUnmatchedExceedBoundary())
具體邏輯比較簡單,就不畫圖了(設置初始密碼時最小長度我設的4):
case MotionEvent.ACTION_UP: // 回調是否成功 if (mOnGestureLockViewListener != null && mChoose.size() > 0) { //如果是初次設置圖案,不需要checkAnswer(),但需要setAnswer() if (isFirstSet) { boolean patternOk = !(mChoose.size() < 4); if (patternOk) { setAnswer(mChoose); isFirstSet = false; } setViewColor(patternOk); mOnGestureLockViewListener.onFirstSetPattern(patternOk); } else { isAnswerRight = checkAnswer(); if (!isAnswerRight) { this.mTryTimes--; } setViewColor(isAnswerRight); mOnGestureLockViewListener.onGestureEvent(isAnswerRight); if (this.mTryTimes == 0) { // 剩余次數為0時進行一些處理(在使用該手勢密碼的activity中覆寫) mOnGestureLockViewListener.onUnmatchedExceedBoundary(); } } } ...
/** * 存儲答案 */ private ListmAnswer = new ArrayList<>(); /** * 設置答案 * @param answer */ private void setAnswer(List answer) { // 直接用賦值語句賦的是對象的引用,新人比較容易坑在這種地方 this.mAnswer.clear(); for (int i = 0; i < answer.size(); i++) { this.mAnswer.add(answer.get(i)); } }
很明顯我不希望外部使用傳入List的方式設置答案,所以重載setAnswer()並對外公開。
由於服務器返回數據多為字符串,所以我這裡也采用String型,具體想怎樣設計答案的規范可以自由發揮:
/** * 對外公布設置答案的方法 * @param answer */ public void setAnswer(String answer) { mAnswer.clear(); for (int i = 0; i < answer.length(); i++) { mAnswer.add((int) answer.charAt(i) - 48); } }
checkAnswer也得改:
private boolean checkAnswer() { return mChoose.equals(mAnswer); }
mGesture = (GestureLockViewGroup) findViewById(R.id.gesture_lock_view_group); mGesture.setAnswer("12369"); mGesture.isFirstSet(true);// 調用 mGesture.setOnGestureLockViewListener(new GestureLockViewGroup.OnGestureLockViewListener() { @Override public void onBlockSelected(int cId) { } @Override public void onGestureEvent(boolean matched) { Toast.makeText(mContext, "Matched:"+matched,Toast.LENGTH_SHORT).show(); } @Override public void onUnmatchedExceedBoundary() { Toast.makeText(mContext, "錯誤5次...",Toast.LENGTH_SHORT).show(); mGesture.setUnMatchExceedBoundary(5); } @Override public void onFirstSetPattern(boolean patternOk) { if (patternOk) { Toast.makeText(mContext, "請再次輸入以確認",Toast.LENGTH_SHORT).show(); } else { Toast.makeText(mContext, "需要四個點以上",Toast.LENGTH_SHORT).show(); } } });當然很明顯因為有 isFirstSet(true),所以setAnswer("12369")語句已經無效了。
另外設置初始密碼時應該對 showPath 為 false 的情況進行規避,即設置初始密碼的前提下令showPath方法失效,由於set方法的調用有先後順序,因此兩個方法都要做改動:
// 對外公開的set方法 public void setShowPath(boolean showPath) { this.showPath = showPath || isFirstSet; }
// 是否是初次設置密碼 public void isFirstSet(boolean isFirstSet) { this.isFirstSet = isFirstSet; setShowPath(true);// 強制顯示路徑 }現在還有個問題就是確認時輸錯5次也會引發onUnmatchedExceedBoundary()回調,最簡單的解決方案就是在isFirstSet為true時設置mTryTimes為一個較大的數字,例如1000,或者外部調用已給的public方法setUnMatchExceedBoundary()。
二、單點生效
當前的手勢密碼控件在單擊某個點的時候並不會觸發答案判斷,而我想仿支付寶,所以先看看代碼。
ACTION_DOWN:
只有個reset(),而添加第一個點的代碼在 ACTION_MOVE 裡,因此只要不觸發 ACTION_MOVE,就無法添加第一個點,我們要做的就是點擊時就觸發MOVE事件的代碼,讓 ACTION_DOWN 走到 ACTION_MOVE 裡。
所以直接把 ACTION_DOWN 裡的 break 刪掉就好:switch (action) { case MotionEvent.ACTION_DOWN: // 重置 reset(); case MotionEvent.ACTION_MOVE: // 初始化畫筆為藍色 setViewColor(true); GestureLockView child = getChildIdByPos(x, y); ...
三、讓繪制路徑中的點自動選擇
現在的手勢如果滑快一點,很容易出現如下這種狀況(第二排第三個點漏掉了):這是因為滑動過快導致的觸摸事件xy坐標值變動太快,造成某個區域都沒有觸發 ACTION_MOVE 事件,也就無法進行子View的添加了。
我想避免太過復雜的情況影響到用戶輸入,因此我想寫個方法,在每次獲取新child的cId時,先判斷這兩個點之間的直線上是否還有其余的點,如果有那麼先加上該點,需求簡單的時候(例如只有3*3)可以直接排列好幾種情況進行添加,但當行列數為4、5、...時,情況就復雜多了,一排可能忽略了兩個點三個點。
合適的算法我暫時也沒有,自己寫了個比較粗糙的方法,邏輯有點亂,注釋也懶得加了,有心的可以自己寫:
// n * n的陣列,首位從0起算,計算公式:cId = x + n*y + 1 private int checkChoose(int cId) { if (mChoose == null || mChoose.size() < 1) { return -1; } int lastX, lastY; int nowX, nowY; int lastId = mChoose.get(mChoose.size() - 1); lastX = (lastId - 1) % mCount; lastY = (lastId - 1) / mCount; nowX = (cId - 1) % mCount; nowY = (cId - 1) / mCount; int signX = compare(lastX, nowX); int signY = compare(lastY, nowY); // 比較x軸y軸間距 int copiesX = (nowX - lastX) * signX; int copiesY = (nowY - lastY) * signY; int copies = copiesX > copiesY ? copiesY : copiesX; if (copiesX == 1 || copiesY == 1) { return -1; } if (signX == 0 || signY == 0) { return lastX + signX + (lastY + signY) * mCount + 1; } if (copies > 1 && copiesX % copies == 0 && copiesY % copies == 0) { return lastX + copiesX / copies * signX + (lastY + copiesY / copies * signY) * mCount + 1; } return -1; } private int compare(int last, int now) { if (now > last) { return 1; } else if (now < last) { return -1; } else { return 0; } }簡單說下邏輯(建議略過這一段):
checkChoose()方法取mChoose的前一個點(lastId)與當前選中的點(cId)進行比較,先根據Id值計算出該點對應的圓圈陣列橫縱坐標值。
例如,一個4*4的手勢陣列,前一個點的 lastId = 1 = 0 + 0*4 + 1,當前選中點 cId = 16 = 3 + 3*4 + 1,即 lastId坐標(0,0),cId坐標(3,3)。
那麼copiesX即為 |3-0| = +3,copiesY同理也為+3,copies取值較小的一方,copies為0說明在同一行或同一列(即signX==0 || signY==0),進行特殊處理,為1時說明兩點之間無遺漏,返回-1,大於1時進行邊的分割,長邊能整除短邊時說明有遺漏的點。
例如3 / 3 = 1說明長邊短邊都分成3份,那麼中間勢必遺漏了兩個點,返回時x坐標值 + signX *1,y坐標值+ signY *1。在圖例的前提下return 6,指第二排第二個點。
又例如 copiesX = 2,copiesY = 3時(n>=4),說明兩點之間的X軸相隔一排,Y軸相隔兩排,選中的兩個點作對角,構成的陣列是3*4,中間沒有能添加的點(左)。
又例如copiesX = 2,copiesY = 4時(n>=5),說明兩點之間的X軸相隔一排,Y軸相隔三排,選中的兩個點作對角,構成的陣列是3*5,那麼第三排的第二個點可以添加(右)。
邏輯十分蛋疼,讀者請自行梳理。。。
因為返回的是依序遞增的點,因此在 ACTION_MOVE 中是使用while 循環對返回值進行判斷,直到返回-1才退出:
case MotionEvent.ACTION_MOVE: // 初始化畫筆為藍色 setViewColor(true); GestureLockView child = getChildIdByPos(x, y); if (child != null) { int cId = child.getId(); if (!mChoose.contains(cId)) { // 循環加入中間點 int subId = checkChoose(cId); Log.e(TAG, "SubId:" +subId); while (subId != -1) { // 1、這部分代碼和以下 2 部分基本一樣,可以抽離出一個方法 mChoose.add(subId); GestureLockView subChild = mGestureLockViews[subId - 1]; subChild.setMode(GestureLockView.Mode.STATUS_FINGER_ON, showPath); // onBlockSelected(cId)這個回調目前基本沒用,不想要可以考慮刪掉 if (mOnGestureLockViewListener != null) { mOnGestureLockViewListener.onBlockSelected(cId); } // 設置指引線的起點 mLastPathX = subChild.getLeft() / 2 + subChild.getRight() / 2; mLastPathY = subChild.getTop() / 2 + subChild.getBottom() / 2; // 非第一個,將兩者使用線連上 mPath.lineTo(mLastPathX, mLastPathY); // 繼續循環 subId = checkChoose(cId); } // 2、中間點加入完成,繼續加入當前選擇的點 mChoose.add(cId); child.setMode(GestureLockView.Mode.STATUS_FINGER_ON, showPath); if (mOnGestureLockViewListener != null) mOnGestureLockViewListener.onBlockSelected(cId); // 設置指引線的起點 mLastPathX = child.getLeft() / 2 + child.getRight() / 2; mLastPathY = child.getTop() / 2 + child.getBottom() / 2; if (mChoose.size() == 1) { // 當前添加為第一個 mPath.moveTo(mLastPathX, mLastPathY); } else { // 非第一個,將兩者使用線連上 mPath.lineTo(mLastPathX, mLastPathY); } } } // 指引線的終點 mTmpTarget.x = x; mTmpTarget.y = y; break;
四、一些其它方面的優化與修改
例如添加一些方法為自己的APP定制功能,降低耦合和冗余代碼,或者修改下功能外觀等等(例如在checkPositionInChild()中修改靈敏度參數等)還有優化下邏輯算法等等,主要就是自己看代碼了。好吧這條就只是湊個字數。。。
下一篇文章會講講手勢密碼在APP中的應用。
本文將要模仿Win8界面的一個設計,一個一個的方塊。方法很簡單。這裡自己把圖片改改就可以成為自己想要的界面了。1、首先來看看自定義的MyImageView:package
Android的啟動過程是從init開始的,它是所有後續進程的祖進程。系統啟動的過程可以大致分為以下幾個步驟1,init.c的啟動 掛載目錄 初始化 解析配置文件2
1 背景去年有很多人私信告訴我讓說說自定義控件,其實通觀網絡上的很多博客都在講各種自定義控件,但是大多數都是授之以魚,卻很少有較為系統性授之於漁的文章,同時由
引言我們在android的APP開發中有時候會碰到提供一個選項列表供用戶選擇的需求,如在投票類型的項目中,我們提供一些主題給用戶選擇,每個主題有若干選項,用戶對這些主題的