棒棒糖手势识别

作者: ReadyShow | 来源:发表于2020-03-07 23:48 被阅读0次

基本效果

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;
    }
}

相关文章

  • 棒棒糖手势识别

    基本效果 背景 为了方便测试打开彩蛋,同时对用户隐藏彩蛋。可以选择一个用户不重视的view,为其加上奇怪的行为,从...

  • 手势控制:点击、滑动、平移、捏合、旋转、长按、轻扫

    手势识别器(Gesture Recognizer)用于识别触摸序列并触发响应事件。当手势识别器识别到一个手势或手势...

  • 3.6 iOS手势识别的状态和手势识别器幕后原理

    2.2手势识别的状态和手势识别器幕后原理 (一)手势的状态 (二)离散型手势识别器和连续型手势识别器之间的对比: ...

  • Gesture手势

    手势识别器 手势识别器是对触摸事件做了封装,我们无需自己去判断某个手势是否触发,手势识别器本身起到了识别作用,我们...

  • 手势识别

    手势识别 6种手势识别 在iOS开发中有6中手势识别:点按、捏合、拖动、轻扫、旋转、长按苹果推出手势识别主要是为了...

  • UIGestureRecognizer

    什么是手势识别器? 手势识别器就是对触摸事件做了封装,我们不需要判断某个手势是否触发,手势识别器本身起到了识别作用...

  • 手势——UIGestureRecognizer

    一、简介 UIGestureRecognizer是具体手势识别器的基类。 手势识别器对象(或简单地说是手势识别器)...

  • EasyAR手势姿势识别

    简介 简单试了下EasyAR的手势识别以及姿势识别 手势识别 目前官方是有两种手势 测试了感觉识别度蛮高的 也蛮快...

  • iOS手势识别

    UIGestureRecognizer手势识别器手势识别器是特殊的触摸事件UIGestureRecognizer是...

  • UIGestureRecognizer手势识别器学习笔记

    UIGestureRecognizer 具体手势识别器的基类。一个手势识别器对象,或者简单地说一个手势识别器,解耦...

网友评论

    本文标题:棒棒糖手势识别

    本文链接:https://www.haomeiwen.com/subject/opokdhtx.html