mirror of https://github.com/kurisufriend/Clover
parent
bb8bfe7984
commit
b86d8be9b8
@ -1,6 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<org.floens.chan.ui.view.DynamicListView xmlns:android="http://schemas.android.com/apk/res/android" |
|
||||||
android:layout_width="match_parent" |
|
||||||
android:layout_height="match_parent" |
|
||||||
android:id="@+id/board_edit_list" > |
|
||||||
</org.floens.chan.ui.view.DynamicListView> |
|
@ -0,0 +1,26 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:orientation="horizontal" > |
||||||
|
|
||||||
|
<org.floens.chan.ui.view.DragGripView |
||||||
|
android:id="@+id/drag_handle" |
||||||
|
android:layout_width="64dp" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:layout_gravity="fill_vertical|start" |
||||||
|
android:paddingBottom="12dp" |
||||||
|
android:paddingStart="16dp" |
||||||
|
android:paddingTop="12dp" |
||||||
|
android:color="#2333" /> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/text" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_marginStart="36dp" |
||||||
|
android:gravity="center_vertical" |
||||||
|
android:minHeight="50sp" |
||||||
|
android:textSize="24sp" /> |
||||||
|
|
||||||
|
</FrameLayout> |
@ -1,28 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|
||||||
android:layout_width="match_parent" |
|
||||||
android:layout_height="match_parent" |
|
||||||
android:orientation="horizontal" > |
|
||||||
<TextView |
|
||||||
android:id="@+id/board_view_text" |
|
||||||
android:layout_width="wrap_content" |
|
||||||
android:layout_height="wrap_content" |
|
||||||
android:textSize="24sp" |
|
||||||
android:gravity="center_vertical" |
|
||||||
android:paddingLeft="24dp" |
|
||||||
android:paddingRight="24dp" |
|
||||||
android:minHeight="60dp" |
|
||||||
/> |
|
||||||
<Button |
|
||||||
android:id="@+id/board_view_delete" |
|
||||||
android:layout_alignParentRight="true" |
|
||||||
android:layout_marginTop="10dp" |
|
||||||
android:layout_marginRight="10dp" |
|
||||||
android:layout_height="40dp" |
|
||||||
android:layout_width="40dp" |
|
||||||
android:focusable="false" |
|
||||||
android:focusableInTouchMode="false" |
|
||||||
android:background="@drawable/ic_action_discard" /> |
|
||||||
|
|
||||||
</RelativeLayout> |
|
||||||
|
|
@ -0,0 +1,469 @@ |
|||||||
|
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(); |
||||||
|
|
||||||
|
// Log.d("mobeta", "touch down on position " + itemnum);
|
||||||
|
// 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) { |
||||||
|
|
||||||
|
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 whatever
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onLongPress(MotionEvent e) { |
||||||
|
// Log.d("mobeta", "lift listener long pressed");
|
||||||
|
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) { |
||||||
|
// Log.d("mobeta", "on fling remove called");
|
||||||
|
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; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,239 @@ |
|||||||
|
package com.mobeta.android.dslv; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.database.Cursor; |
||||||
|
import android.support.v4.widget.CursorAdapter; |
||||||
|
import android.util.SparseIntArray; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.ListAdapter; |
||||||
|
|
||||||
|
/** |
||||||
|
* A subclass of {@link android.widget.CursorAdapter} that provides reordering |
||||||
|
* of the elements in the Cursor based on completed drag-sort operations. The |
||||||
|
* reordering is a simple mapping of list positions into Cursor positions (the |
||||||
|
* Cursor is unchanged). To persist changes made by drag-sorts, one can retrieve |
||||||
|
* the mapping with the {@link #getCursorPositions()} method, which returns the |
||||||
|
* reordered list of Cursor positions. |
||||||
|
* |
||||||
|
* An instance of this class is passed to |
||||||
|
* {@link DragSortListView#setAdapter(ListAdapter)} and, since this class
|
||||||
|
* implements the {@link DragSortListView.DragSortListener} interface, it is |
||||||
|
* automatically set as the DragSortListener for the DragSortListView instance. |
||||||
|
*/ |
||||||
|
public abstract class DragSortCursorAdapter extends CursorAdapter implements DragSortListView.DragSortListener { |
||||||
|
|
||||||
|
public static final int REMOVED = -1; |
||||||
|
|
||||||
|
/** |
||||||
|
* Key is ListView position, value is Cursor position |
||||||
|
*/ |
||||||
|
private final SparseIntArray mListMapping = new SparseIntArray(); |
||||||
|
|
||||||
|
private final ArrayList<Integer> mRemovedCursorPositions = new ArrayList<Integer>(); |
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") |
||||||
|
public DragSortCursorAdapter(Context context, Cursor c) { |
||||||
|
super(context, c); |
||||||
|
} |
||||||
|
|
||||||
|
public DragSortCursorAdapter(Context context, Cursor c, boolean autoRequery) { |
||||||
|
super(context, c, autoRequery); |
||||||
|
} |
||||||
|
|
||||||
|
public DragSortCursorAdapter(Context context, Cursor c, int flags) { |
||||||
|
super(context, c, flags); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Swaps Cursor and clears list-Cursor mapping. |
||||||
|
* |
||||||
|
* @see android.widget.CursorAdapter#swapCursor(android.database.Cursor) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public Cursor swapCursor(Cursor newCursor) { |
||||||
|
Cursor old = super.swapCursor(newCursor); |
||||||
|
resetMappings(); |
||||||
|
return old; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Changes Cursor and clears list-Cursor mapping. |
||||||
|
* |
||||||
|
* @see android.widget.CursorAdapter#changeCursor(android.database.Cursor) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void changeCursor(Cursor cursor) { |
||||||
|
super.changeCursor(cursor); |
||||||
|
resetMappings(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Resets list-cursor mapping. |
||||||
|
*/ |
||||||
|
public void reset() { |
||||||
|
resetMappings(); |
||||||
|
notifyDataSetChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
private void resetMappings() { |
||||||
|
mListMapping.clear(); |
||||||
|
mRemovedCursorPositions.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object getItem(int position) { |
||||||
|
return super.getItem(mListMapping.get(position, position)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public long getItemId(int position) { |
||||||
|
return super.getItemId(mListMapping.get(position, position)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public View getDropDownView(int position, View convertView, ViewGroup parent) { |
||||||
|
return super.getDropDownView(mListMapping.get(position, position), convertView, parent); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public View getView(int position, View convertView, ViewGroup parent) { |
||||||
|
return super.getView(mListMapping.get(position, position), convertView, parent); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* On drop, this updates the mapping between Cursor positions and ListView |
||||||
|
* positions. The Cursor is unchanged. Retrieve the current mapping with |
||||||
|
* {@link getCursorPositions()}. |
||||||
|
* |
||||||
|
* @see DragSortListView.DropListener#drop(int, int) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void drop(int from, int to) { |
||||||
|
if (from != to) { |
||||||
|
int cursorFrom = mListMapping.get(from, from); |
||||||
|
|
||||||
|
if (from > to) { |
||||||
|
for (int i = from; i > to; --i) { |
||||||
|
mListMapping.put(i, mListMapping.get(i - 1, i - 1)); |
||||||
|
} |
||||||
|
} else { |
||||||
|
for (int i = from; i < to; ++i) { |
||||||
|
mListMapping.put(i, mListMapping.get(i + 1, i + 1)); |
||||||
|
} |
||||||
|
} |
||||||
|
mListMapping.put(to, cursorFrom); |
||||||
|
|
||||||
|
cleanMapping(); |
||||||
|
notifyDataSetChanged(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* On remove, this updates the mapping between Cursor positions and ListView |
||||||
|
* positions. The Cursor is unchanged. Retrieve the current mapping with |
||||||
|
* {@link getCursorPositions()}. |
||||||
|
* |
||||||
|
* @see DragSortListView.RemoveListener#remove(int) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void remove(int which) { |
||||||
|
int cursorPos = mListMapping.get(which, which); |
||||||
|
if (!mRemovedCursorPositions.contains(cursorPos)) { |
||||||
|
mRemovedCursorPositions.add(cursorPos); |
||||||
|
} |
||||||
|
|
||||||
|
int newCount = getCount(); |
||||||
|
for (int i = which; i < newCount; ++i) { |
||||||
|
mListMapping.put(i, mListMapping.get(i + 1, i + 1)); |
||||||
|
} |
||||||
|
|
||||||
|
mListMapping.delete(newCount); |
||||||
|
|
||||||
|
cleanMapping(); |
||||||
|
notifyDataSetChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Does nothing. Just completes DragSortListener interface. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void drag(int from, int to) { |
||||||
|
// do nothing
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Remove unnecessary mappings from sparse array. |
||||||
|
*/ |
||||||
|
private void cleanMapping() { |
||||||
|
ArrayList<Integer> toRemove = new ArrayList<Integer>(); |
||||||
|
|
||||||
|
int size = mListMapping.size(); |
||||||
|
for (int i = 0; i < size; ++i) { |
||||||
|
if (mListMapping.keyAt(i) == mListMapping.valueAt(i)) { |
||||||
|
toRemove.add(mListMapping.keyAt(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
size = toRemove.size(); |
||||||
|
for (int i = 0; i < size; ++i) { |
||||||
|
mListMapping.delete(toRemove.get(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getCount() { |
||||||
|
return super.getCount() - mRemovedCursorPositions.size(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get the Cursor position mapped to by the provided list position (given |
||||||
|
* all previously handled drag-sort operations). |
||||||
|
* |
||||||
|
* @param position |
||||||
|
* List position |
||||||
|
* |
||||||
|
* @return The mapped-to Cursor position |
||||||
|
*/ |
||||||
|
public int getCursorPosition(int position) { |
||||||
|
return mListMapping.get(position, position); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get the current order of Cursor positions presented by the list. |
||||||
|
*/ |
||||||
|
public ArrayList<Integer> getCursorPositions() { |
||||||
|
ArrayList<Integer> result = new ArrayList<Integer>(); |
||||||
|
|
||||||
|
for (int i = 0; i < getCount(); ++i) { |
||||||
|
result.add(mListMapping.get(i, i)); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get the list position mapped to by the provided Cursor position. If the |
||||||
|
* provided Cursor position has been removed by a drag-sort, this returns |
||||||
|
* {@link #REMOVED}. |
||||||
|
* |
||||||
|
* @param cursorPosition |
||||||
|
* A Cursor position |
||||||
|
* @return The mapped-to list position or REMOVED |
||||||
|
*/ |
||||||
|
public int getListPosition(int cursorPosition) { |
||||||
|
if (mRemovedCursorPositions.contains(cursorPosition)) { |
||||||
|
return REMOVED; |
||||||
|
} |
||||||
|
|
||||||
|
int index = mListMapping.indexOfValue(cursorPosition); |
||||||
|
if (index < 0) { |
||||||
|
return cursorPosition; |
||||||
|
} else { |
||||||
|
return mListMapping.keyAt(index); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,92 @@ |
|||||||
|
package com.mobeta.android.dslv; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.view.Gravity; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.AbsListView; |
||||||
|
|
||||||
|
/** |
||||||
|
* Lightweight ViewGroup that wraps list items obtained from user's ListAdapter. |
||||||
|
* ItemView expects a single child that has a definite height (i.e. the child's |
||||||
|
* layout height is not MATCH_PARENT). The width of ItemView will always match |
||||||
|
* the width of its child (that is, the width MeasureSpec given to ItemView is |
||||||
|
* passed directly to the child, and the ItemView measured width is set to the |
||||||
|
* child's measured width). The height of ItemView can be anything; the |
||||||
|
* |
||||||
|
* |
||||||
|
* The purpose of this class is to optimize slide shuffle animations. |
||||||
|
*/ |
||||||
|
public class DragSortItemView extends ViewGroup { |
||||||
|
|
||||||
|
private int mGravity = Gravity.TOP; |
||||||
|
|
||||||
|
public DragSortItemView(Context context) { |
||||||
|
super(context); |
||||||
|
|
||||||
|
// always init with standard ListView layout params
|
||||||
|
setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, |
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT)); |
||||||
|
|
||||||
|
//setClipChildren(true);
|
||||||
|
} |
||||||
|
|
||||||
|
public void setGravity(int gravity) { |
||||||
|
mGravity = gravity; |
||||||
|
} |
||||||
|
|
||||||
|
public int getGravity() { |
||||||
|
return mGravity; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
||||||
|
final View child = getChildAt(0); |
||||||
|
|
||||||
|
if (child == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (mGravity == Gravity.TOP) { |
||||||
|
child.layout(0, 0, getMeasuredWidth(), child.getMeasuredHeight()); |
||||||
|
} else { |
||||||
|
child.layout(0, getMeasuredHeight() - child.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
||||||
|
|
||||||
|
int height = MeasureSpec.getSize(heightMeasureSpec); |
||||||
|
int width = MeasureSpec.getSize(widthMeasureSpec); |
||||||
|
|
||||||
|
int heightMode = MeasureSpec.getMode(heightMeasureSpec); |
||||||
|
|
||||||
|
final View child = getChildAt(0); |
||||||
|
if (child == null) { |
||||||
|
setMeasuredDimension(0, width); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (child.isLayoutRequested()) { |
||||||
|
// Always let child be as tall as it wants.
|
||||||
|
measureChild(child, widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); |
||||||
|
} |
||||||
|
|
||||||
|
if (heightMode == MeasureSpec.UNSPECIFIED) { |
||||||
|
ViewGroup.LayoutParams lp = getLayoutParams(); |
||||||
|
|
||||||
|
if (lp.height > 0) { |
||||||
|
height = lp.height; |
||||||
|
} else { |
||||||
|
height = child.getMeasuredHeight(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
setMeasuredDimension(width, height); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
package com.mobeta.android.dslv; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.view.View; |
||||||
|
import android.widget.Checkable; |
||||||
|
|
||||||
|
/** |
||||||
|
* Lightweight ViewGroup that wraps list items obtained from user's ListAdapter. |
||||||
|
* ItemView expects a single child that has a definite height (i.e. the child's |
||||||
|
* layout height is not MATCH_PARENT). The width of ItemView will always match |
||||||
|
* the width of its child (that is, the width MeasureSpec given to ItemView is |
||||||
|
* passed directly to the child, and the ItemView measured width is set to the |
||||||
|
* child's measured width). The height of ItemView can be anything; the |
||||||
|
* |
||||||
|
* |
||||||
|
* The purpose of this class is to optimize slide shuffle animations. |
||||||
|
*/ |
||||||
|
public class DragSortItemViewCheckable extends DragSortItemView implements Checkable { |
||||||
|
|
||||||
|
public DragSortItemViewCheckable(Context context) { |
||||||
|
super(context); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isChecked() { |
||||||
|
View child = getChildAt(0); |
||||||
|
if (child instanceof Checkable) |
||||||
|
return ((Checkable) child).isChecked(); |
||||||
|
else |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setChecked(boolean checked) { |
||||||
|
View child = getChildAt(0); |
||||||
|
if (child instanceof Checkable) |
||||||
|
((Checkable) child).setChecked(checked); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void toggle() { |
||||||
|
View child = getChildAt(0); |
||||||
|
if (child instanceof Checkable) |
||||||
|
((Checkable) child).toggle(); |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,155 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2011 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package com.mobeta.android.dslv; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.database.Cursor; |
||||||
|
import android.view.LayoutInflater; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
|
||||||
|
// taken from v4 rev. 10 ResourceCursorAdapter.java
|
||||||
|
|
||||||
|
/** |
||||||
|
* Static library support version of the framework's |
||||||
|
* {@link android.widget.ResourceCursorAdapter}. Used to write apps that run on |
||||||
|
* platforms prior to Android 3.0. When running on Android 3.0 or above, this |
||||||
|
* implementation is still used; it does not try to switch to the framework's |
||||||
|
* implementation. See the framework SDK documentation for a class overview. |
||||||
|
*/ |
||||||
|
public abstract class ResourceDragSortCursorAdapter extends DragSortCursorAdapter { |
||||||
|
private int mLayout; |
||||||
|
|
||||||
|
private int mDropDownLayout; |
||||||
|
|
||||||
|
private LayoutInflater mInflater; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor the enables auto-requery. |
||||||
|
* |
||||||
|
* @deprecated This option is discouraged, as it results in Cursor queries |
||||||
|
* being performed on the application's UI thread and thus can |
||||||
|
* cause poor responsiveness or even Application Not Responding |
||||||
|
* errors. As an alternative, use |
||||||
|
* {@link android.app.LoaderManager} with a |
||||||
|
* {@link android.content.CursorLoader}. |
||||||
|
* |
||||||
|
* @param context |
||||||
|
* The context where the ListView associated with this adapter is |
||||||
|
* running |
||||||
|
* @param layout |
||||||
|
* resource identifier of a layout file that defines the views |
||||||
|
* for this list item. Unless you override them later, this will |
||||||
|
* define both the item views and the drop down views. |
||||||
|
*/ |
||||||
|
@Deprecated |
||||||
|
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c) { |
||||||
|
super(context, c); |
||||||
|
mLayout = mDropDownLayout = layout; |
||||||
|
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor with default behavior as per |
||||||
|
* {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is |
||||||
|
* recommended you not use this, but instead |
||||||
|
* {@link #ResourceCursorAdapter(Context, int, Cursor, int)}. When using |
||||||
|
* this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER} will always be |
||||||
|
* set. |
||||||
|
* |
||||||
|
* @param context |
||||||
|
* The context where the ListView associated with this adapter is |
||||||
|
* running |
||||||
|
* @param layout |
||||||
|
* resource identifier of a layout file that defines the views |
||||||
|
* for this list item. Unless you override them later, this will |
||||||
|
* define both the item views and the drop down views. |
||||||
|
* @param c |
||||||
|
* The cursor from which to get the data. |
||||||
|
* @param autoRequery |
||||||
|
* If true the adapter will call requery() on the cursor whenever |
||||||
|
* it changes so the most recent data is always displayed. Using |
||||||
|
* true here is discouraged. |
||||||
|
*/ |
||||||
|
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) { |
||||||
|
super(context, c, autoRequery); |
||||||
|
mLayout = mDropDownLayout = layout; |
||||||
|
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Standard constructor. |
||||||
|
* |
||||||
|
* @param context |
||||||
|
* The context where the ListView associated with this adapter is |
||||||
|
* running |
||||||
|
* @param layout |
||||||
|
* Resource identifier of a layout file that defines the views |
||||||
|
* for this list item. Unless you override them later, this will |
||||||
|
* define both the item views and the drop down views. |
||||||
|
* @param c |
||||||
|
* The cursor from which to get the data. |
||||||
|
* @param flags |
||||||
|
* Flags used to determine the behavior of the adapter, as per |
||||||
|
* {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}. |
||||||
|
*/ |
||||||
|
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, int flags) { |
||||||
|
super(context, c, flags); |
||||||
|
mLayout = mDropDownLayout = layout; |
||||||
|
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Inflates view(s) from the specified XML file. |
||||||
|
* |
||||||
|
* @see android.widget.CursorAdapter#newView(android.content.Context, |
||||||
|
* android.database.Cursor, ViewGroup) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public View newView(Context context, Cursor cursor, ViewGroup parent) { |
||||||
|
return mInflater.inflate(mLayout, parent, false); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) { |
||||||
|
return mInflater.inflate(mDropDownLayout, parent, false); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* <p> |
||||||
|
* Sets the layout resource of the item views. |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @param layout |
||||||
|
* the layout resources used to create item views |
||||||
|
*/ |
||||||
|
public void setViewResource(int layout) { |
||||||
|
mLayout = layout; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* <p> |
||||||
|
* Sets the layout resource of the drop down views. |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @param dropDownLayout |
||||||
|
* the layout resources used to create drop down views |
||||||
|
*/ |
||||||
|
public void setDropDownViewResource(int dropDownLayout) { |
||||||
|
mDropDownLayout = dropDownLayout; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,448 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C) 2006 The Android Open Source Project |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package com.mobeta.android.dslv; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.database.Cursor; |
||||||
|
import android.net.Uri; |
||||||
|
import android.view.View; |
||||||
|
import android.widget.ImageView; |
||||||
|
import android.widget.TextView; |
||||||
|
|
||||||
|
// taken from sdk/sources/android-16/android/widget/SimpleCursorAdapter.java
|
||||||
|
|
||||||
|
/** |
||||||
|
* An easy adapter to map columns from a cursor to TextViews or ImageViews |
||||||
|
* defined in an XML file. You can specify which columns you want, which views |
||||||
|
* you want to display the columns, and the XML file that defines the appearance |
||||||
|
* of these views. |
||||||
|
* |
||||||
|
* Binding occurs in two phases. First, if a |
||||||
|
* {@link android.widget.SimpleCursorAdapter.ViewBinder} is available, |
||||||
|
* {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)} |
||||||
|
* is invoked. If the returned value is true, binding has occured. If the |
||||||
|
* returned value is false and the view to bind is a TextView, |
||||||
|
* {@link #setViewText(TextView, String)} is invoked. If the returned value is |
||||||
|
* false and the view to bind is an ImageView, |
||||||
|
* {@link #setViewImage(ImageView, String)} is invoked. If no appropriate |
||||||
|
* binding can be found, an {@link IllegalStateException} is thrown. |
||||||
|
* |
||||||
|
* If this adapter is used with filtering, for instance in an |
||||||
|
* {@link android.widget.AutoCompleteTextView}, you can use the |
||||||
|
* {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} and the |
||||||
|
* {@link android.widget.FilterQueryProvider} interfaces to get control over the |
||||||
|
* filtering process. You can refer to |
||||||
|
* {@link #convertToString(android.database.Cursor)} and |
||||||
|
* {@link #runQueryOnBackgroundThread(CharSequence)} for more information. |
||||||
|
*/ |
||||||
|
public class SimpleDragSortCursorAdapter extends ResourceDragSortCursorAdapter { |
||||||
|
/** |
||||||
|
* A list of columns containing the data to bind to the UI. This field |
||||||
|
* should be made private, so it is hidden from the SDK. {@hide} |
||||||
|
*/ |
||||||
|
protected int[] mFrom; |
||||||
|
/** |
||||||
|
* A list of View ids representing the views to which the data must be |
||||||
|
* bound. This field should be made private, so it is hidden from the SDK. |
||||||
|
* {@hide} |
||||||
|
*/ |
||||||
|
protected int[] mTo; |
||||||
|
|
||||||
|
private int mStringConversionColumn = -1; |
||||||
|
private CursorToStringConverter mCursorToStringConverter; |
||||||
|
private ViewBinder mViewBinder; |
||||||
|
|
||||||
|
String[] mOriginalFrom; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor the enables auto-requery. |
||||||
|
* |
||||||
|
* @deprecated This option is discouraged, as it results in Cursor queries |
||||||
|
* being performed on the application's UI thread and thus can |
||||||
|
* cause poor responsiveness or even Application Not Responding |
||||||
|
* errors. As an alternative, use |
||||||
|
* {@link android.app.LoaderManager} with a |
||||||
|
* {@link android.content.CursorLoader}. |
||||||
|
*/ |
||||||
|
@Deprecated |
||||||
|
public SimpleDragSortCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) { |
||||||
|
super(context, layout, c); |
||||||
|
mTo = to; |
||||||
|
mOriginalFrom = from; |
||||||
|
findColumns(c, from); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Standard constructor. |
||||||
|
* |
||||||
|
* @param context |
||||||
|
* The context where the ListView associated with this |
||||||
|
* SimpleListItemFactory is running |
||||||
|
* @param layout |
||||||
|
* resource identifier of a layout file that defines the views |
||||||
|
* for this list item. The layout file should include at least |
||||||
|
* those named views defined in "to" |
||||||
|
* @param c |
||||||
|
* The database cursor. Can be null if the cursor is not |
||||||
|
* available yet. |
||||||
|
* @param from |
||||||
|
* A list of column names representing the data to bind to the |
||||||
|
* UI. Can be null if the cursor is not available yet. |
||||||
|
* @param to |
||||||
|
* The views that should display column in the "from" parameter. |
||||||
|
* These should all be TextViews. The first N views in this list |
||||||
|
* are given the values of the first N columns in the from |
||||||
|
* parameter. Can be null if the cursor is not available yet. |
||||||
|
* @param flags |
||||||
|
* Flags used to determine the behavior of the adapter, as per |
||||||
|
* {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}. |
||||||
|
*/ |
||||||
|
public SimpleDragSortCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) { |
||||||
|
super(context, layout, c, flags); |
||||||
|
mTo = to; |
||||||
|
mOriginalFrom = from; |
||||||
|
findColumns(c, from); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Binds all of the field names passed into the "to" parameter of the |
||||||
|
* constructor with their corresponding cursor columns as specified in the |
||||||
|
* "from" parameter. |
||||||
|
* |
||||||
|
* Binding occurs in two phases. First, if a |
||||||
|
* {@link android.widget.SimpleCursorAdapter.ViewBinder} is available, |
||||||
|
* {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)} |
||||||
|
* is invoked. If the returned value is true, binding has occured. If the |
||||||
|
* returned value is false and the view to bind is a TextView, |
||||||
|
* {@link #setViewText(TextView, String)} is invoked. If the returned value |
||||||
|
* is false and the view to bind is an ImageView, |
||||||
|
* {@link #setViewImage(ImageView, String)} is invoked. If no appropriate |
||||||
|
* binding can be found, an {@link IllegalStateException} is thrown. |
||||||
|
* |
||||||
|
* @throws IllegalStateException |
||||||
|
* if binding cannot occur |
||||||
|
* |
||||||
|
* @see android.widget.CursorAdapter#bindView(android.view.View, |
||||||
|
* android.content.Context, android.database.Cursor) |
||||||
|
* @see #getViewBinder() |
||||||
|
* @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder) |
||||||
|
* @see #setViewImage(ImageView, String) |
||||||
|
* @see #setViewText(TextView, String) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void bindView(View view, Context context, Cursor cursor) { |
||||||
|
final ViewBinder binder = mViewBinder; |
||||||
|
final int count = mTo.length; |
||||||
|
final int[] from = mFrom; |
||||||
|
final int[] to = mTo; |
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) { |
||||||
|
final View v = view.findViewById(to[i]); |
||||||
|
if (v != null) { |
||||||
|
boolean bound = false; |
||||||
|
if (binder != null) { |
||||||
|
bound = binder.setViewValue(v, cursor, from[i]); |
||||||
|
} |
||||||
|
|
||||||
|
if (!bound) { |
||||||
|
String text = cursor.getString(from[i]); |
||||||
|
if (text == null) { |
||||||
|
text = ""; |
||||||
|
} |
||||||
|
|
||||||
|
if (v instanceof TextView) { |
||||||
|
setViewText((TextView) v, text); |
||||||
|
} else if (v instanceof ImageView) { |
||||||
|
setViewImage((ImageView) v, text); |
||||||
|
} else { |
||||||
|
throw new IllegalStateException(v.getClass().getName() + " is not a " |
||||||
|
+ " view that can be bounds by this SimpleCursorAdapter"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the {@link ViewBinder} used to bind data to views. |
||||||
|
* |
||||||
|
* @return a ViewBinder or null if the binder does not exist |
||||||
|
* |
||||||
|
* @see #bindView(android.view.View, android.content.Context, |
||||||
|
* android.database.Cursor) |
||||||
|
* @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder) |
||||||
|
*/ |
||||||
|
public ViewBinder getViewBinder() { |
||||||
|
return mViewBinder; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the binder used to bind data to views. |
||||||
|
* |
||||||
|
* @param viewBinder |
||||||
|
* the binder used to bind data to views, can be null to remove |
||||||
|
* the existing binder |
||||||
|
* |
||||||
|
* @see #bindView(android.view.View, android.content.Context, |
||||||
|
* android.database.Cursor) |
||||||
|
* @see #getViewBinder() |
||||||
|
*/ |
||||||
|
public void setViewBinder(ViewBinder viewBinder) { |
||||||
|
mViewBinder = viewBinder; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Called by bindView() to set the image for an ImageView but only if there |
||||||
|
* is no existing ViewBinder or if the existing ViewBinder cannot handle |
||||||
|
* binding to an ImageView. |
||||||
|
* |
||||||
|
* By default, the value will be treated as an image resource. If the value |
||||||
|
* cannot be used as an image resource, the value is used as an image Uri. |
||||||
|
* |
||||||
|
* Intended to be overridden by Adapters that need to filter strings |
||||||
|
* retrieved from the database. |
||||||
|
* |
||||||
|
* @param v |
||||||
|
* ImageView to receive an image |
||||||
|
* @param value |
||||||
|
* the value retrieved from the cursor |
||||||
|
*/ |
||||||
|
public void setViewImage(ImageView v, String value) { |
||||||
|
try { |
||||||
|
v.setImageResource(Integer.parseInt(value)); |
||||||
|
} catch (NumberFormatException nfe) { |
||||||
|
v.setImageURI(Uri.parse(value)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Called by bindView() to set the text for a TextView but only if there is |
||||||
|
* no existing ViewBinder or if the existing ViewBinder cannot handle |
||||||
|
* binding to a TextView. |
||||||
|
* |
||||||
|
* Intended to be overridden by Adapters that need to filter strings |
||||||
|
* retrieved from the database. |
||||||
|
* |
||||||
|
* @param v |
||||||
|
* TextView to receive text |
||||||
|
* @param text |
||||||
|
* the text to be set for the TextView |
||||||
|
*/ |
||||||
|
public void setViewText(TextView v, String text) { |
||||||
|
v.setText(text); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the index of the column used to get a String representation of the |
||||||
|
* Cursor. |
||||||
|
* |
||||||
|
* @return a valid index in the current Cursor or -1 |
||||||
|
* |
||||||
|
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor) |
||||||
|
* @see #setStringConversionColumn(int) |
||||||
|
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter) |
||||||
|
* @see #getCursorToStringConverter() |
||||||
|
*/ |
||||||
|
public int getStringConversionColumn() { |
||||||
|
return mStringConversionColumn; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Defines the index of the column in the Cursor used to get a String |
||||||
|
* representation of that Cursor. The column is used to convert the Cursor |
||||||
|
* to a String only when the current CursorToStringConverter is null. |
||||||
|
* |
||||||
|
* @param stringConversionColumn |
||||||
|
* a valid index in the current Cursor or -1 to use the default |
||||||
|
* conversion mechanism |
||||||
|
* |
||||||
|
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor) |
||||||
|
* @see #getStringConversionColumn() |
||||||
|
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter) |
||||||
|
* @see #getCursorToStringConverter() |
||||||
|
*/ |
||||||
|
public void setStringConversionColumn(int stringConversionColumn) { |
||||||
|
mStringConversionColumn = stringConversionColumn; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the converter used to convert the filtering Cursor into a String. |
||||||
|
* |
||||||
|
* @return null if the converter does not exist or an instance of |
||||||
|
* {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} |
||||||
|
* |
||||||
|
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter) |
||||||
|
* @see #getStringConversionColumn() |
||||||
|
* @see #setStringConversionColumn(int) |
||||||
|
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor) |
||||||
|
*/ |
||||||
|
public CursorToStringConverter getCursorToStringConverter() { |
||||||
|
return mCursorToStringConverter; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the converter used to convert the filtering Cursor into a String. |
||||||
|
* |
||||||
|
* @param cursorToStringConverter |
||||||
|
* the Cursor to String converter, or null to remove the |
||||||
|
* converter |
||||||
|
* |
||||||
|
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter) |
||||||
|
* @see #getStringConversionColumn() |
||||||
|
* @see #setStringConversionColumn(int) |
||||||
|
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor) |
||||||
|
*/ |
||||||
|
public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) { |
||||||
|
mCursorToStringConverter = cursorToStringConverter; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a CharSequence representation of the specified Cursor as defined |
||||||
|
* by the current CursorToStringConverter. If no CursorToStringConverter has |
||||||
|
* been set, the String conversion column is used instead. If the conversion |
||||||
|
* column is -1, the returned String is empty if the cursor is null or |
||||||
|
* Cursor.toString(). |
||||||
|
* |
||||||
|
* @param cursor |
||||||
|
* the Cursor to convert to a CharSequence |
||||||
|
* |
||||||
|
* @return a non-null CharSequence representing the cursor |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public CharSequence convertToString(Cursor cursor) { |
||||||
|
if (mCursorToStringConverter != null) { |
||||||
|
return mCursorToStringConverter.convertToString(cursor); |
||||||
|
} else if (mStringConversionColumn > -1) { |
||||||
|
return cursor.getString(mStringConversionColumn); |
||||||
|
} |
||||||
|
|
||||||
|
return super.convertToString(cursor); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a map from an array of strings to an array of column-id integers |
||||||
|
* in cursor c. If c is null, the array will be discarded. |
||||||
|
* |
||||||
|
* @param c |
||||||
|
* the cursor to find the columns from |
||||||
|
* @param from |
||||||
|
* the Strings naming the columns of interest |
||||||
|
*/ |
||||||
|
private void findColumns(Cursor c, String[] from) { |
||||||
|
if (c != null) { |
||||||
|
int i; |
||||||
|
int count = from.length; |
||||||
|
if (mFrom == null || mFrom.length != count) { |
||||||
|
mFrom = new int[count]; |
||||||
|
} |
||||||
|
for (i = 0; i < count; i++) { |
||||||
|
mFrom[i] = c.getColumnIndexOrThrow(from[i]); |
||||||
|
} |
||||||
|
} else { |
||||||
|
mFrom = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Cursor swapCursor(Cursor c) { |
||||||
|
// super.swapCursor() will notify observers before we have
|
||||||
|
// a valid mapping, make sure we have a mapping before this
|
||||||
|
// happens
|
||||||
|
findColumns(c, mOriginalFrom); |
||||||
|
return super.swapCursor(c); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Change the cursor and change the column-to-view mappings at the same |
||||||
|
* time. |
||||||
|
* |
||||||
|
* @param c |
||||||
|
* The database cursor. Can be null if the cursor is not |
||||||
|
* available yet. |
||||||
|
* @param from |
||||||
|
* A list of column names representing the data to bind to the |
||||||
|
* UI. Can be null if the cursor is not available yet. |
||||||
|
* @param to |
||||||
|
* The views that should display column in the "from" parameter. |
||||||
|
* These should all be TextViews. The first N views in this list |
||||||
|
* are given the values of the first N columns in the from |
||||||
|
* parameter. Can be null if the cursor is not available yet. |
||||||
|
*/ |
||||||
|
public void changeCursorAndColumns(Cursor c, String[] from, int[] to) { |
||||||
|
mOriginalFrom = from; |
||||||
|
mTo = to; |
||||||
|
// super.changeCursor() will notify observers before we have
|
||||||
|
// a valid mapping, make sure we have a mapping before this
|
||||||
|
// happens
|
||||||
|
findColumns(c, mOriginalFrom); |
||||||
|
super.changeCursor(c); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This class can be used by external clients of SimpleCursorAdapter to bind |
||||||
|
* values fom the Cursor to views. |
||||||
|
* |
||||||
|
* You should use this class to bind values from the Cursor to views that |
||||||
|
* are not directly supported by SimpleCursorAdapter or to change the way |
||||||
|
* binding occurs for views supported by SimpleCursorAdapter. |
||||||
|
* |
||||||
|
* @see SimpleCursorAdapter#bindView(android.view.View, |
||||||
|
* android.content.Context, android.database.Cursor) |
||||||
|
* @see SimpleCursorAdapter#setViewImage(ImageView, String) |
||||||
|
* @see SimpleCursorAdapter#setViewText(TextView, String) |
||||||
|
*/ |
||||||
|
public static interface ViewBinder { |
||||||
|
/** |
||||||
|
* Binds the Cursor column defined by the specified index to the |
||||||
|
* specified view. |
||||||
|
* |
||||||
|
* When binding is handled by this ViewBinder, this method must return |
||||||
|
* true. If this method returns false, SimpleCursorAdapter will attempts |
||||||
|
* to handle the binding on its own. |
||||||
|
* |
||||||
|
* @param view |
||||||
|
* the view to bind the data to |
||||||
|
* @param cursor |
||||||
|
* the cursor to get the data from |
||||||
|
* @param columnIndex |
||||||
|
* the column at which the data can be found in the cursor |
||||||
|
* |
||||||
|
* @return true if the data was bound to the view, false otherwise |
||||||
|
*/ |
||||||
|
boolean setViewValue(View view, Cursor cursor, int columnIndex); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This class can be used by external clients of SimpleCursorAdapter to |
||||||
|
* define how the Cursor should be converted to a String. |
||||||
|
* |
||||||
|
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor) |
||||||
|
*/ |
||||||
|
public static interface CursorToStringConverter { |
||||||
|
/** |
||||||
|
* Returns a CharSequence representing the specified Cursor. |
||||||
|
* |
||||||
|
* @param cursor |
||||||
|
* the cursor for which a CharSequence representation is |
||||||
|
* requested |
||||||
|
* |
||||||
|
* @return a non-null CharSequence representing the cursor |
||||||
|
*/ |
||||||
|
CharSequence convertToString(Cursor cursor); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
package com.mobeta.android.dslv; |
||||||
|
|
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.graphics.Color; |
||||||
|
import android.graphics.Point; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.ImageView; |
||||||
|
import android.widget.ListView; |
||||||
|
|
||||||
|
/** |
||||||
|
* Simple implementation of the FloatViewManager class. Uses list items as they |
||||||
|
* appear in the ListView to create the floating View. |
||||||
|
*/ |
||||||
|
public class SimpleFloatViewManager implements DragSortListView.FloatViewManager { |
||||||
|
|
||||||
|
private Bitmap mFloatBitmap; |
||||||
|
|
||||||
|
private ImageView mImageView; |
||||||
|
|
||||||
|
private int mFloatBGColor = Color.BLACK; |
||||||
|
|
||||||
|
private ListView mListView; |
||||||
|
|
||||||
|
public SimpleFloatViewManager(ListView lv) { |
||||||
|
mListView = lv; |
||||||
|
} |
||||||
|
|
||||||
|
public void setBackgroundColor(int color) { |
||||||
|
mFloatBGColor = color; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This simple implementation creates a Bitmap copy of the list item |
||||||
|
* currently shown at ListView <code>position</code>. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public View onCreateFloatView(int position) { |
||||||
|
// Guaranteed that this will not be null? I think so. Nope, got
|
||||||
|
// a NullPointerException once...
|
||||||
|
View v = mListView.getChildAt(position + mListView.getHeaderViewsCount() - mListView.getFirstVisiblePosition()); |
||||||
|
|
||||||
|
if (v == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
v.setPressed(false); |
||||||
|
|
||||||
|
// Create a copy of the drawing cache so that it does not get
|
||||||
|
// recycled by the framework when the list tries to clean up memory
|
||||||
|
//v.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
|
||||||
|
v.setDrawingCacheEnabled(true); |
||||||
|
mFloatBitmap = Bitmap.createBitmap(v.getDrawingCache()); |
||||||
|
v.setDrawingCacheEnabled(false); |
||||||
|
|
||||||
|
if (mImageView == null) { |
||||||
|
mImageView = new ImageView(mListView.getContext()); |
||||||
|
} |
||||||
|
mImageView.setBackgroundColor(mFloatBGColor); |
||||||
|
mImageView.setPadding(0, 0, 0, 0); |
||||||
|
mImageView.setImageBitmap(mFloatBitmap); |
||||||
|
mImageView.setLayoutParams(new ViewGroup.LayoutParams(v.getWidth(), v.getHeight())); |
||||||
|
|
||||||
|
return mImageView; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This does nothing |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onDragFloatView(View floatView, Point position, Point touch) { |
||||||
|
// do nothing
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes the Bitmap from the ImageView created in onCreateFloatView() and |
||||||
|
* tells the system to recycle it. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onDestroyFloatView(View floatView) { |
||||||
|
((ImageView) floatView).setImageDrawable(null); |
||||||
|
|
||||||
|
mFloatBitmap.recycle(); |
||||||
|
mFloatBitmap = null; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,84 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright (C) 2013 The Android Open Source Project |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
package org.floens.chan.ui.adapter; |
|
||||||
|
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import org.floens.chan.R; |
|
||||||
import org.floens.chan.core.model.Board; |
|
||||||
import org.floens.chan.ui.activity.BoardEditor; |
|
||||||
|
|
||||||
import android.content.Context; |
|
||||||
import android.view.LayoutInflater; |
|
||||||
import android.view.View; |
|
||||||
import android.view.ViewGroup; |
|
||||||
import android.widget.ArrayAdapter; |
|
||||||
import android.widget.Button; |
|
||||||
import android.widget.TextView; |
|
||||||
|
|
||||||
public class BoardEditAdapter extends ArrayAdapter<Board> { |
|
||||||
HashMap<Board, Integer> mIdMap = new HashMap<Board, Integer>(); |
|
||||||
|
|
||||||
private final BoardEditor editor; |
|
||||||
|
|
||||||
public BoardEditAdapter(Context context, int textViewResourceId, List<Board> objects, BoardEditor editor) { |
|
||||||
super(context, textViewResourceId, objects); |
|
||||||
this.editor = editor; |
|
||||||
|
|
||||||
for (int i = 0; i < objects.size(); ++i) { |
|
||||||
mIdMap.put(objects.get(i), i); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public long getItemId(int position) { |
|
||||||
if (position < 0 || position >= mIdMap.size()) { |
|
||||||
return -1; |
|
||||||
} |
|
||||||
|
|
||||||
Board item = getItem(position); |
|
||||||
return mIdMap.get(item); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public View getView(final int position, View convertView, ViewGroup parent) { |
|
||||||
String text = getItem(position).key; |
|
||||||
|
|
||||||
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
|
||||||
|
|
||||||
View view = inflater.inflate(R.layout.board_view, null); |
|
||||||
|
|
||||||
TextView textView = (TextView) view.findViewById(R.id.board_view_text); |
|
||||||
textView.setText(text); |
|
||||||
|
|
||||||
Button button = (Button) view.findViewById(R.id.board_view_delete); |
|
||||||
button.setOnClickListener(new View.OnClickListener() { |
|
||||||
@Override |
|
||||||
public void onClick(View v) { |
|
||||||
editor.onDeleteClicked(getItem(position)); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
return view; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean hasStableIds() { |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,117 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2013 Google Inc. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.floens.chan.ui.view; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.content.res.TypedArray; |
||||||
|
import android.graphics.Canvas; |
||||||
|
import android.graphics.Paint; |
||||||
|
import android.graphics.RectF; |
||||||
|
import android.util.AttributeSet; |
||||||
|
import android.util.DisplayMetrics; |
||||||
|
import android.util.TypedValue; |
||||||
|
import android.view.Gravity; |
||||||
|
import android.view.View; |
||||||
|
|
||||||
|
public class DragGripView extends View { |
||||||
|
private static final int[] ATTRS = new int[]{ |
||||||
|
android.R.attr.gravity, |
||||||
|
android.R.attr.color, |
||||||
|
}; |
||||||
|
|
||||||
|
private static final int HORIZ_RIDGES = 2; |
||||||
|
|
||||||
|
private int mGravity = Gravity.START; |
||||||
|
private int mColor = 0xff666666; |
||||||
|
|
||||||
|
private final Paint mRidgePaint; |
||||||
|
private final RectF mTempRectF = new RectF(); |
||||||
|
|
||||||
|
private final float mRidgeSize; |
||||||
|
private final float mRidgeGap; |
||||||
|
|
||||||
|
// private int mWidth;
|
||||||
|
private int mHeight; |
||||||
|
|
||||||
|
public DragGripView(Context context) { |
||||||
|
this(context, null, 0); |
||||||
|
} |
||||||
|
|
||||||
|
public DragGripView(Context context, AttributeSet attrs) { |
||||||
|
this(context, attrs, 0); |
||||||
|
} |
||||||
|
|
||||||
|
public DragGripView(Context context, AttributeSet attrs, int defStyle) { |
||||||
|
super(context, attrs, defStyle); |
||||||
|
|
||||||
|
final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS); |
||||||
|
mGravity = a.getInteger(0, mGravity); |
||||||
|
mColor = a.getColor(1, mColor); |
||||||
|
a.recycle(); |
||||||
|
|
||||||
|
final DisplayMetrics res = getResources().getDisplayMetrics(); |
||||||
|
mRidgeSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, res); |
||||||
|
mRidgeGap = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, res); |
||||||
|
|
||||||
|
mRidgePaint = new Paint(); |
||||||
|
mRidgePaint.setColor(mColor); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
||||||
|
setMeasuredDimension( |
||||||
|
View.resolveSize( |
||||||
|
(int) (HORIZ_RIDGES * (mRidgeSize + mRidgeGap) - mRidgeGap) |
||||||
|
+ getPaddingLeft() + getPaddingRight(), |
||||||
|
widthMeasureSpec), |
||||||
|
View.resolveSize( |
||||||
|
(int) mRidgeSize, |
||||||
|
heightMeasureSpec)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onDraw(Canvas canvas) { |
||||||
|
super.onDraw(canvas); |
||||||
|
|
||||||
|
// float drawWidth = HORIZ_RIDGES * (mRidgeSize + mRidgeGap) - mRidgeGap;
|
||||||
|
float drawLeft = getPaddingLeft(); |
||||||
|
|
||||||
|
int vertRidges = (int) ((mHeight - getPaddingTop() - getPaddingBottom() + mRidgeGap) |
||||||
|
/ (mRidgeSize + mRidgeGap)); |
||||||
|
float drawHeight = vertRidges * (mRidgeSize + mRidgeGap) - mRidgeGap; |
||||||
|
float drawTop = getPaddingTop() |
||||||
|
+ ((mHeight - getPaddingTop() - getPaddingBottom()) - drawHeight) / 2; |
||||||
|
|
||||||
|
for (int y = 0; y < vertRidges; y++) { |
||||||
|
for (int x = 0; x < HORIZ_RIDGES; x++) { |
||||||
|
mTempRectF.set( |
||||||
|
drawLeft + x * (mRidgeSize + mRidgeGap), |
||||||
|
drawTop + y * (mRidgeSize + mRidgeGap), |
||||||
|
drawLeft + x * (mRidgeSize + mRidgeGap) + mRidgeSize, |
||||||
|
drawTop + y * (mRidgeSize + mRidgeGap) + mRidgeSize); |
||||||
|
canvas.drawOval(mTempRectF, mRidgePaint); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
||||||
|
super.onSizeChanged(w, h, oldw, oldh); |
||||||
|
mHeight = h; |
||||||
|
// mWidth = w;
|
||||||
|
} |
||||||
|
} |
@ -1,596 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright (C) 2013 The Android Open Source Project |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
package org.floens.chan.ui.view; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import org.floens.chan.ui.adapter.BoardEditAdapter; |
|
||||||
|
|
||||||
import android.animation.Animator; |
|
||||||
import android.animation.AnimatorListenerAdapter; |
|
||||||
import android.animation.ObjectAnimator; |
|
||||||
import android.animation.TypeEvaluator; |
|
||||||
import android.animation.ValueAnimator; |
|
||||||
import android.content.Context; |
|
||||||
import android.graphics.Bitmap; |
|
||||||
import android.graphics.Canvas; |
|
||||||
import android.graphics.Color; |
|
||||||
import android.graphics.Paint; |
|
||||||
import android.graphics.Rect; |
|
||||||
import android.graphics.drawable.BitmapDrawable; |
|
||||||
import android.util.AttributeSet; |
|
||||||
import android.util.DisplayMetrics; |
|
||||||
import android.view.MotionEvent; |
|
||||||
import android.view.View; |
|
||||||
import android.view.ViewTreeObserver; |
|
||||||
import android.widget.AbsListView; |
|
||||||
import android.widget.AdapterView; |
|
||||||
import android.widget.BaseAdapter; |
|
||||||
import android.widget.ListView; |
|
||||||
|
|
||||||
/** |
|
||||||
* The dynamic listview is an extension of listview that supports cell dragging |
|
||||||
* and swapping. |
|
||||||
* |
|
||||||
* This layout is in charge of positioning the hover cell in the correct |
|
||||||
* location on the screen in response to user touch events. It uses the position |
|
||||||
* of the hover cell to determine when two cells should be swapped. If two cells |
|
||||||
* should be swapped, all the corresponding data set and layout changes are |
|
||||||
* handled here. |
|
||||||
* |
|
||||||
* If no cell is selected, all the touch events are passed down to the listview |
|
||||||
* and behave normally. If one of the items in the listview experiences a long |
|
||||||
* press event, the contents of its current visible state are captured as a |
|
||||||
* bitmap and its visibility is set to INVISIBLE. A hover cell is then created |
|
||||||
* and added to this layout as an overlaying BitmapDrawable above the listview. |
|
||||||
* Once the hover cell is translated some distance to signify an item swap, a |
|
||||||
* data set change accompanied by animation takes place. When the user releases |
|
||||||
* the hover cell, it animates into its corresponding position in the listview. |
|
||||||
* |
|
||||||
* When the hover cell is either above or below the bounds of the listview, this |
|
||||||
* listview also scrolls on its own so as to reveal additional content. |
|
||||||
*/ |
|
||||||
public class DynamicListView<T> extends ListView { |
|
||||||
|
|
||||||
private final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 15; |
|
||||||
private final int MOVE_DURATION = 150; |
|
||||||
private final int LINE_THICKNESS = 10; |
|
||||||
|
|
||||||
public List<T> mCheeseList; |
|
||||||
|
|
||||||
private int mLastEventY = -1; |
|
||||||
|
|
||||||
private int mDownY = -1; |
|
||||||
private int mDownX = -1; |
|
||||||
|
|
||||||
private int mTotalOffset = 0; |
|
||||||
|
|
||||||
private boolean mCellIsMobile = false; |
|
||||||
private boolean mIsMobileScrolling = false; |
|
||||||
private int mSmoothScrollAmountAtEdge = 0; |
|
||||||
|
|
||||||
private final int INVALID_ID = -1; |
|
||||||
private long mAboveItemId = INVALID_ID; |
|
||||||
private long mMobileItemId = INVALID_ID; |
|
||||||
private long mBelowItemId = INVALID_ID; |
|
||||||
|
|
||||||
private BitmapDrawable mHoverCell; |
|
||||||
private Rect mHoverCellCurrentBounds; |
|
||||||
private Rect mHoverCellOriginalBounds; |
|
||||||
|
|
||||||
private final int INVALID_POINTER_ID = -1; |
|
||||||
private int mActivePointerId = INVALID_POINTER_ID; |
|
||||||
|
|
||||||
private boolean mIsWaitingForScrollFinish = false; |
|
||||||
private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE; |
|
||||||
|
|
||||||
public DynamicListView(Context context) { |
|
||||||
super(context); |
|
||||||
init(context); |
|
||||||
} |
|
||||||
|
|
||||||
public DynamicListView(Context context, AttributeSet attrs, int defStyle) { |
|
||||||
super(context, attrs, defStyle); |
|
||||||
init(context); |
|
||||||
} |
|
||||||
|
|
||||||
public DynamicListView(Context context, AttributeSet attrs) { |
|
||||||
super(context, attrs); |
|
||||||
init(context); |
|
||||||
} |
|
||||||
|
|
||||||
public void init(Context context) { |
|
||||||
setOnItemLongClickListener(mOnItemLongClickListener); |
|
||||||
setOnScrollListener(mScrollListener); |
|
||||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics(); |
|
||||||
mSmoothScrollAmountAtEdge = (int) (SMOOTH_SCROLL_AMOUNT_AT_EDGE / metrics.density); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Listens for long clicks on any items in the listview. When a cell has |
|
||||||
* been selected, the hover cell is created and set up. |
|
||||||
*/ |
|
||||||
private final AdapterView.OnItemLongClickListener mOnItemLongClickListener = new AdapterView.OnItemLongClickListener() { |
|
||||||
@Override |
|
||||||
public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int pos, long id) { |
|
||||||
mTotalOffset = 0; |
|
||||||
|
|
||||||
int position = pointToPosition(mDownX, mDownY); |
|
||||||
int itemNum = position - getFirstVisiblePosition(); |
|
||||||
|
|
||||||
View selectedView = getChildAt(itemNum); |
|
||||||
mMobileItemId = getAdapter().getItemId(position); |
|
||||||
mHoverCell = getAndAddHoverView(selectedView); |
|
||||||
selectedView.setVisibility(INVISIBLE); |
|
||||||
|
|
||||||
mCellIsMobile = true; |
|
||||||
|
|
||||||
updateNeighborViewsForID(mMobileItemId); |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates the hover cell with the appropriate bitmap and of appropriate |
|
||||||
* size. The hover cell's BitmapDrawable is drawn on top of the bitmap every |
|
||||||
* single time an invalidate call is made. |
|
||||||
*/ |
|
||||||
private BitmapDrawable getAndAddHoverView(View v) { |
|
||||||
|
|
||||||
int w = v.getWidth(); |
|
||||||
int h = v.getHeight(); |
|
||||||
int top = v.getTop(); |
|
||||||
int left = v.getLeft(); |
|
||||||
|
|
||||||
Bitmap b = getBitmapWithBorder(v); |
|
||||||
|
|
||||||
BitmapDrawable drawable = new BitmapDrawable(getResources(), b); |
|
||||||
|
|
||||||
mHoverCellOriginalBounds = new Rect(left, top, left + w, top + h); |
|
||||||
mHoverCellCurrentBounds = new Rect(mHoverCellOriginalBounds); |
|
||||||
|
|
||||||
drawable.setBounds(mHoverCellCurrentBounds); |
|
||||||
|
|
||||||
return drawable; |
|
||||||
} |
|
||||||
|
|
||||||
/** Draws a black border over the screenshot of the view passed in. */ |
|
||||||
private Bitmap getBitmapWithBorder(View v) { |
|
||||||
Bitmap bitmap = getBitmapFromView(v); |
|
||||||
Canvas can = new Canvas(bitmap); |
|
||||||
|
|
||||||
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); |
|
||||||
|
|
||||||
Paint paint = new Paint(); |
|
||||||
paint.setStyle(Paint.Style.STROKE); |
|
||||||
paint.setStrokeWidth(LINE_THICKNESS); |
|
||||||
paint.setColor(Color.BLACK); |
|
||||||
|
|
||||||
can.drawBitmap(bitmap, 0, 0, null); |
|
||||||
can.drawRect(rect, paint); |
|
||||||
|
|
||||||
return bitmap; |
|
||||||
} |
|
||||||
|
|
||||||
/** Returns a bitmap showing a screenshot of the view passed in. */ |
|
||||||
private Bitmap getBitmapFromView(View v) { |
|
||||||
Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888); |
|
||||||
Canvas canvas = new Canvas(bitmap); |
|
||||||
v.draw(canvas); |
|
||||||
return bitmap; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Stores a reference to the views above and below the item currently |
|
||||||
* corresponding to the hover cell. It is important to note that if this |
|
||||||
* item is either at the top or bottom of the list, mAboveItemId or |
|
||||||
* mBelowItemId may be invalid. |
|
||||||
*/ |
|
||||||
private void updateNeighborViewsForID(long itemID) { |
|
||||||
int position = getPositionForID(itemID); |
|
||||||
BoardEditAdapter adapter = ((BoardEditAdapter) getAdapter()); |
|
||||||
mAboveItemId = adapter.getItemId(position - 1); |
|
||||||
mBelowItemId = adapter.getItemId(position + 1); |
|
||||||
} |
|
||||||
|
|
||||||
/** Retrieves the view in the list corresponding to itemID */ |
|
||||||
public View getViewForID(long itemID) { |
|
||||||
int firstVisiblePosition = getFirstVisiblePosition(); |
|
||||||
BoardEditAdapter adapter = ((BoardEditAdapter) getAdapter()); |
|
||||||
for (int i = 0; i < getChildCount(); i++) { |
|
||||||
View v = getChildAt(i); |
|
||||||
int position = firstVisiblePosition + i; |
|
||||||
long id = adapter.getItemId(position); |
|
||||||
if (id == itemID) { |
|
||||||
return v; |
|
||||||
} |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
/** Retrieves the position in the list corresponding to itemID */ |
|
||||||
public int getPositionForID(long itemID) { |
|
||||||
View v = getViewForID(itemID); |
|
||||||
if (v == null) { |
|
||||||
return -1; |
|
||||||
} else { |
|
||||||
return getPositionForView(v); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* dispatchDraw gets invoked when all the child views are about to be drawn. |
|
||||||
* By overriding this method, the hover cell (BitmapDrawable) can be drawn |
|
||||||
* over the listview's items whenever the listview is redrawn. |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
protected void dispatchDraw(Canvas canvas) { |
|
||||||
super.dispatchDraw(canvas); |
|
||||||
if (mHoverCell != null) { |
|
||||||
mHoverCell.draw(canvas); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean onTouchEvent(MotionEvent event) { |
|
||||||
|
|
||||||
switch (event.getAction() & MotionEvent.ACTION_MASK) { |
|
||||||
case MotionEvent.ACTION_DOWN: |
|
||||||
mDownX = (int) event.getX(); |
|
||||||
mDownY = (int) event.getY(); |
|
||||||
mActivePointerId = event.getPointerId(0); |
|
||||||
break; |
|
||||||
case MotionEvent.ACTION_MOVE: |
|
||||||
if (mActivePointerId == INVALID_POINTER_ID) { |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
int pointerIndex = event.findPointerIndex(mActivePointerId); |
|
||||||
|
|
||||||
mLastEventY = (int) event.getY(pointerIndex); |
|
||||||
int deltaY = mLastEventY - mDownY; |
|
||||||
|
|
||||||
if (mCellIsMobile) { |
|
||||||
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left, mHoverCellOriginalBounds.top + deltaY |
|
||||||
+ mTotalOffset); |
|
||||||
mHoverCell.setBounds(mHoverCellCurrentBounds); |
|
||||||
invalidate(); |
|
||||||
|
|
||||||
handleCellSwitch(); |
|
||||||
|
|
||||||
mIsMobileScrolling = false; |
|
||||||
handleMobileCellScroll(); |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
|
||||||
break; |
|
||||||
case MotionEvent.ACTION_UP: |
|
||||||
touchEventsEnded(); |
|
||||||
break; |
|
||||||
case MotionEvent.ACTION_CANCEL: |
|
||||||
touchEventsCancelled(); |
|
||||||
break; |
|
||||||
case MotionEvent.ACTION_POINTER_UP: |
|
||||||
/* If a multitouch event took place and the original touch dictating |
|
||||||
* the movement of the hover cell has ended, then the dragging event |
|
||||||
* ends and the hover cell is animated to its corresponding position |
|
||||||
* in the listview. */ |
|
||||||
pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; |
|
||||||
final int pointerId = event.getPointerId(pointerIndex); |
|
||||||
if (pointerId == mActivePointerId) { |
|
||||||
touchEventsEnded(); |
|
||||||
} |
|
||||||
break; |
|
||||||
default: |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
return super.onTouchEvent(event); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method determines whether the hover cell has been shifted far enough |
|
||||||
* to invoke a cell swap. If so, then the respective cell swap candidate is |
|
||||||
* determined and the data set is changed. Upon posting a notification of |
|
||||||
* the data set change, a layout is invoked to place the cells in the right |
|
||||||
* place. Using a ViewTreeObserver and a corresponding OnPreDrawListener, we |
|
||||||
* can offset the cell being swapped to where it previously was and then |
|
||||||
* animate it to its new position. |
|
||||||
*/ |
|
||||||
private void handleCellSwitch() { |
|
||||||
final int deltaY = mLastEventY - mDownY; |
|
||||||
int deltaYTotal = mHoverCellOriginalBounds.top + mTotalOffset + deltaY; |
|
||||||
|
|
||||||
View belowView = getViewForID(mBelowItemId); |
|
||||||
View mobileView = getViewForID(mMobileItemId); |
|
||||||
View aboveView = getViewForID(mAboveItemId); |
|
||||||
|
|
||||||
boolean isBelow = (belowView != null) && (deltaYTotal > belowView.getTop()); |
|
||||||
boolean isAbove = (aboveView != null) && (deltaYTotal < aboveView.getTop()); |
|
||||||
|
|
||||||
if (isBelow || isAbove) { |
|
||||||
|
|
||||||
final long switchItemID = isBelow ? mBelowItemId : mAboveItemId; |
|
||||||
View switchView = isBelow ? belowView : aboveView; |
|
||||||
final int originalItem = getPositionForView(mobileView); |
|
||||||
|
|
||||||
if (switchView == null) { |
|
||||||
updateNeighborViewsForID(mMobileItemId); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
swapElements(mCheeseList, originalItem, getPositionForView(switchView)); |
|
||||||
|
|
||||||
((BaseAdapter) getAdapter()).notifyDataSetChanged(); |
|
||||||
|
|
||||||
mDownY = mLastEventY; |
|
||||||
|
|
||||||
final int switchViewStartTop = switchView.getTop(); |
|
||||||
|
|
||||||
mobileView.setVisibility(View.VISIBLE); |
|
||||||
switchView.setVisibility(View.INVISIBLE); |
|
||||||
|
|
||||||
updateNeighborViewsForID(mMobileItemId); |
|
||||||
|
|
||||||
final ViewTreeObserver observer = getViewTreeObserver(); |
|
||||||
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { |
|
||||||
@Override |
|
||||||
public boolean onPreDraw() { |
|
||||||
observer.removeOnPreDrawListener(this); |
|
||||||
|
|
||||||
View switchView = getViewForID(switchItemID); |
|
||||||
|
|
||||||
mTotalOffset += deltaY; |
|
||||||
|
|
||||||
int switchViewNewTop = switchView.getTop(); |
|
||||||
int delta = switchViewStartTop - switchViewNewTop; |
|
||||||
|
|
||||||
switchView.setTranslationY(delta); |
|
||||||
|
|
||||||
ObjectAnimator animator = ObjectAnimator.ofFloat(switchView, View.TRANSLATION_Y, 0); |
|
||||||
animator.setDuration(MOVE_DURATION); |
|
||||||
animator.start(); |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void swapElements(List<T> arrayList, int indexOne, int indexTwo) { |
|
||||||
T temp = arrayList.get(indexOne); |
|
||||||
arrayList.set(indexOne, arrayList.get(indexTwo)); |
|
||||||
arrayList.set(indexTwo, temp); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Resets all the appropriate fields to a default state while also animating |
|
||||||
* the hover cell back to its correct location. |
|
||||||
*/ |
|
||||||
private void touchEventsEnded() { |
|
||||||
final View mobileView = getViewForID(mMobileItemId); |
|
||||||
if (mCellIsMobile || mIsWaitingForScrollFinish) { |
|
||||||
mCellIsMobile = false; |
|
||||||
mIsWaitingForScrollFinish = false; |
|
||||||
mIsMobileScrolling = false; |
|
||||||
mActivePointerId = INVALID_POINTER_ID; |
|
||||||
|
|
||||||
// If the autoscroller has not completed scrolling, we need to wait for it to
|
|
||||||
// finish in order to determine the final location of where the hover cell
|
|
||||||
// should be animated to.
|
|
||||||
if (mScrollState != OnScrollListener.SCROLL_STATE_IDLE) { |
|
||||||
mIsWaitingForScrollFinish = true; |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left, mobileView.getTop()); |
|
||||||
|
|
||||||
ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(mHoverCell, "bounds", sBoundEvaluator, |
|
||||||
mHoverCellCurrentBounds); |
|
||||||
hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
|
||||||
@Override |
|
||||||
public void onAnimationUpdate(ValueAnimator valueAnimator) { |
|
||||||
invalidate(); |
|
||||||
} |
|
||||||
}); |
|
||||||
hoverViewAnimator.addListener(new AnimatorListenerAdapter() { |
|
||||||
@Override |
|
||||||
public void onAnimationStart(Animator animation) { |
|
||||||
setEnabled(false); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onAnimationEnd(Animator animation) { |
|
||||||
mAboveItemId = INVALID_ID; |
|
||||||
mMobileItemId = INVALID_ID; |
|
||||||
mBelowItemId = INVALID_ID; |
|
||||||
mobileView.setVisibility(VISIBLE); |
|
||||||
mHoverCell = null; |
|
||||||
setEnabled(true); |
|
||||||
invalidate(); |
|
||||||
} |
|
||||||
}); |
|
||||||
hoverViewAnimator.start(); |
|
||||||
} else { |
|
||||||
touchEventsCancelled(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Resets all the appropriate fields to a default state. |
|
||||||
*/ |
|
||||||
private void touchEventsCancelled() { |
|
||||||
View mobileView = getViewForID(mMobileItemId); |
|
||||||
if (mCellIsMobile) { |
|
||||||
mAboveItemId = INVALID_ID; |
|
||||||
mMobileItemId = INVALID_ID; |
|
||||||
mBelowItemId = INVALID_ID; |
|
||||||
mobileView.setVisibility(VISIBLE); |
|
||||||
mHoverCell = null; |
|
||||||
invalidate(); |
|
||||||
} |
|
||||||
mCellIsMobile = false; |
|
||||||
mIsMobileScrolling = false; |
|
||||||
mActivePointerId = INVALID_POINTER_ID; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This TypeEvaluator is used to animate the BitmapDrawable back to its |
|
||||||
* final location when the user lifts his finger by modifying the |
|
||||||
* BitmapDrawable's bounds. |
|
||||||
*/ |
|
||||||
private final static TypeEvaluator<Rect> sBoundEvaluator = new TypeEvaluator<Rect>() { |
|
||||||
@Override |
|
||||||
public Rect evaluate(float fraction, Rect startValue, Rect endValue) { |
|
||||||
return new Rect(interpolate(startValue.left, endValue.left, fraction), interpolate(startValue.top, |
|
||||||
endValue.top, fraction), interpolate(startValue.right, endValue.right, fraction), interpolate( |
|
||||||
startValue.bottom, endValue.bottom, fraction)); |
|
||||||
} |
|
||||||
|
|
||||||
public int interpolate(int start, int end, float fraction) { |
|
||||||
return (int) (start + fraction * (end - start)); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Determines whether this listview is in a scrolling state invoked by the |
|
||||||
* fact that the hover cell is out of the bounds of the listview; |
|
||||||
*/ |
|
||||||
private void handleMobileCellScroll() { |
|
||||||
mIsMobileScrolling = handleMobileCellScroll(mHoverCellCurrentBounds); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method is in charge of determining if the hover cell is above or |
|
||||||
* below the bounds of the listview. If so, the listview does an appropriate |
|
||||||
* upward or downward smooth scroll so as to reveal new items. |
|
||||||
*/ |
|
||||||
public boolean handleMobileCellScroll(Rect r) { |
|
||||||
int offset = computeVerticalScrollOffset(); |
|
||||||
int height = getHeight(); |
|
||||||
int extent = computeVerticalScrollExtent(); |
|
||||||
int range = computeVerticalScrollRange(); |
|
||||||
int hoverViewTop = r.top; |
|
||||||
int hoverHeight = r.height(); |
|
||||||
|
|
||||||
if (hoverViewTop <= 0 && offset > 0) { |
|
||||||
smoothScrollBy(-mSmoothScrollAmountAtEdge, 0); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
if (hoverViewTop + hoverHeight >= height && (offset + extent) < range) { |
|
||||||
smoothScrollBy(mSmoothScrollAmountAtEdge, 0); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
public void setArrayList(List<T> cheeseList) { |
|
||||||
mCheeseList = cheeseList; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This scroll listener is added to the listview in order to handle cell |
|
||||||
* swapping when the cell is either at the top or bottom edge of the |
|
||||||
* listview. If the hover cell is at either edge of the listview, the |
|
||||||
* listview will begin scrolling. As scrolling takes place, the listview |
|
||||||
* continuously checks if new cells became visible and determines whether |
|
||||||
* they are potential candidates for a cell swap. |
|
||||||
*/ |
|
||||||
private final AbsListView.OnScrollListener mScrollListener = new AbsListView.OnScrollListener() { |
|
||||||
|
|
||||||
private int mPreviousFirstVisibleItem = -1; |
|
||||||
private int mPreviousVisibleItemCount = -1; |
|
||||||
private int mCurrentFirstVisibleItem; |
|
||||||
private int mCurrentVisibleItemCount; |
|
||||||
private int mCurrentScrollState; |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { |
|
||||||
mCurrentFirstVisibleItem = firstVisibleItem; |
|
||||||
mCurrentVisibleItemCount = visibleItemCount; |
|
||||||
|
|
||||||
mPreviousFirstVisibleItem = (mPreviousFirstVisibleItem == -1) ? mCurrentFirstVisibleItem |
|
||||||
: mPreviousFirstVisibleItem; |
|
||||||
mPreviousVisibleItemCount = (mPreviousVisibleItemCount == -1) ? mCurrentVisibleItemCount |
|
||||||
: mPreviousVisibleItemCount; |
|
||||||
|
|
||||||
checkAndHandleFirstVisibleCellChange(); |
|
||||||
checkAndHandleLastVisibleCellChange(); |
|
||||||
|
|
||||||
mPreviousFirstVisibleItem = mCurrentFirstVisibleItem; |
|
||||||
mPreviousVisibleItemCount = mCurrentVisibleItemCount; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onScrollStateChanged(AbsListView view, int scrollState) { |
|
||||||
mCurrentScrollState = scrollState; |
|
||||||
mScrollState = scrollState; |
|
||||||
isScrollCompleted(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method is in charge of invoking 1 of 2 actions. Firstly, if the |
|
||||||
* listview is in a state of scrolling invoked by the hover cell being |
|
||||||
* outside the bounds of the listview, then this scrolling event is |
|
||||||
* continued. Secondly, if the hover cell has already been released, |
|
||||||
* this invokes the animation for the hover cell to return to its |
|
||||||
* correct position after the listview has entered an idle scroll state. |
|
||||||
*/ |
|
||||||
private void isScrollCompleted() { |
|
||||||
if (mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE) { |
|
||||||
if (mCellIsMobile && mIsMobileScrolling) { |
|
||||||
handleMobileCellScroll(); |
|
||||||
} else if (mIsWaitingForScrollFinish) { |
|
||||||
touchEventsEnded(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Determines if the listview scrolled up enough to reveal a new cell at |
|
||||||
* the top of the list. If so, then the appropriate parameters are |
|
||||||
* updated. |
|
||||||
*/ |
|
||||||
public void checkAndHandleFirstVisibleCellChange() { |
|
||||||
if (mCurrentFirstVisibleItem != mPreviousFirstVisibleItem) { |
|
||||||
if (mCellIsMobile && mMobileItemId != INVALID_ID) { |
|
||||||
updateNeighborViewsForID(mMobileItemId); |
|
||||||
handleCellSwitch(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Determines if the listview scrolled down enough to reveal a new cell |
|
||||||
* at the bottom of the list. If so, then the appropriate parameters are |
|
||||||
* updated. |
|
||||||
*/ |
|
||||||
public void checkAndHandleLastVisibleCellChange() { |
|
||||||
int currentLastVisibleItem = mCurrentFirstVisibleItem + mCurrentVisibleItemCount; |
|
||||||
int previousLastVisibleItem = mPreviousFirstVisibleItem + mPreviousVisibleItemCount; |
|
||||||
if (currentLastVisibleItem != previousLastVisibleItem) { |
|
||||||
if (mCellIsMobile && mMobileItemId != INVALID_ID) { |
|
||||||
updateNeighborViewsForID(mMobileItemId); |
|
||||||
handleCellSwitch(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
Loading…
Reference in new issue