Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2016 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 
     17 package androidx.core.view;
     18 
     19 
     20 import android.graphics.Point;
     21 import android.view.MotionEvent;
     22 import android.view.View;
     23 
     24 /**
     25  * DragStartHelper is a utility class for implementing drag and drop support.
     26  * <p>
     27  * It detects gestures commonly used to start drag (long click for any input source,
     28  * click and drag for mouse).
     29  * <p>
     30  * It also keeps track of the screen location where the drag started, and helps determining
     31  * the hot spot position for a drag shadow.
     32  * <p>
     33  * Implement {@link DragStartHelper.OnDragStartListener} to start the drag operation:
     34  * <pre>
     35  * DragStartHelper.OnDragStartListener listener = new DragStartHelper.OnDragStartListener {
     36  *     protected void onDragStart(View view, DragStartHelper helper) {
     37  *         View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
     38  *             public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
     39  *                 super.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
     40  *                 helper.getTouchPosition(shadowTouchPoint);
     41  *             }
     42  *         };
     43  *         view.startDrag(mClipData, shadowBuilder, mLocalState, mDragFlags);
     44  *     }
     45  * };
     46  * mDragStartHelper = new DragStartHelper(mDraggableView, listener);
     47  * </pre>
     48  * Once created, DragStartHelper can be attached to a view (this will replace existing long click
     49  * and touch listeners):
     50  * <pre>
     51  * mDragStartHelper.attach();
     52  * </pre>
     53  * It may also be used in combination with existing listeners:
     54  * <pre>
     55  * public boolean onTouch(View view, MotionEvent event) {
     56  *     if (mDragStartHelper.onTouch(view, event)) {
     57  *         return true;
     58  *     }
     59  *     return handleTouchEvent(view, event);
     60  * }
     61  * public boolean onLongClick(View view) {
     62  *     if (mDragStartHelper.onLongClick(view)) {
     63  *         return true;
     64  *     }
     65  *     return handleLongClickEvent(view);
     66  * }
     67  * </pre>
     68  */
     69 public class DragStartHelper {
     70     private final View mView;
     71     private final OnDragStartListener mListener;
     72 
     73     private int mLastTouchX, mLastTouchY;
     74     private boolean mDragging;
     75 
     76     /**
     77      * Interface definition for a callback to be invoked when a drag start gesture is detected.
     78      */
     79     public interface OnDragStartListener {
     80         /**
     81          * Called when a drag start gesture has been detected.
     82          *
     83          * @param v The view over which the drag start gesture has been detected.
     84          * @param helper The DragStartHelper object which detected the gesture.
     85          * @return True if the listener has started the drag operation, false otherwise.
     86          */
     87         boolean onDragStart(View v, DragStartHelper helper);
     88     }
     89 
     90     /**
     91      * Create a DragStartHelper associated with the specified view.
     92      * The newly created helper is not initially attached to the view, {@link #attach} must be
     93      * called explicitly.
     94      * @param view A View
     95      */
     96     public DragStartHelper(View view, OnDragStartListener listener) {
     97         mView = view;
     98         mListener = listener;
     99     }
    100 
    101     /**
    102      * Attach the helper to the view.
    103      * <p>
    104      * This will replace previously existing touch and long click listeners.
    105      */
    106     public void attach() {
    107         mView.setOnLongClickListener(mLongClickListener);
    108         mView.setOnTouchListener(mTouchListener);
    109     }
    110 
    111     /**
    112      * Detach the helper from the view.
    113      * <p>
    114      * This will reset touch and long click listeners to {@code null}.
    115      */
    116     public void detach() {
    117         mView.setOnLongClickListener(null);
    118         mView.setOnTouchListener(null);
    119     }
    120 
    121     /**
    122      * Handle a touch event.
    123      * @param v The view the touch event has been dispatched to.
    124      * @param event The MotionEvent object containing full information about
    125      *        the event.
    126      * @return True if the listener has consumed the event, false otherwise.
    127      */
    128     public boolean onTouch(View v, MotionEvent event) {
    129         final int x = (int) event.getX();
    130         final int y = (int) event.getY();
    131         switch (event.getAction()) {
    132             case MotionEvent.ACTION_DOWN:
    133                 mLastTouchX = x;
    134                 mLastTouchY = y;
    135                 break;
    136 
    137             case MotionEvent.ACTION_MOVE:
    138                 if (!MotionEventCompat.isFromSource(event, InputDeviceCompat.SOURCE_MOUSE)
    139                         || (event.getButtonState()
    140                                 & MotionEvent.BUTTON_PRIMARY) == 0) {
    141                     break;
    142                 }
    143                 if (mDragging) {
    144                     // Ignore ACTION_MOVE events once the drag operation is in progress.
    145                     break;
    146                 }
    147                 if (mLastTouchX == x && mLastTouchY == y) {
    148                     // Do not call the listener unless the pointer position has actually changed.
    149                     break;
    150                 }
    151                 mLastTouchX = x;
    152                 mLastTouchY = y;
    153                 mDragging = mListener.onDragStart(v, this);
    154                 return mDragging;
    155 
    156             case MotionEvent.ACTION_UP:
    157             case MotionEvent.ACTION_CANCEL:
    158                 mDragging = false;
    159                 break;
    160         }
    161         return false;
    162     }
    163 
    164     /**
    165      * Handle a long click event.
    166      * @param v The view that was clicked and held.
    167      * @return true if the callback consumed the long click, false otherwise.
    168      */
    169     public boolean onLongClick(View v) {
    170         return mListener.onDragStart(v, this);
    171     }
    172 
    173     /**
    174      * Compute the position of the touch event that started the drag operation.
    175      * @param point The position of the touch event that started the drag operation.
    176      */
    177     public void getTouchPosition(Point point) {
    178         point.set(mLastTouchX, mLastTouchY);
    179     }
    180 
    181     private final View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() {
    182         @Override
    183         public boolean onLongClick(View v) {
    184             return DragStartHelper.this.onLongClick(v);
    185         }
    186     };
    187 
    188     private final View.OnTouchListener mTouchListener = new View.OnTouchListener() {
    189         @Override
    190         public boolean onTouch(View v, MotionEvent event) {
    191             return DragStartHelper.this.onTouch(v, event);
    192         }
    193     };
    194 }
    195 
    196