基本效果
背景
爲了方便測試打開彩蛋,同時對用戶隱藏彩蛋。
正確的手勢
可胖、可瘦、可高、可矮、對於雍餘的畫筆不參與計算。
錯誤手勢
1.不完整、亂畫
2.出現重疊
3.過渡傾斜
手勢識別的算法實現:
爲了最優的性能,算法的實現,並沒有記錄手指的完整軌跡,只是在手指移動過程中,識別出曲線的拐點。這些拐點作爲判斷的關鍵點,通過關鍵點的座標,判斷出是否符合棒棒糖手勢。
第一步識別出8個關鍵點
8個點的標註:
[標註圖todo]
起始點:
起始點的獲取非常容易,ACTION_DOWN開始,重置8個關鍵點數組,並記錄起始點。
曲線的拐點,也就是識別出曲線局部的極值
想要避免記錄完整手勢軌跡,就必須在手指一動的過程中,記錄曲線的關鍵點。
連續曲線,極大值與極小值的簡單的理解就是方向突變。
- 在垂直方向上曲線的局部極大值爲:一個向上移動的點,縱座標改爲向下移動,那麼就達到了局部最高點。
- 在垂直方向上曲線的局部極小值爲:一個向下移動的點,縱座標改爲向上移動,那麼就達到了局部最低點。
- 在水平方向上曲線的局部極大值爲:一個向右移動的點,縱座標改爲向左移動,那麼就達到了局部最右點。
- 在水平方向上曲線的局部極小值爲:一個向左移動的點,縱座標改爲向右移動,那麼就達到了局部最左點。
拐點的連續識別
一個拐點識別完成,那就填充下一個拐點,如果並沒有出現拐點,那就更新當前節點的座標爲手指的座標。
第二步,根據關鍵點的座標,判斷是否爲正確手勢
通過判斷關鍵點的座標相對位置,比如起始點,縱座標,需要在其它所有點之下,等等其它點的判斷。
由於不能要求手勢畫太多圈圈,這裏只要求畫出540度的圈,因此,沒有規整成for循環的效果,如果要畫上720度以上的圈,再完成for循環。
關於點位座標的判斷,應當考慮短路算法,一個條件不滿足立馬返回false,而不要把全部點的判斷結果都計算出來,浪費效率。
代碼實現
package com.lollipop;
import android.graphics.Point;
import android.view.MotionEvent;
import android.view.View;
/**
* 棒棒糖路徑檢測
*/
public class LollipopPathDetection implements View.OnTouchListener {
private Upward upward = new Upward();
private Downward downward = new Downward();
private Rightward rightward = new Rightward();
private Leftward leftward = new Leftward();
private Point[] pathPoints;
private Direction[] directions = new Direction[]{null, upward, rightward, downward, leftward, upward, rightward, downward};
private OnDetectListener listener;
public LollipopPathDetection(OnDetectListener listener) {
this.listener = listener;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event == null) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
beginPath((int) event.getX(), (int) event.getY());
break;
case MotionEvent.ACTION_MOVE:
fillPath((int) event.getX(), (int) event.getY());
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
endPath((int) event.getX(), (int) event.getY());
if (listener != null) {
listener.onDetect(judgeLollipopPath(pathPoints));
}
break;
default:
}
return true;
}
public interface OnDetectListener {
void onDetect(boolean isLollipop);
}
private interface Direction {
boolean judgeDirect(Point lastPoint, int x, int y);
}
private static class Upward implements Direction {
@Override
public boolean judgeDirect(Point p, int x, int y) {
return y <= p.y;
}
}
private static class Downward implements Direction {
@Override
public boolean judgeDirect(Point p, int x, int y) {
return y >= p.y;
}
}
private static class Rightward implements Direction {
@Override
public boolean judgeDirect(Point p, int x, int y) {
return x >= p.x;
}
}
private static class Leftward implements Direction {
@Override
public boolean judgeDirect(Point p, int x, int y) {
return x <= p.x;
}
}
private void beginPath(int x, int y) {
pathPoints = new Point[9];
pathPoints[0] = new Point(x, y);
pathPoints[1] = new Point(x, y);
}
private void fillPath(int x, int y) {
if (pathPoints == null) {
return;
}
int points = pathPoints.length - 1;
for (int i = 1; i < points; i++) {
if (pathPoints[i + 1] == null) {
if (directions[i].judgeDirect(pathPoints[i], x, y)) {
pathPoints[i].set(x, y);
} else {
pathPoints[i + 1] = new Point(x, y);
}
break;
}
}
}
private void endPath(int x, int y) {
if (pathPoints == null) {
return;
}
pathPoints[pathPoints.length - 1] = new Point(x, y);
}
private boolean judgeLollipopPath(Point[] p) {
if (p == null) {
return false;
}
for (Point e : p) {
if (e == null) {
return false;
}
}
// p1必須高於p0
boolean b11 = p[1].y < p[0].y;
if (!b11) {
return false;
}
// p2水平方向在p0/p1的右邊
boolean b21 = p[2].x > p[0].x && p[2].x > p[1].x;
if (!b21) {
return false;
}
// p2垂直方向在p0/p1的之間
boolean b22 = p[2].y < p[0].y && p[2].y > p[1].y;
if (!b22) {
return false;
}
// p3水平方向在p2的左邊
boolean b31 = p[3].x < p[2].x;
if (!b31) {
return false;
}
// p3垂直方向在p0/p2的之間
boolean b32 = p[3].y < p[0].y && p[3].y > p[2].y;
if (!b32) {
return false;
}
// p4水平方向 在p0/p1的左邊
boolean b41 = p[4].x < p[0].x && p[4].x < p[1].x;
if (!b41) {
return false;
}
// p4垂直方向在p0之上
boolean b42 = p[4].y < p[0].y;
if (!b42) {
return false;
}
// p5水平方向在p4右邊
boolean b51 = p[5].x > p[4].x;
if (!b51) {
return false;
}
// p5垂直方向在p1/p4之上 無需額外判斷p1 p2 p3
boolean b52 = p[5].y < p[1].y && p[5].y < p[4].y;
if (!b52) {
return false;
}
// p6水平方向,在p5/p2右邊
boolean b61 = p[6].x > p[2].x && p[6].x > p[5].x;
if (!b61) {
return false;
}
// p6垂直方向在p0/p5之間
boolean b62 = p[6].y < p[0].y && p[6].y > p[5].y;
if (!b62) {
return false;
}
// p7水平方向 在p4的右邊
boolean b71 = p[7].x > p[4].x;
if (!b71) {
return false;
}
// p7垂直方向在p0/p3之間
boolean b72 = p[7].y < p[0].y && p[7].y > p[3].y;
if (!b72) {
return false;
}
return true;
}
}