Android高級彩蛋

基本效果

00ju-dx4ln.gif

背景

爲了方便測試打開彩蛋,同時對用戶隱藏彩蛋。

正確的手勢

可胖、可瘦、可高、可矮、對於雍餘的畫筆不參與計算。

image.png
image.png
image.png

錯誤手勢

1.不完整、亂畫

image.png
image.png

2.出現重疊

image.png

3.過渡傾斜

image.png

手勢識別的算法實現:

爲了最優的性能,算法的實現,並沒有記錄手指的完整軌跡,只是在手指移動過程中,識別出曲線的拐點。這些拐點作爲判斷的關鍵點,通過關鍵點的座標,判斷出是否符合棒棒糖手勢。

第一步識別出8個關鍵點

8個點的標註:

[標註圖todo]

起始點:

起始點的獲取非常容易,ACTION_DOWN開始,重置8個關鍵點數組,並記錄起始點。

曲線的拐點,也就是識別出曲線局部的極值

想要避免記錄完整手勢軌跡,就必須在手指一動的過程中,記錄曲線的關鍵點。

連續曲線,極大值與極小值的簡單的理解就是方向突變。

  1. 在垂直方向上曲線的局部極大值爲:一個向上移動的點,縱座標改爲向下移動,那麼就達到了局部最高點。
  2. 在垂直方向上曲線的局部極小值爲:一個向下移動的點,縱座標改爲向上移動,那麼就達到了局部最低點。
  3. 在水平方向上曲線的局部極大值爲:一個向右移動的點,縱座標改爲向左移動,那麼就達到了局部最右點。
  4. 在水平方向上曲線的局部極小值爲:一個向左移動的點,縱座標改爲向右移動,那麼就達到了局部最左點。

拐點的連續識別

一個拐點識別完成,那就填充下一個拐點,如果並沒有出現拐點,那就更新當前節點的座標爲手指的座標。

第二步,根據關鍵點的座標,判斷是否爲正確手勢

通過判斷關鍵點的座標相對位置,比如起始點,縱座標,需要在其它所有點之下,等等其它點的判斷。

由於不能要求手勢畫太多圈圈,這裏只要求畫出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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章