原文链接 Android事情高档手势处理
GestureDetector只能帮咱们处理并辨认一些常用的简单的手势,如点击,双击,长按,滑动(Scroll)和快速滑动(Fling)等,一般情况下,这些足够咱们使用了,但有些时候需求一些更为杂乱的手势操作,如Translate,Zoom,Scale和Rotate,以及像处理一些多点触控(MultiTouch),这就需求开发人猿自己处理了,本文将讨论一下这些内容。
高档手势辨认
移动(Translate/Drag)
这儿的移动的意思是让物体跟着手指在屏幕上移动,或许叫作拖拽。而且这个只需求一个手指就能够办到,不触及多点触控。
其实,这个完结起来并不杂乱,从onTouchEvent处取得事情后,不断的用MotionEvent的坐标来改写方针View即可,甚至都不必管详细的事情类型,由于无论是ACTION_DOWN,ACTION_UP或许ACTION_MOVE,都能够供给新的坐标,只管从事情处取坐标然后改写就能够了。
draw at (x0, y0);
onTouchEvent(event) {
x = event.getRawX();
y = event.getRawY();
invalidate with (x, y); // will draw at (x, y);
}
旋转(Rotate)
相同,关于旋转用单个手指也能够办到,以方针View当时的方位为圆心,以手指划过的曲线作为圆弧,由此便可让方针View旋转起来,而且这个手势由单个手指也能够完结,不必管多点触控。
其实能够进一步的做简化,确定屏幕中央为圆心,来核算手势划过的角度,而且为了连惯性,要以事情ACTION_MOVE过程中的增量角度来对View进行旋转,这样会让旋转看起来更顺滑一些,额定的工作是要把事情的坐标进行一下转化,转化为以屏幕中心为原点的坐标。
详细的流程是:
lastTheta = -1;
onTouchEvent(event) {
switch (action) {
case ACTION_DOWN:
lastX = normalize(event.getX());
lastY = normalize(event.getY());
lastTheta = angle(lastX, lastY);
break;
case ACTION_MOVE:
newX = normalize(event.getX());
newY = normalize(event.getY());
theta = angle(newX, newY);
deltaTheta = alpha - beta;
invalidate to rotate with deltaTheta;
lastTheta = theta;
break;
case ACTION_CANCEL:
case ACTION_UP:
we are done.
}
normalizeX(x) {
return 2 * x / screenWidth;
}
normalizeY(y) {
return 2 * y / screenHeight;
}
angle(x, y) {
return atan(y / x);
}
至于缩放,单个手指无法完结,有必要要用两个手指才能够,就触及到多点触控,所以需求先介绍一下多点触控。
多点触控(MultiTouch)
这个并不杂乱,虽然听起来像个神秘高科技,但其实,处理流程并不杂乱,主体流程仍然是在onTouchEvent办法中,而且主要的目标仍是MotionEvent,文档里边基本上都说清楚了,要点便是:
- MotionEvent目标,会用pointerId和pointerIndex来区别不同的触控点(术语是Pointer)
- 事情流是:ACTION_DOWN 称为主触控点(Primary Pointer),然后是ACTION_POINTER_DOWN 别的一个触控点来了(非Primary Pointer),然后是ACTION_MOVE 这儿没有显示 区别不同的pointer,需求开发人猿自己去区别,然后是ACTION_POINTER_UP 非主触控点 离开了,最终是ACTION_UP 主触控点离开了。需求留意的是,这是处理事情的逻辑上的顺序 ,实在的事情流,纷歧定是这样的(ACTION_DOWN必定是第一个,ACTION_UP必定 必定最终一个,但中心的几个有顺序 不定)。
- 留意的要点,每次事情来了后,不同的触控点(Pointer)的index并不是固定的,比方上一次MOVE时它在index 0,但下次可能就在index 1,而其Pointer Id是固定的。所以在处理的整个流程中要记载不同Pointer的id,然后取得其index,再用index去取坐标啊之类的数据。
- 多点触控,天生就支撑,所以即使你不辨认多点触控手势(如scale),只关心单个手指手势,在处理的时候,仍要考虑到多点的逻辑。比方说translate时,假如不考虑多点,那么当别的一个手指触摸了屏幕,发生了ACTION_MOVE事情,但它的坐标跟开始发生事情的Pointer差距很远,那么假如不做扫除,就可能发生瞬间漂移。
加强版的单触控点手势
关于前面说到的单触控点手势(单手指就能辨认的手势)如Translate和Rotate,其实都需求加强一下逻辑,以避免多触控点发生的搅扰。
加强版别的单触控点手势处理:
primaryPointerId = INVALIDE_POINTER_ID;
onTouchEvent(event) {
switch (event.getActionMasked()) {
case ACTION_DOWN:
primaryPointer = event.getPointerId(event.getActionIndex());
break;
case ACTION_MOVE:
pointerIndex = event.findPointerIndex(primaryPointerId);
x = event.getX(pointerIndex);
y = event.getY(pointerIndex);
be happy with x and y;
break;
case ACTION_UP:
case ACTION_CANCEL:
primaryIndex = INVALIDE_POINTER_ID;
break;
}
}
当然,这儿也取决于详细的使用场景,假设答应切换触控点,比方先一个手指拖动,然后别的一个手指点进来,这时第一个手指离开了,假如想持续 拖动的话,就需求替换已保存的primaryPointer。这时会收到ACTION_POINTER_UP,需求在此做切换处理,持续 上面的代码片段,
secondPointer = INVALIDE_POINTER_ID;
case ACTION_POINTER_DOWN:
secondPointer = event.getPointerId(event.getActionIndex());
break;
case ACTION_POINTER_UP:
thisPointer = event.getPointerId(event.getActionIndex());
if (thisPointer == primaryPointer) {
primaryPointer = secondPointer;
}
secondPointer = INVALIDE_POINTER_ID;
break;
还有一点需求留意的是,不能简单的只用getPointerCount来作判别,就比方pointer 1先来,然后pointer 2来了,pointer 1又离开了,这时pointerCount仍是1,但是pointer已改变 了,事情的方位就变了,假如不按上述办法处理,将会发生跳变。
缩放(Zoom/Scale)
缩放手势是多点触控的一个十分典型的使用,由于单手无法做出比较合理的手势判别。SDK傍边供给了一个用于辨认缩放的手势辨认器ScaleGestureDetector,它的使用办法与GestureDetector相同,创建目标,塞MotionEvent进去,然后注册listener即可。
但假如,用单独的detector不是很便利,比方现已自己完结了一套手势辨认逻辑,现在只想加上Scale,或许其他原因不便利引入ScaleGestureDetector,那么就得自己去做了,也并不是很杂乱。
主要思路便是,收集齐两个触控点,记载它们初始的方位,核算它们之间初始的间隔,在ACTION_MOVE时,再核算新的间隔,新旧间隔之比既可当作缩放的比例:
primaryPointer = INVALIDE_POINTER_ID;
secondPointer = INVALIDE_POINTER_ID;
initialSpan = -1;
startPoint = null;
onTouchEvent(event) {
case ACTION_DOWN:
index = event.getActionIndex();
primaryPointer = event.getPointerId(index);
startPoint = Point(event.getX(index), event.getY(index));
break;
case ACTION_POINTER_DOWN:
index = event.getActionIndex();
secondPointer = event.getPointerId(index);
sp = Point(event.getX(index), event.getY(index));
initialSpan = distance(startPoint, sp);
break;
case ACTION_MOVE:
if (event.getPointerCount() > 1) {
primaryIndex = event.findPointerIndex(primaryPointer);
pp = Point(event.getX(primaryIndex), event.getY(primaryIndex));
secondIndex = event.findPointerIndex(secondPointer);
sp = Point(event.getX(secondIndex), event.getY(secondIndex));
thisDistance = distance(pp, sp);
if (thisDistance > ScaledSpan) {
scale = thisDistance / initialSpan;
be happy with scale;
}
}
break;
case ACTION_UP:
case ACTION_CANCEL:
case ACTION_POINTER_UP:
thisPointer = event.getPointerId(event.getActionIndex());
if (thisPointer == primaryPointer) {
primaryPointer = INVALIDE_POINTER_ID;
} else if (thisPointer == seocndPointer) {
secondPointer = INVALIDE_POINTER_ID;
}
break;
}
当然 ,还能够加一些阈值判别,比方当distance大于getScaledTouchSlop,才触发使用scale的逻辑。
参考资料
- Detecting gestures on Android via GestureDetector
- Handle multi-touch gestures
- Drag and scale
- Drag and drop
- MotionEvent
- Gestures and Touch Events
- android-gesture-detectors
- SwipeBackLayout
- GestureViews
- Sensey