mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-09-10 08:16:48 +02:00
475 lines
15 KiB
Java
475 lines
15 KiB
Java
|
package com.mobeta.android.dslv;
|
||
|
|
||
|
import android.graphics.Point;
|
||
|
import android.view.GestureDetector;
|
||
|
import android.view.HapticFeedbackConstants;
|
||
|
import android.view.MotionEvent;
|
||
|
import android.view.View;
|
||
|
import android.view.ViewConfiguration;
|
||
|
import android.widget.AdapterView;
|
||
|
|
||
|
/**
|
||
|
* Class that starts and stops item drags on a {@link DragSortListView}
|
||
|
* based on touch gestures. This class also inherits from
|
||
|
* {@link SimpleFloatViewManager}, which provides basic float View
|
||
|
* creation.
|
||
|
*
|
||
|
* An instance of this class is meant to be passed to the methods
|
||
|
* {@link DragSortListView#setTouchListener()} and
|
||
|
* {@link DragSortListView#setFloatViewManager()} of your
|
||
|
* {@link DragSortListView} instance.
|
||
|
*/
|
||
|
public class DragSortController extends SimpleFloatViewManager implements View.OnTouchListener,
|
||
|
GestureDetector.OnGestureListener {
|
||
|
|
||
|
/**
|
||
|
* Drag init mode enum.
|
||
|
*/
|
||
|
public static final int ON_DOWN = 0;
|
||
|
public static final int ON_DRAG = 1;
|
||
|
public static final int ON_LONG_PRESS = 2;
|
||
|
|
||
|
private int mDragInitMode = ON_DOWN;
|
||
|
|
||
|
private boolean mSortEnabled = true;
|
||
|
|
||
|
/**
|
||
|
* Remove mode enum.
|
||
|
*/
|
||
|
public static final int CLICK_REMOVE = 0;
|
||
|
public static final int FLING_REMOVE = 1;
|
||
|
|
||
|
/**
|
||
|
* The current remove mode.
|
||
|
*/
|
||
|
private int mRemoveMode;
|
||
|
|
||
|
private boolean mRemoveEnabled = false;
|
||
|
private boolean mIsRemoving = false;
|
||
|
|
||
|
private GestureDetector mDetector;
|
||
|
|
||
|
private GestureDetector mFlingRemoveDetector;
|
||
|
|
||
|
private int mTouchSlop;
|
||
|
|
||
|
public static final int MISS = -1;
|
||
|
|
||
|
private int mHitPos = MISS;
|
||
|
private int mFlingHitPos = MISS;
|
||
|
|
||
|
private int mClickRemoveHitPos = MISS;
|
||
|
|
||
|
private int[] mTempLoc = new int[2];
|
||
|
|
||
|
private int mItemX;
|
||
|
private int mItemY;
|
||
|
|
||
|
private int mCurrX;
|
||
|
private int mCurrY;
|
||
|
|
||
|
private boolean mDragging = false;
|
||
|
|
||
|
private float mFlingSpeed = 500f;
|
||
|
|
||
|
private int mDragHandleId;
|
||
|
|
||
|
private int mClickRemoveId;
|
||
|
|
||
|
private int mFlingHandleId;
|
||
|
private boolean mCanDrag;
|
||
|
|
||
|
private DragSortListView mDslv;
|
||
|
private int mPositionX;
|
||
|
|
||
|
/**
|
||
|
* Calls {@link #DragSortController(DragSortListView, int)} with a
|
||
|
* 0 drag handle id, FLING_RIGHT_REMOVE remove mode,
|
||
|
* and ON_DOWN drag init. By default, sorting is enabled, and
|
||
|
* removal is disabled.
|
||
|
*
|
||
|
* @param dslv The DSLV instance
|
||
|
*/
|
||
|
public DragSortController(DragSortListView dslv) {
|
||
|
this(dslv, 0, ON_DOWN, FLING_REMOVE);
|
||
|
}
|
||
|
|
||
|
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode,
|
||
|
int removeMode) {
|
||
|
|
||
|
this(dslv, dragHandleId, dragInitMode, removeMode, 0);
|
||
|
}
|
||
|
|
||
|
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode,
|
||
|
int removeMode, int clickRemoveId) {
|
||
|
|
||
|
this(dslv, dragHandleId, dragInitMode, removeMode, clickRemoveId, 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* By default, sorting is enabled, and removal is disabled.
|
||
|
*
|
||
|
* @param dslv The DSLV instance
|
||
|
* @param dragHandleId The resource id of the View that represents
|
||
|
* the drag handle in a list item.
|
||
|
*/
|
||
|
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode,
|
||
|
int removeMode, int clickRemoveId, int flingHandleId) {
|
||
|
|
||
|
super(dslv);
|
||
|
mDslv = dslv;
|
||
|
mDetector = new GestureDetector(dslv.getContext(), this);
|
||
|
mFlingRemoveDetector = new GestureDetector(dslv.getContext(), mFlingRemoveListener);
|
||
|
mFlingRemoveDetector.setIsLongpressEnabled(false);
|
||
|
mTouchSlop = ViewConfiguration.get(dslv.getContext()).getScaledTouchSlop();
|
||
|
mDragHandleId = dragHandleId;
|
||
|
mClickRemoveId = clickRemoveId;
|
||
|
mFlingHandleId = flingHandleId;
|
||
|
setRemoveMode(removeMode);
|
||
|
setDragInitMode(dragInitMode);
|
||
|
}
|
||
|
|
||
|
|
||
|
public int getDragInitMode() {
|
||
|
return mDragInitMode;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set how a drag is initiated. Needs to be one of
|
||
|
* {@link ON_DOWN}, {@link ON_DRAG}, or {@link ON_LONG_PRESS}.
|
||
|
*
|
||
|
* @param mode The drag init mode.
|
||
|
*/
|
||
|
public void setDragInitMode(int mode) {
|
||
|
mDragInitMode = mode;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Enable/Disable list item sorting. Disabling is useful if only item
|
||
|
* removal is desired. Prevents drags in the vertical direction.
|
||
|
*
|
||
|
* @param enabled Set <code>true</code> to enable list
|
||
|
* item sorting.
|
||
|
*/
|
||
|
public void setSortEnabled(boolean enabled) {
|
||
|
mSortEnabled = enabled;
|
||
|
}
|
||
|
|
||
|
public boolean isSortEnabled() {
|
||
|
return mSortEnabled;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* One of {@link CLICK_REMOVE}, {@link FLING_RIGHT_REMOVE},
|
||
|
* {@link FLING_LEFT_REMOVE},
|
||
|
* {@link SLIDE_RIGHT_REMOVE}, or {@link SLIDE_LEFT_REMOVE}.
|
||
|
*/
|
||
|
public void setRemoveMode(int mode) {
|
||
|
mRemoveMode = mode;
|
||
|
}
|
||
|
|
||
|
public int getRemoveMode() {
|
||
|
return mRemoveMode;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Enable/Disable item removal without affecting remove mode.
|
||
|
*/
|
||
|
public void setRemoveEnabled(boolean enabled) {
|
||
|
mRemoveEnabled = enabled;
|
||
|
}
|
||
|
|
||
|
public boolean isRemoveEnabled() {
|
||
|
return mRemoveEnabled;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the resource id for the View that represents the drag
|
||
|
* handle in a list item.
|
||
|
*
|
||
|
* @param id An android resource id.
|
||
|
*/
|
||
|
public void setDragHandleId(int id) {
|
||
|
mDragHandleId = id;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the resource id for the View that represents the fling
|
||
|
* handle in a list item.
|
||
|
*
|
||
|
* @param id An android resource id.
|
||
|
*/
|
||
|
public void setFlingHandleId(int id) {
|
||
|
mFlingHandleId = id;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the resource id for the View that represents click
|
||
|
* removal button.
|
||
|
*
|
||
|
* @param id An android resource id.
|
||
|
*/
|
||
|
public void setClickRemoveId(int id) {
|
||
|
mClickRemoveId = id;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets flags to restrict certain motions of the floating View
|
||
|
* based on DragSortController settings (such as remove mode).
|
||
|
* Starts the drag on the DragSortListView.
|
||
|
*
|
||
|
* @param position The list item position (includes headers).
|
||
|
* @param deltaX Touch x-coord minus left edge of floating View.
|
||
|
* @param deltaY Touch y-coord minus top edge of floating View.
|
||
|
*
|
||
|
* @return True if drag started, false otherwise.
|
||
|
*/
|
||
|
public boolean startDrag(int position, int deltaX, int deltaY) {
|
||
|
|
||
|
int dragFlags = 0;
|
||
|
if (mSortEnabled && !mIsRemoving) {
|
||
|
dragFlags |= DragSortListView.DRAG_POS_Y | DragSortListView.DRAG_NEG_Y;
|
||
|
}
|
||
|
if (mRemoveEnabled && mIsRemoving) {
|
||
|
dragFlags |= DragSortListView.DRAG_POS_X;
|
||
|
dragFlags |= DragSortListView.DRAG_NEG_X;
|
||
|
}
|
||
|
|
||
|
mDragging = mDslv.startDrag(position - mDslv.getHeaderViewsCount(), dragFlags, deltaX,
|
||
|
deltaY);
|
||
|
return mDragging;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean onTouch(View v, MotionEvent ev) {
|
||
|
if (!mDslv.isDragEnabled() || mDslv.listViewIntercepted()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
mDetector.onTouchEvent(ev);
|
||
|
if (mRemoveEnabled && mDragging && mRemoveMode == FLING_REMOVE) {
|
||
|
mFlingRemoveDetector.onTouchEvent(ev);
|
||
|
}
|
||
|
|
||
|
int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||
|
switch (action) {
|
||
|
case MotionEvent.ACTION_DOWN:
|
||
|
mCurrX = (int) ev.getX();
|
||
|
mCurrY = (int) ev.getY();
|
||
|
break;
|
||
|
case MotionEvent.ACTION_UP:
|
||
|
if (mRemoveEnabled && mIsRemoving) {
|
||
|
int x = mPositionX >= 0 ? mPositionX : -mPositionX;
|
||
|
int removePoint = mDslv.getWidth() / 2;
|
||
|
if (x > removePoint) {
|
||
|
mDslv.stopDragWithVelocity(true, 0);
|
||
|
}
|
||
|
}
|
||
|
case MotionEvent.ACTION_CANCEL:
|
||
|
mIsRemoving = false;
|
||
|
mDragging = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Overrides to provide fading when slide removal is enabled.
|
||
|
*/
|
||
|
@Override
|
||
|
public void onDragFloatView(View floatView, Point position, Point touch) {
|
||
|
|
||
|
if (mRemoveEnabled && mIsRemoving) {
|
||
|
mPositionX = position.x;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the position to start dragging based on the ACTION_DOWN
|
||
|
* MotionEvent. This function simply calls
|
||
|
* {@link #dragHandleHitPosition(MotionEvent)}. Override
|
||
|
* to change drag handle behavior;
|
||
|
* this function is called internally when an ACTION_DOWN
|
||
|
* event is detected.
|
||
|
*
|
||
|
* @param ev The ACTION_DOWN MotionEvent.
|
||
|
*
|
||
|
* @return The list position to drag if a drag-init gesture is
|
||
|
* detected; MISS if unsuccessful.
|
||
|
*/
|
||
|
public int startDragPosition(MotionEvent ev) {
|
||
|
return dragHandleHitPosition(ev);
|
||
|
}
|
||
|
|
||
|
public int startFlingPosition(MotionEvent ev) {
|
||
|
return mRemoveMode == FLING_REMOVE ? flingHandleHitPosition(ev) : MISS;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks for the touch of an item's drag handle (specified by
|
||
|
* {@link #setDragHandleId(int)}), and returns that item's position
|
||
|
* if a drag handle touch was detected.
|
||
|
*
|
||
|
* @param ev The ACTION_DOWN MotionEvent.
|
||
|
* @return The list position of the item whose drag handle was
|
||
|
* touched; MISS if unsuccessful.
|
||
|
*/
|
||
|
public int dragHandleHitPosition(MotionEvent ev) {
|
||
|
return viewIdHitPosition(ev, mDragHandleId);
|
||
|
}
|
||
|
|
||
|
public int flingHandleHitPosition(MotionEvent ev) {
|
||
|
return viewIdHitPosition(ev, mFlingHandleId);
|
||
|
}
|
||
|
|
||
|
public int viewIdHitPosition(MotionEvent ev, int id) {
|
||
|
final int x = (int) ev.getX();
|
||
|
final int y = (int) ev.getY();
|
||
|
|
||
|
int touchPos = mDslv.pointToPosition(x, y); // includes headers/footers
|
||
|
|
||
|
final int numHeaders = mDslv.getHeaderViewsCount();
|
||
|
final int numFooters = mDslv.getFooterViewsCount();
|
||
|
final int count = mDslv.getCount();
|
||
|
|
||
|
// We're only interested if the touch was on an
|
||
|
// item that's not a header or footer.
|
||
|
if (touchPos != AdapterView.INVALID_POSITION && touchPos >= numHeaders
|
||
|
&& touchPos < (count - numFooters)) {
|
||
|
final View item = mDslv.getChildAt(touchPos - mDslv.getFirstVisiblePosition());
|
||
|
final int rawX = (int) ev.getRawX();
|
||
|
final int rawY = (int) ev.getRawY();
|
||
|
|
||
|
View dragBox = id == 0 ? item : (View) item.findViewById(id);
|
||
|
if (dragBox != null) {
|
||
|
dragBox.getLocationOnScreen(mTempLoc);
|
||
|
|
||
|
if (rawX > mTempLoc[0] && rawY > mTempLoc[1] &&
|
||
|
rawX < mTempLoc[0] + dragBox.getWidth() &&
|
||
|
rawY < mTempLoc[1] + dragBox.getHeight()) {
|
||
|
|
||
|
mItemX = item.getLeft();
|
||
|
mItemY = item.getTop();
|
||
|
|
||
|
return touchPos;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return MISS;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean onDown(MotionEvent ev) {
|
||
|
if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
|
||
|
mClickRemoveHitPos = viewIdHitPosition(ev, mClickRemoveId);
|
||
|
}
|
||
|
|
||
|
mHitPos = startDragPosition(ev);
|
||
|
if (mHitPos != MISS && mDragInitMode == ON_DOWN) {
|
||
|
startDrag(mHitPos, (int) ev.getX() - mItemX, (int) ev.getY() - mItemY);
|
||
|
}
|
||
|
|
||
|
mIsRemoving = false;
|
||
|
mCanDrag = true;
|
||
|
mPositionX = 0;
|
||
|
mFlingHitPos = startFlingPosition(ev);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||
|
|
||
|
if (e1 == null || e2 == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
final int x1 = (int) e1.getX();
|
||
|
final int y1 = (int) e1.getY();
|
||
|
final int x2 = (int) e2.getX();
|
||
|
final int y2 = (int) e2.getY();
|
||
|
final int deltaX = x2 - mItemX;
|
||
|
final int deltaY = y2 - mItemY;
|
||
|
|
||
|
if (mCanDrag && !mDragging && (mHitPos != MISS || mFlingHitPos != MISS)) {
|
||
|
if (mHitPos != MISS) {
|
||
|
if (mDragInitMode == ON_DRAG && Math.abs(y2 - y1) > mTouchSlop && mSortEnabled) {
|
||
|
startDrag(mHitPos, deltaX, deltaY);
|
||
|
}
|
||
|
else if (mDragInitMode != ON_DOWN && Math.abs(x2 - x1) > mTouchSlop
|
||
|
&& mRemoveEnabled)
|
||
|
{
|
||
|
mIsRemoving = true;
|
||
|
startDrag(mFlingHitPos, deltaX, deltaY);
|
||
|
}
|
||
|
} else if (mFlingHitPos != MISS) {
|
||
|
if (Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) {
|
||
|
mIsRemoving = true;
|
||
|
startDrag(mFlingHitPos, deltaX, deltaY);
|
||
|
} else if (Math.abs(y2 - y1) > mTouchSlop) {
|
||
|
mCanDrag = false; // if started to scroll the list then
|
||
|
// don't allow sorting nor fling-removing
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onLongPress(MotionEvent e) {
|
||
|
if (mHitPos != MISS && mDragInitMode == ON_LONG_PRESS) {
|
||
|
mDslv.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||
|
startDrag(mHitPos, mCurrX - mItemX, mCurrY - mItemY);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// complete the OnGestureListener interface
|
||
|
@Override
|
||
|
public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// complete the OnGestureListener interface
|
||
|
@Override
|
||
|
public boolean onSingleTapUp(MotionEvent ev) {
|
||
|
if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
|
||
|
if (mClickRemoveHitPos != MISS) {
|
||
|
mDslv.removeItem(mClickRemoveHitPos - mDslv.getHeaderViewsCount());
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// complete the OnGestureListener interface
|
||
|
@Override
|
||
|
public void onShowPress(MotionEvent ev) {
|
||
|
// do nothing
|
||
|
}
|
||
|
|
||
|
private GestureDetector.OnGestureListener mFlingRemoveListener =
|
||
|
new GestureDetector.SimpleOnGestureListener() {
|
||
|
@Override
|
||
|
public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
|
||
|
float velocityY) {
|
||
|
if (mRemoveEnabled && mIsRemoving) {
|
||
|
int w = mDslv.getWidth();
|
||
|
int minPos = w / 5;
|
||
|
if (velocityX > mFlingSpeed) {
|
||
|
if (mPositionX > -minPos) {
|
||
|
mDslv.stopDragWithVelocity(true, velocityX);
|
||
|
}
|
||
|
} else if (velocityX < -mFlingSpeed) {
|
||
|
if (mPositionX < minPos) {
|
||
|
mDslv.stopDragWithVelocity(true, velocityX);
|
||
|
}
|
||
|
}
|
||
|
mIsRemoving = false;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
}
|