2 package com.andrew.apollo.dragdrop;
4 import android.graphics.Point;
5 import android.view.GestureDetector;
6 import android.view.HapticFeedbackConstants;
7 import android.view.MotionEvent;
8 import android.view.View;
9 import android.view.ViewConfiguration;
10 import android.widget.AdapterView;
13 * Class that starts and stops item drags on a {@link DragSortListView} based on
14 * touch gestures. This class also inherits from {@link SimpleFloatViewManager},
15 * which provides basic float View creation. An instance of this class is meant
16 * to be passed to the methods {@link DragSortListView#setTouchListener()} and
17 * {@link DragSortListView#setFloatViewManager()} of your
18 * {@link DragSortListView} instance.
20 public class DragSortController extends SimpleFloatViewManager implements View.OnTouchListener,
21 GestureDetector.OnGestureListener {
23 public final static int ON_DOWN = 0;
25 public final static int ON_DRAG = 1;
27 public final static int ON_LONG_PRESS = 2;
29 public final static int FLING_RIGHT_REMOVE = 0;
31 public final static int FLING_LEFT_REMOVE = 1;
33 public final static int SLIDE_RIGHT_REMOVE = 2;
35 public final static int SLIDE_LEFT_REMOVE = 3;
37 public final static int MISS = -1;
39 private final GestureDetector mDetector;
41 private final GestureDetector mFlingRemoveDetector;
43 private final int mTouchSlop;
45 private final int[] mTempLoc = new int[2];
47 private final float mFlingSpeed = 500f;
49 private final DragSortListView mDslv;
51 private boolean mSortEnabled = true;
53 private boolean mRemoveEnabled = false;
55 private boolean mDragging = false;
57 private int mDragInitMode = ON_DOWN;
59 private int mRemoveMode;
61 private int mHitPos = MISS;
71 private int mDragHandleId;
73 private float mOrigFloatAlpha = 1.0f;
76 * Calls {@link #DragSortController(DragSortListView, int)} with a 0 drag
77 * handle id, FLING_RIGHT_REMOVE remove mode, and ON_DOWN drag init. By
78 * default, sorting is enabled, and removal is disabled.
80 * @param dslv The DSLV instance
82 public DragSortController(DragSortListView dslv) {
83 this(dslv, 0, ON_DOWN, FLING_RIGHT_REMOVE);
87 * By default, sorting is enabled, and removal is disabled.
89 * @param dslv The DSLV instance
90 * @param dragHandleId The resource id of the View that represents the drag
91 * handle in a list item.
93 public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode,
97 mDetector = new GestureDetector(dslv.getContext(), this);
98 mFlingRemoveDetector = new GestureDetector(dslv.getContext(), mFlingRemoveListener);
99 mFlingRemoveDetector.setIsLongpressEnabled(false);
100 mTouchSlop = ViewConfiguration.get(dslv.getContext()).getScaledTouchSlop();
101 mDragHandleId = dragHandleId;
102 setRemoveMode(removeMode);
103 setDragInitMode(dragInitMode);
104 mOrigFloatAlpha = dslv.getFloatAlpha();
108 * @return The current drag init mode.
110 public int getDragInitMode() {
111 return mDragInitMode;
115 * Set how a drag is initiated. Needs to be one of {@link ON_DOWN},
116 * {@link ON_DRAG}, or {@link ON_LONG_PRESS}.
118 * @param mode The drag init mode.
120 public void setDragInitMode(int mode) {
121 mDragInitMode = mode;
125 * Enable/Disable list item sorting. Disabling is useful if only item
126 * removal is desired. Prevents drags in the vertical direction.
128 * @param enabled Set <code>true</code> to enable list item sorting.
130 public void setSortEnabled(boolean enabled) {
131 mSortEnabled = enabled;
135 * @return True if sort is enabled, false otherwise.
137 public boolean isSortEnabled() {
142 * One of {@link FLING_RIGHT_REMOVE}, {@link FLING_LEFT_REMOVE},
143 * {@link SLIDE_RIGHT_REMOVE}, or {@link SLIDE_LEFT_REMOVE}.
145 public void setRemoveMode(int mode) {
150 * @return The current remove mode.
152 public int getRemoveMode() {
157 * Enable/Disable item removal without affecting remove mode.
159 public void setRemoveEnabled(boolean enabled) {
160 mRemoveEnabled = enabled;
164 * @return True if remove is enabled, false otherwise.
166 public boolean isRemoveEnabled() {
167 return mRemoveEnabled;
171 * Set the resource id for the View that represents the drag handle in a
174 * @param id An android resource id.
176 public void setDragHandleId(int id) {
181 * Sets flags to restrict certain motions of the floating View based on
182 * DragSortController settings (such as remove mode). Starts the drag on the
185 * @param position The list item position (includes headers).
186 * @param deltaX Touch x-coord minus left edge of floating View.
187 * @param deltaY Touch y-coord minus top edge of floating View.
188 * @return True if drag started, false otherwise.
190 public boolean startDrag(int position, int deltaX, int deltaY) {
194 mDragFlags |= DragSortListView.DRAG_POS_Y | DragSortListView.DRAG_NEG_Y;
197 if (mRemoveEnabled) {
198 if (mRemoveMode == FLING_RIGHT_REMOVE) {
199 mDragFlags |= DragSortListView.DRAG_POS_X;
200 } else if (mRemoveMode == FLING_LEFT_REMOVE) {
201 mDragFlags |= DragSortListView.DRAG_NEG_X;
205 mDragging = mDslv.startDrag(position - mDslv.getHeaderViewsCount(), mDragFlags, deltaX,
214 public boolean onTouch(View v, MotionEvent ev) {
215 mDetector.onTouchEvent(ev);
216 if (mRemoveEnabled && mDragging
217 && (mRemoveMode == FLING_RIGHT_REMOVE || mRemoveMode == FLING_LEFT_REMOVE)) {
218 mFlingRemoveDetector.onTouchEvent(ev);
221 final int mAction = ev.getAction() & MotionEvent.ACTION_MASK;
224 case MotionEvent.ACTION_DOWN:
225 mCurrX = (int)ev.getX();
226 mCurrY = (int)ev.getY();
228 case MotionEvent.ACTION_UP:
229 if (mRemoveEnabled) {
230 final int x = (int)ev.getX();
231 int thirdW = mDslv.getWidth() / 3;
232 int twoThirdW = mDslv.getWidth() - thirdW;
233 if ((mRemoveMode == SLIDE_RIGHT_REMOVE && x > twoThirdW)
234 || (mRemoveMode == SLIDE_LEFT_REMOVE && x < thirdW)) {
235 mDslv.stopDrag(true);
238 case MotionEvent.ACTION_CANCEL:
246 * Overrides to provide fading when slide removal is enabled.
249 public void onDragFloatView(View floatView, Point position, Point touch) {
251 if (mRemoveEnabled) {
254 if (mRemoveMode == SLIDE_RIGHT_REMOVE) {
255 int width = mDslv.getWidth();
256 int thirdWidth = width / 3;
259 if (x < thirdWidth) {
261 } else if (x < width - thirdWidth) {
262 alpha = ((float)(width - thirdWidth - x)) / ((float)thirdWidth);
266 mDslv.setFloatAlpha(mOrigFloatAlpha * alpha);
267 } else if (mRemoveMode == SLIDE_LEFT_REMOVE) {
268 int width = mDslv.getWidth();
269 int thirdWidth = width / 3;
272 if (x < thirdWidth) {
274 } else if (x < width - thirdWidth) {
275 alpha = ((float)(x - thirdWidth)) / ((float)thirdWidth);
279 mDslv.setFloatAlpha(mOrigFloatAlpha * alpha);
285 * Get the position to start dragging based on the ACTION_DOWN MotionEvent.
286 * This function simply calls {@link #dragHandleHitPosition(MotionEvent)}.
287 * Override to change drag handle behavior; this function is called
288 * internally when an ACTION_DOWN event is detected.
290 * @param ev The ACTION_DOWN MotionEvent.
291 * @return The list position to drag if a drag-init gesture is detected;
292 * MISS if unsuccessful.
294 public int startDragPosition(MotionEvent ev) {
295 return dragHandleHitPosition(ev);
299 * Checks for the touch of an item's drag handle (specified by
300 * {@link #setDragHandleId(int)}), and returns that item's position if a
301 * drag handle touch was detected.
303 * @param ev The ACTION_DOWN MotionEvent.
304 * @return The list position of the item whose drag handle was touched; MISS
307 public int dragHandleHitPosition(MotionEvent ev) {
308 final int x = (int)ev.getX();
309 final int y = (int)ev.getY();
311 int touchPos = mDslv.pointToPosition(x, y);
313 final int numHeaders = mDslv.getHeaderViewsCount();
314 final int numFooters = mDslv.getFooterViewsCount();
315 final int count = mDslv.getCount();
317 if (touchPos != AdapterView.INVALID_POSITION && touchPos >= numHeaders
318 && touchPos < (count - numFooters)) {
319 final View item = mDslv.getChildAt(touchPos - mDslv.getFirstVisiblePosition());
320 final int rawX = (int)ev.getRawX();
321 final int rawY = (int)ev.getRawY();
323 View dragBox = item.findViewById(mDragHandleId);
324 if (dragBox != null) {
325 dragBox.getLocationOnScreen(mTempLoc);
327 if (rawX > mTempLoc[0] && rawY > mTempLoc[1]
328 && rawX < mTempLoc[0] + dragBox.getWidth()
329 && rawY < mTempLoc[1] + dragBox.getHeight()) {
331 mItemX = item.getLeft();
332 mItemY = item.getTop();
345 public boolean onDown(MotionEvent ev) {
346 mHitPos = startDragPosition(ev);
348 if (mHitPos != MISS && mDragInitMode == ON_DOWN) {
349 startDrag(mHitPos, (int)ev.getX() - mItemX, (int)ev.getY() - mItemY);
359 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
360 if (mHitPos != MISS && mDragInitMode == ON_DRAG && !mDragging) {
361 final int x1 = (int)e1.getX();
362 final int y1 = (int)e1.getY();
363 final int x2 = (int)e2.getX();
364 final int y2 = (int)e2.getY();
366 boolean start = false;
367 if (mRemoveEnabled && mSortEnabled) {
369 } else if (mRemoveEnabled) {
370 start = Math.abs(x2 - x1) > mTouchSlop;
371 } else if (mSortEnabled) {
372 start = Math.abs(y2 - y1) > mTouchSlop;
376 startDrag(mHitPos, x2 - mItemX, y2 - mItemY);
386 public void onLongPress(MotionEvent e) {
387 if (mHitPos != MISS && mDragInitMode == ON_LONG_PRESS) {
388 mDslv.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
389 startDrag(mHitPos, mCurrX - mItemX, mCurrY - mItemY);
397 public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
405 public boolean onSingleTapUp(MotionEvent ev) {
413 public void onShowPress(MotionEvent ev) {
416 private final GestureDetector.OnGestureListener mFlingRemoveListener = new GestureDetector.SimpleOnGestureListener() {
422 public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
424 if (mRemoveEnabled) {
425 switch (mRemoveMode) {
426 case FLING_RIGHT_REMOVE:
427 if (velocityX > mFlingSpeed) {
428 mDslv.stopDrag(true);
431 case FLING_LEFT_REMOVE:
432 if (velocityX < -mFlingSpeed) {
433 mDslv.stopDrag(true);