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