/** * Copyright (C) 2014 Google Inc. * Licensed to The Android Open Source Project. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mail.ui; import android.content.Context; import androidx.annotation.Nullable; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; /** * Generic utility class that deals with capturing drag events in a particular horizontal direction * and calls the callback interface for drag events. * * Usage: * * * * class CustomView extends ... { * private boolean mShouldInterceptDrag; * private int mDragMode; * * public boolean onInterceptTouchEvent(MotionEvent ev) { * switch (ev.getAction()) { * case MotionEvent.ACTION_DOWN: * // Check if the event is in the draggable area * mShouldInterceptDrag = ...; * mDragMode = ...; * } * return mShouldInterceptDrag && GmailDragHelper.processTouchEvent(ev, mDragMode); * } * * public boolean onTouchEvent(MotionEvent ev) { * if (mShouldInterceptDrag) { * GmailDragHelper.processTouchEvent(ev, mDragMode); * return true; * } * return super.onTouchEvent(ev); * } * } * * */ public class GmailDragHelper { public static final int CAPTURE_LEFT_TO_RIGHT = 0; public static final int CAPTURE_RIGHT_TO_LEFT = 1; private final GmailDragHelperCallback mCallback; private final ViewConfiguration mConfiguration; private boolean mDragging; private VelocityTracker mVelocityTracker; private float mInitialInterceptedX; private float mInitialInterceptedY; private float mStartDragX; public interface GmailDragHelperCallback { public void onDragStarted(); public void onDrag(float deltaX); public void onDragEnded(float deltaX, float velocityX, boolean isFling); } /** */ public GmailDragHelper(Context context, GmailDragHelperCallback callback) { mCallback = callback; mConfiguration = ViewConfiguration.get(context); } /** * Process incoming MotionEvent to compute the new drag state and coordinates. * * @param ev the captured MotionEvent * @param dragMode either {@link GmailDragHelper#CAPTURE_LEFT_TO_RIGHT} or * {@link GmailDragHelper#CAPTURE_RIGHT_TO_LEFT} * @return whether if drag is happening */ public boolean processTouchEvent(MotionEvent ev, int dragMode) { return processTouchEvent(ev, dragMode, null); } /** * @param xThreshold optional parameter to specify that the drag can only happen if it crosses * the threshold coordinate. This can be used to only start the drag once the user hits the * edge of the view. */ public boolean processTouchEvent(MotionEvent ev, int dragMode, @Nullable Float xThreshold) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mInitialInterceptedX = ev.getX(); mInitialInterceptedY = ev.getY(); break; case MotionEvent.ACTION_MOVE: if (mDragging) { mCallback.onDrag(ev.getX() - mStartDragX); } else { // Try to start dragging final float evX = ev.getX(); // Check for directional drag if ((dragMode == CAPTURE_LEFT_TO_RIGHT && evX <= mInitialInterceptedX) || (dragMode == CAPTURE_RIGHT_TO_LEFT && evX >= mInitialInterceptedX)) { break; } // Check for optional threshold boolean passedThreshold = true; if (xThreshold != null) { if (dragMode == CAPTURE_LEFT_TO_RIGHT) { passedThreshold = evX > xThreshold; } else { passedThreshold = evX < xThreshold; } } // Check for drag threshold final float deltaX = Math.abs(evX - mInitialInterceptedX); final float deltaY = Math.abs(ev.getY() - mInitialInterceptedY); if (deltaX >= mConfiguration.getScaledTouchSlop() && deltaX >= deltaY && passedThreshold) { setDragging(true, evX); } } break; case MotionEvent.ACTION_UP: if (mDragging) { setDragging(false, ev.getX()); } break; } return mDragging; } /** * Set the internal dragging state and calls the appropriate callbacks. */ private void setDragging(boolean dragging, float evX) { mDragging = dragging; if (mDragging) { mStartDragX = evX; mCallback.onDragStarted(); } else { // Here velocity is in pixel/second, let's take that into account for evX. mVelocityTracker.computeCurrentVelocity(1000); // Check for fling final float xVelocity = mVelocityTracker.getXVelocity(); final boolean isFling = Math.abs(xVelocity) > mConfiguration.getScaledMinimumFlingVelocity(); mVelocityTracker.clear(); mCallback.onDragEnded(evX - mStartDragX, xVelocity, isFling); } } }