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