棒棒糖手势识别

基本效果

00ju-dx4ln.gif

背景

为了方便测试打开彩蛋,同时对用户隐藏彩蛋。可以选择一个用户不重视的view,为其加上奇怪的行为,从而触发这个事件。

正确的手势

可胖、可瘦、可高、可矮、对于雍余的画笔不参与计算。

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;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 简介 苹果官方为我们提供了简单手势的识别器,但对于图形手势,例如五角星、三角形等的识别,就需要自己实现了。通过识别...
    Soulghost阅读 1,072评论 2 11
  • 我观望四周的时候,忽然发现在流水线旁的另一侧有一个螺旋上升的楼梯,我悄悄的走了过去,尽量让自己的脚步声轻点,不去...
    哎呦哎呦拔萝卜阅读 77评论 0 0
  • 笑着笑着就哭了的喜剧 《喜剧之王》平淡的讲述了一个小人物梦想能够成为被大家能认可的演员,卑微的在逆境中不断奋斗的辛...
    阿肥的小蜗牛阅读 922评论 0 3
  • 前提:性格磁场已形成,社交光明 人不应当抛弃已有社交,以及你争取到的,他人对你的印象 否则会毫无动力 - [ ] ...
    64exc阅读 278评论 0 0
  • 抬头看了看黑板,那越来越小的数字让他感觉到不安,明明已经热起来的天,却带来了一股说不出的寒意。 不知不觉,又到了...
    风影天蓝阅读 156评论 0 1