編輯:關於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中的應用。
Android仿Win8界面開發
本文將要模仿Win8界面的一個設計,一個一個的方塊。方法很簡單。這裡自己把圖片改改就可以成為自己想要的界面了。1、首先來看看自定義的MyImageView:package
【源碼分析】Android系統啟動流程
Android的啟動過程是從init開始的,它是所有後續進程的祖進程。系統啟動的過程可以大致分為以下幾個步驟1,init.c的啟動 掛載目錄 初始化 解析配置文件2
深入淺析Android坐標系統
1 背景去年有很多人私信告訴我讓說說自定義控件,其實通觀網絡上的很多博客都在講各種自定義控件,但是大多數都是授之以魚,卻很少有較為系統性授之於漁的文章,同時由
Android ListView構建支持單選和多選的投票項目
引言我們在android的APP開發中有時候會碰到提供一個選項列表供用戶選擇的需求,如在投票類型的項目中,我們提供一些主題給用戶選擇,每個主題有若干選項,用戶對這些主題的