/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.drawee.gestures;
import android.content.Context;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import com.facebook.common.internal.VisibleForTesting;
/**
* Gesture detector based on touch events.
* <p>
* This class allows us to get click events when we need them, but not to consume them when we are
* temporarily not interested in them. Doing {@code View.setClickable(true)} will cause for the
* view always to consume click event, even if {@code View.performClick} is overridden to return
* false. That means even though our view didn't handle the click event, the event will not get
* propagated upwards. Result of {@code View.onTouchEvent} is handled correctly though so we use
* that instead.
* <p> This class currently only detects clicks.
*/
public class GestureDetector {
/** Interface for the click listener. */
public interface ClickListener {
public boolean onClick();
}
@VisibleForTesting ClickListener mClickListener;
@VisibleForTesting final float mSingleTapSlopPx;
@VisibleForTesting boolean mIsCapturingGesture;
@VisibleForTesting boolean mIsClickCandidate;
@VisibleForTesting long mActionDownTime;
@VisibleForTesting float mActionDownX;
@VisibleForTesting float mActionDownY;
public GestureDetector(Context context) {
final ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
mSingleTapSlopPx = viewConfiguration.getScaledTouchSlop();
init();
}
/** Creates a new instance of this gesture detector. */
public static GestureDetector newInstance(Context context) {
return new GestureDetector(context);
}
/** Initializes this component to its initial state. */
public void init() {
mClickListener = null;
reset();
}
/**
* Resets component.
* <p> This will drop any gesture recognition that might currently be in progress.
*/
public void reset() {
mIsCapturingGesture = false;
mIsClickCandidate = false;
}
/** Sets the click listener. */
public void setClickListener(ClickListener clickListener) {
mClickListener = clickListener;
}
/** Returns whether the gesture capturing is in progress. */
public boolean isCapturingGesture() {
return mIsCapturingGesture;
}
/** Handles the touch event */
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mIsCapturingGesture = true;
mIsClickCandidate = true;
mActionDownTime = event.getEventTime();
mActionDownX = event.getX();
mActionDownY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(event.getX() - mActionDownX) > mSingleTapSlopPx ||
Math.abs(event.getY() - mActionDownY) > mSingleTapSlopPx) {
mIsClickCandidate = false;
}
break;
case MotionEvent.ACTION_CANCEL:
mIsCapturingGesture = false;
mIsClickCandidate = false;
break;
case MotionEvent.ACTION_UP:
mIsCapturingGesture = false;
if (Math.abs(event.getX() - mActionDownX) > mSingleTapSlopPx ||
Math.abs(event.getY() - mActionDownY) > mSingleTapSlopPx) {
mIsClickCandidate = false;
}
if (mIsClickCandidate) {
if (event.getEventTime() - mActionDownTime <= ViewConfiguration.getLongPressTimeout()) {
if (mClickListener != null) {
mClickListener.onClick();
}
} else {
// long click, not handled
}
}
mIsClickCandidate = false;
break;
}
return true;
}
}