基本效果
背景
为了方便测试打开彩蛋,同时对用户隐藏彩蛋。可以选择一个用户不重视的view,为其加上奇怪的行为,从而触发这个事件。
正确的手势
可胖、可瘦、可高、可矮、对于雍余的画笔不参与计算。
错误手势
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;
}
}