// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.contextualsearch;
import android.content.Context;
import android.graphics.PointF;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeEventFilter.ScrollDirection;
import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeHandler;
/**
* TODO(pedrosimonetti): Confirm with dtrainor@ the proper location for this file.
*
* Recognizes directional swipe gestures using supplied {@link MotionEvent}s.
* The {@EdgeSwipeHandler} callbacks will notify users when a particular gesture
* has occurred, if the handler supports the particular direction of the swipe.
*
* To use this class:
* <ul>
* <li>Create an instance of the {@code SwipeRecognizer} for your View
* <li>In the View#onTouchEvent(MotionEvent) method ensure you call
* {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback
* will be executed when the gestures occur.
* <li>Before trying to recognize the gesture, the class will call
* {@link #shouldRecognizeSwipe(MotionEvent, MotionEvent)}, which allow
* ignoring swipe recognition based on the MotionEvents.
* <li>Once a swipe gesture is detected, the class will check if the the direction
* is supported by calling {@link EdgeSwipeHandler#isSwipeEnabled(ScrollDirection)}.
* </ul>
* Internally, this class uses a {@link GestureDetector} to recognize swipe gestures.
* For convenience, this class also extends {@link SimpleOnGestureListener} which
* is passed to the {@GestureDetector}. This means that this class can also be
* used to detect simple gestures defined in {@link GestureDetector}.
*/
public class SwipeRecognizer extends SimpleOnGestureListener {
/**
* The threshold for a vertical swipe gesture, in dps.
*/
private static final float SWIPE_VERTICAL_DRAG_THRESHOLD_DP = 5.f;
/**
* The threshold for a horizontal swipe gesture, in dps.
*/
private static final float SWIPE_HORIZONTAL_DRAG_THRESHOLD_DP = 10.f;
/**
* The {@link EdgeSwipeHandler} that will respond to recognized gestures.
*/
private EdgeSwipeHandler mSwipeHandler;
/**
* The direction of the swipe gesture.
* TODO(pedrosimonetti): Consider renaming ScrollDirection to SwipeDirection.
* Also consider renaming EdgeSwipeHandler to SwipeHandler or DirectionalSwipeHandler.
* Finally, consider moving the ScrollDirection/SwipeDirection enum to this class.
*/
private ScrollDirection mSwipeDirection = ScrollDirection.UNKNOWN;
/**
* The point that originated the swipe gesture.
*/
private final PointF mMotionStartPoint = new PointF();
/**
* The dps per pixel ratio.
*/
private final float mPxToDp;
/**
* The internal {@GestureDetector} used to recognize swipe gestures.
*/
private final GestureDetector mGestureDetector;
/**
* @param context The current Android {@link Context}.
*/
public SwipeRecognizer(Context context) {
mPxToDp = 1.f / context.getResources().getDisplayMetrics().density;
mGestureDetector = new GestureDetector(context, this);
}
/**
* Sets the {@link EdgeSwipeHandler} that will respond to recognized gestures.
* @param handler The {@link EdgeSwipeHandler}.
*/
public void setSwipeHandler(EdgeSwipeHandler handler) {
mSwipeHandler = handler;
}
/**
* Analyzes the given motion event by feeding it to a {@GestureDetector}. Depending on the
* results from the onScroll() and onFling() methods, it triggers the appropriate callbacks
* on the {@link EdgeSwipeHandler} supplied.
*
* @param event The {@link MotionEvent}.
* @return Whether the event has been consumed.
*/
public boolean onTouchEvent(MotionEvent event) {
boolean consumed = mGestureDetector.onTouchEvent(event);
if (mSwipeHandler != null) {
final int action = event.getAction();
if ((action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)
&& mSwipeDirection != ScrollDirection.UNKNOWN) {
mSwipeHandler.swipeFinished();
mSwipeDirection = ScrollDirection.UNKNOWN;
consumed = true;
}
}
return consumed;
}
/**
* Checks whether the swipe gestures should be recognized. If this method returns false,
* then the whole swipe recognition process will be ignored. By default this method returns
* true. If a more complex logic is needed, this method should be overridden.
*
* @param e1 The first {@link MotionEvent}.
* @param e2 The second {@link MotionEvent}.
* @return Whether the swipe gestures should be recognized
*/
public boolean shouldRecognizeSwipe(MotionEvent e1, MotionEvent e2) {
return true;
}
// ============================================================================================
// Swipe Recognition Helpers
// ============================================================================================
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (mSwipeHandler == null || e1 == null || e2 == null) return false;
final float x = e2.getRawX() * mPxToDp;
final float y = e2.getRawY() * mPxToDp;
if (mSwipeDirection == ScrollDirection.UNKNOWN && shouldRecognizeSwipe(e1, e2)) {
float tx = (e2.getRawX() - e1.getRawX()) * mPxToDp;
float ty = (e2.getRawY() - e1.getRawY()) * mPxToDp;
ScrollDirection direction = ScrollDirection.UNKNOWN;
if (Math.abs(tx) > SWIPE_HORIZONTAL_DRAG_THRESHOLD_DP) {
direction = tx > 0.f ? ScrollDirection.RIGHT : ScrollDirection.LEFT;
} else if (Math.abs(ty) > SWIPE_VERTICAL_DRAG_THRESHOLD_DP) {
direction = ty > 0.f ? ScrollDirection.DOWN : ScrollDirection.UP;
}
if (direction != ScrollDirection.UNKNOWN && mSwipeHandler.isSwipeEnabled(direction)) {
mSwipeDirection = direction;
mSwipeHandler.swipeStarted(direction, x, y);
mMotionStartPoint.set(e2.getRawX(), e2.getRawY());
}
}
if (mSwipeDirection != ScrollDirection.UNKNOWN) {
final float tx = (e2.getRawX() - mMotionStartPoint.x) * mPxToDp;
final float ty = (e2.getRawY() - mMotionStartPoint.y) * mPxToDp;
final float dx = -distanceX * mPxToDp;
final float dy = -distanceY * mPxToDp;
mSwipeHandler.swipeUpdated(x, y, dx, dy, tx, ty);
return true;
}
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (mSwipeHandler == null) return false;
if (mSwipeDirection != ScrollDirection.UNKNOWN) {
final float x = e2.getRawX() * mPxToDp;
final float y = e2.getRawY() * mPxToDp;
final float tx = (e2.getRawX() - mMotionStartPoint.x) * mPxToDp;
final float ty = (e2.getRawY() - mMotionStartPoint.y) * mPxToDp;
final float vx = velocityX * mPxToDp;
final float vy = velocityY * mPxToDp;
mSwipeHandler.swipeFlingOccurred(x, y, tx, ty, vx, vy);
return true;
}
return false;
}
}