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
17package android.support.v13.view;
18
19
20import android.graphics.Point;
21import android.support.v4.view.InputDeviceCompat;
22import android.support.v4.view.MotionEventCompat;
23import android.view.MotionEvent;
24import 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 */
71public 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         */
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     */
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     */
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     */
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     */
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     */
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     */
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