FreeRDP
Loading...
Searching...
No Matches
GestureDetector.java
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * Modified for aFreeRDP by Martin Fleisz (martin.fleisz@thincast.com)
17 */
18
19package com.freerdp.freerdpcore.utils;
20
21import android.content.Context;
22import android.os.Handler;
23import android.os.Message;
24import android.util.DisplayMetrics;
25import android.view.MotionEvent;
26import android.view.ViewConfiguration;
27
28public class GestureDetector
29{
30
31 private static final int TAP_TIMEOUT = 100;
32 private static final int DOUBLE_TAP_TIMEOUT = 200;
33 // Distance a touch can wander before we think the user is the first touch in a sequence of
34 // double tap
35 private static final int LARGE_TOUCH_SLOP = 18;
36 // Distance between the first touch and second touch to still be considered a double tap
37 private static final int DOUBLE_TAP_SLOP = 100;
38 // constants for Message.what used by GestureHandler below
39 private static final int SHOW_PRESS = 1;
40 private static final int LONG_PRESS = 2;
41 private static final int TAP = 3;
42 private final Handler mHandler;
43 private final OnGestureListener mListener;
44 private int mTouchSlopSquare;
45 private int mLargeTouchSlopSquare;
46 private int mDoubleTapSlopSquare;
47 private int mLongpressTimeout = 100;
48 private OnDoubleTapListener mDoubleTapListener;
49 private boolean mStillDown;
50 private boolean mInLongPress;
51 private boolean mAlwaysInTapRegion;
52 private boolean mAlwaysInBiggerTapRegion;
53 private MotionEvent mCurrentDownEvent;
54 private MotionEvent mPreviousUpEvent;
59 private boolean mIsDoubleTapping;
60 private float mLastMotionY;
61 private float mLastMotionX;
62 private boolean mIsLongpressEnabled;
68 private boolean mIgnoreMultitouch;
79 public GestureDetector(Context context, OnGestureListener listener)
80 {
81 this(context, listener, null);
82 }
83
95 public GestureDetector(Context context, OnGestureListener listener, Handler handler)
96 {
97 this(context, listener, handler, context != null);
98 }
99
113 public GestureDetector(Context context, OnGestureListener listener, Handler handler,
114 boolean ignoreMultitouch)
115 {
116 if (handler != null)
117 {
118 mHandler = new GestureHandler(handler);
119 }
120 else
121 {
122 mHandler = new GestureHandler();
123 }
124 mListener = listener;
125 if (listener instanceof OnDoubleTapListener)
126 {
128 }
129 init(context, ignoreMultitouch);
130 }
131
132 private void init(Context context, boolean ignoreMultitouch)
133 {
134 if (mListener == null)
135 {
136 throw new NullPointerException("OnGestureListener must not be null");
137 }
138 mIsLongpressEnabled = true;
139 mIgnoreMultitouch = ignoreMultitouch;
140
141 // Fallback to support pre-donuts releases
142 int touchSlop, largeTouchSlop, doubleTapSlop;
143 if (context == null)
144 {
145 // noinspection deprecation
146 touchSlop = ViewConfiguration.getTouchSlop();
147 largeTouchSlop = touchSlop + 2;
148 doubleTapSlop = DOUBLE_TAP_SLOP;
149 }
150 else
151 {
152 final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
153 final float density = metrics.density;
154 final ViewConfiguration configuration = ViewConfiguration.get(context);
155 touchSlop = configuration.getScaledTouchSlop();
156 largeTouchSlop = (int)(density * LARGE_TOUCH_SLOP + 0.5f);
157 doubleTapSlop = configuration.getScaledDoubleTapSlop();
158 }
159 mTouchSlopSquare = touchSlop * touchSlop;
160 mLargeTouchSlopSquare = largeTouchSlop * largeTouchSlop;
161 mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
162 }
163
171 public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener)
172 {
173 mDoubleTapListener = onDoubleTapListener;
174 }
175
186 {
187 mIsLongpressEnabled = isLongpressEnabled;
188 }
189
193 public boolean isLongpressEnabled()
194 {
195 return mIsLongpressEnabled;
196 }
197
198 public void setLongPressTimeout(int timeout)
199 {
200 mLongpressTimeout = timeout;
201 }
202
211 public boolean onTouchEvent(MotionEvent ev)
212 {
213 final int action = ev.getAction();
214 final float y = ev.getY();
215 final float x = ev.getX();
216
217 boolean handled = false;
218
219 switch (action & MotionEvent.ACTION_MASK)
220 {
221 case MotionEvent.ACTION_POINTER_DOWN:
222 if (mIgnoreMultitouch)
223 {
224 // Multitouch event - abort.
225 cancel();
226 }
227 break;
228
229 case MotionEvent.ACTION_POINTER_UP:
230 // Ending a multitouch gesture and going back to 1 finger
231 if (mIgnoreMultitouch && ev.getPointerCount() == 2)
232 {
233 int index = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
234 MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0)
235 ? 1
236 : 0;
237 mLastMotionX = ev.getX(index);
238 mLastMotionY = ev.getY(index);
239 }
240 break;
241
242 case MotionEvent.ACTION_DOWN:
243 if (mDoubleTapListener != null)
244 {
245 boolean hadTapMessage = mHandler.hasMessages(TAP);
246 if (hadTapMessage)
247 mHandler.removeMessages(TAP);
248 if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) &&
249 hadTapMessage &&
250 isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev))
251 {
252 // This is a second tap
253 mIsDoubleTapping = true;
254 // Give a callback with the first tap of the double-tap
255 handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
256 // Give a callback with down event of the double-tap
257 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
258 }
259 else
260 {
261 // This is a first tap
262 mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
263 }
264 }
265
266 mLastMotionX = x;
267 mLastMotionY = y;
268 if (mCurrentDownEvent != null)
269 {
270 mCurrentDownEvent.recycle();
271 }
272 mCurrentDownEvent = MotionEvent.obtain(ev);
273 mAlwaysInTapRegion = true;
274 mAlwaysInBiggerTapRegion = true;
275 mStillDown = true;
276 mInLongPress = false;
277
278 if (mIsLongpressEnabled)
279 {
280 mHandler.removeMessages(LONG_PRESS);
281 mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() +
282 TAP_TIMEOUT +
283 mLongpressTimeout);
284 }
285 mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
286 mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
287 handled |= mListener.onDown(ev);
288 break;
289
290 case MotionEvent.ACTION_MOVE:
291 if (mIgnoreMultitouch && ev.getPointerCount() > 1)
292 {
293 break;
294 }
295 final float scrollX = mLastMotionX - x;
296 final float scrollY = mLastMotionY - y;
297 if (mIsDoubleTapping)
298 {
299 // Give the move events of the double-tap
300 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
301 }
302 else if (mAlwaysInTapRegion)
303 {
304 final int deltaX = (int)(x - mCurrentDownEvent.getX());
305 final int deltaY = (int)(y - mCurrentDownEvent.getY());
306 int distance = (deltaX * deltaX) + (deltaY * deltaY);
307 if (distance > mTouchSlopSquare)
308 {
309 mLastMotionX = x;
310 mLastMotionY = y;
311 mAlwaysInTapRegion = false;
312 mHandler.removeMessages(TAP);
313 mHandler.removeMessages(SHOW_PRESS);
314 mHandler.removeMessages(LONG_PRESS);
315 }
316 if (distance > mLargeTouchSlopSquare)
317 {
318 mAlwaysInBiggerTapRegion = false;
319 }
320 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
321 }
322 else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1))
323 {
324 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
325 mLastMotionX = x;
326 mLastMotionY = y;
327 }
328 break;
329
330 case MotionEvent.ACTION_UP:
331 mStillDown = false;
332 MotionEvent currentUpEvent = MotionEvent.obtain(ev);
333 if (mIsDoubleTapping)
334 {
335 // Finally, give the up event of the double-tap
336 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
337 }
338 else if (mInLongPress)
339 {
340 mHandler.removeMessages(TAP);
341 mListener.onLongPressUp(ev);
342 mInLongPress = false;
343 }
344 else if (mAlwaysInTapRegion)
345 {
346 handled = mListener.onSingleTapUp(mCurrentDownEvent);
347 }
348 else
349 {
350 // A fling must travel the minimum tap distance
351 }
352 if (mPreviousUpEvent != null)
353 {
354 mPreviousUpEvent.recycle();
355 }
356 // Hold the event we obtained above - listeners may have changed the original.
357 mPreviousUpEvent = currentUpEvent;
358 mIsDoubleTapping = false;
359 mHandler.removeMessages(SHOW_PRESS);
360 mHandler.removeMessages(LONG_PRESS);
361 handled |= mListener.onUp(ev);
362 break;
363 case MotionEvent.ACTION_CANCEL:
364 cancel();
365 break;
366 }
367 return handled;
368 }
369
370 private void cancel()
371 {
372 mHandler.removeMessages(SHOW_PRESS);
373 mHandler.removeMessages(LONG_PRESS);
374 mHandler.removeMessages(TAP);
375 mAlwaysInTapRegion = false; // ensures that we won't receive an OnSingleTap notification
376 // when a 2-Finger tap is performed
377 mIsDoubleTapping = false;
378 mStillDown = false;
379 if (mInLongPress)
380 {
381 mInLongPress = false;
382 }
383 }
384
385 private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
386 MotionEvent secondDown)
387 {
388 if (!mAlwaysInBiggerTapRegion)
389 {
390 return false;
391 }
392
393 if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT)
394 {
395 return false;
396 }
397
398 int deltaX = (int)firstDown.getX() - (int)secondDown.getX();
399 int deltaY = (int)firstDown.getY() - (int)secondDown.getY();
400 return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
401 }
402
403 private void dispatchLongPress()
404 {
405 mHandler.removeMessages(TAP);
406 mInLongPress = true;
407 mListener.onLongPress(mCurrentDownEvent);
408 }
409
416 public interface OnGestureListener {
417
425 boolean onDown(MotionEvent e);
426
434 boolean onUp(MotionEvent e);
435
444 void onShowPress(MotionEvent e);
445
453 boolean onSingleTapUp(MotionEvent e);
454
470 boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
471
478 void onLongPress(MotionEvent e);
479
485 void onLongPressUp(MotionEvent e);
486 }
487
492 public interface OnDoubleTapListener {
504 boolean onSingleTapConfirmed(MotionEvent e);
505
512 boolean onDoubleTap(MotionEvent e);
513
521 boolean onDoubleTapEvent(MotionEvent e);
522 }
523
531 {
532 public boolean onSingleTapUp(MotionEvent e)
533 {
534 return false;
535 }
536
537 public void onLongPress(MotionEvent e)
538 {
539 }
540
541 public void onLongPressUp(MotionEvent e)
542 {
543 }
544
545 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
546 {
547 return false;
548 }
549
550 public void onShowPress(MotionEvent e)
551 {
552 }
553
554 public boolean onDown(MotionEvent e)
555 {
556 return false;
557 }
558
559 public boolean onUp(MotionEvent e)
560 {
561 return false;
562 }
563
564 public boolean onDoubleTap(MotionEvent e)
565 {
566 return false;
567 }
568
569 public boolean onDoubleTapEvent(MotionEvent e)
570 {
571 return false;
572 }
573
574 public boolean onSingleTapConfirmed(MotionEvent e)
575 {
576 return false;
577 }
578 }
579
580 private class GestureHandler extends Handler
581 {
582 GestureHandler()
583 {
584 super();
585 }
586
587 GestureHandler(Handler handler)
588 {
589 super(handler.getLooper());
590 }
591
592 @Override public void handleMessage(Message msg)
593 {
594 switch (msg.what)
595 {
596 case SHOW_PRESS:
597 mListener.onShowPress(mCurrentDownEvent);
598 break;
599
600 case LONG_PRESS:
601 dispatchLongPress();
602 break;
603
604 case TAP:
605 // If the user's finger is still down, do not count it as a tap
606 if (mDoubleTapListener != null && !mStillDown)
607 {
608 mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
609 }
610 break;
611
612 default:
613 throw new RuntimeException("Unknown message " + msg); // never
614 }
615 }
616 }
617}
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener)
void setIsLongpressEnabled(boolean isLongpressEnabled)
GestureDetector(Context context, OnGestureListener listener, Handler handler, boolean ignoreMultitouch)
GestureDetector(Context context, OnGestureListener listener)
GestureDetector(Context context, OnGestureListener listener, Handler handler)
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)