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