diff --git a/README.md b/README.md
index 9ca3bb3..3abac0f 100644
--- a/README.md
+++ b/README.md
@@ -1,65 +1,13 @@
-Horizontal Variable ListView
+Draggable Horizontal ListView
==========================
-Horizontal ListView for Android. Based on the official [ListView][3] google code.
-The library includes also an ExpandableHListView, also based on the official [ExpandableListView][4].
-See the demo project for sample implementations
+This is forked from [sephiroth74/HorizontalVariableListView](https://github.com/sephiroth74/HorizontalVariableListView) and modified with [DynamicListView from DevBytes of Android Developer](https://www.youtube.com/watch?v=_BZIvjMgH-Q) to come up with a **Draggable** **Horizontal** ListView.
-## Usage (gradle)
-Add this line to your dependency group:
-
-
- compile 'it.sephiroth.android.library.horizontallistview:hlistview:1.2.2'
-
-## Features
-It supports almost all the features of the ListView widget.
-There are minor differences in the attributes supported like "hlv_dividerWidth" instead of the default "dividerHeight".
-
-This is the styleable used for the HListView class:
-
- <declare-styleable name="HListView">
- <attr name="android:entries" />
- <attr name="android:divider" />
- <attr name="hlv_dividerWidth" format="dimension" />
- <attr name="hlv_headerDividersEnabled" format="boolean" />
- <attr name="hlv_footerDividersEnabled" format="boolean" />
- <attr name="hlv_overScrollHeader" format="reference|color" />
- <attr name="hlv_overScrollFooter" format="reference|color" />
-
- <!--
- When "wrap_content" is used as value of the layout_height property.
- Pass the position, inside the adapter, of the view being used to measure the view
- or '-1' to use the default behavior ( default is -1 )
- -->
- <attr name="hlv_measureWithChild" format="integer" />
- </declare-styleable>
-
-
- <declare-styleable name="AbsHListView">
- <attr name="android:listSelector" />
- <attr name="android:smoothScrollbar" />
- <attr name="android:drawSelectorOnTop" />
- <attr name="android:cacheColorHint" />
- <attr name="android:scrollingCache" />
- <attr name="android:choiceMode" />
-
- <attr name="hlv_stackFromRight" format="boolean" />
- <attr name="hlv_transcriptMode">
- <enum name="disabled" value="0"/>
- <enum name="normal" value="1" />
- <enum name="alwaysScroll" value="2" />
- </attr>
-
- </declare-styleable>
-
-
+Please also checkout live demo on [Youtube](http://youtu.be/mnaq-MA16Cs) to see if it meets your requirement.
## ChangeLog
-* 1.2.0 Added the **ExpandableHListView**
-
-## API Requirements
-The minimum supported Android version is android 2.3 (API Level 9)
+* 0.0.1 Add DraggableHorizontalListView & its demo Activity
## License
This software is distributed under Apache License 2.0:
@@ -68,12 +16,5 @@ http://www.apache.org/licenses/LICENSE-2.0
---
> Author
-> [Alessandro Crugnola][2]
-
-
-
-[2]: http://www.sephiroth.it
-
-[3]: http://developer.android.com/reference/android/widget/ListView.html
+> [Robert Wang](http://twitter.com/cyberrob)
-[4]: http://developer.android.com/reference/android/widget/ExpandableListView.html
diff --git a/build.gradle b/build.gradle
index e148820..96e6e0b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.10.+'
+ classpath 'com.android.tools.build:gradle:0.12.2'
}
}
diff --git a/demo/AndroidManifest.xml b/demo/AndroidManifest.xml
index 1095572..e97cfc1 100644
--- a/demo/AndroidManifest.xml
+++ b/demo/AndroidManifest.xml
@@ -30,6 +30,9 @@
+
+
\ No newline at end of file
diff --git a/demo/build.gradle b/demo/build.gradle
index d0b160d..865bb2e 100644
--- a/demo/build.gradle
+++ b/demo/build.gradle
@@ -6,7 +6,7 @@ dependencies {
android {
compileSdkVersion 19
- buildToolsVersion "19.0.3"
+ buildToolsVersion "19.1.0"
defaultConfig {
versionCode 1
diff --git a/demo/res/layout/activity_draggable_hlistview.xml b/demo/res/layout/activity_draggable_hlistview.xml
new file mode 100644
index 0000000..3d55665
--- /dev/null
+++ b/demo/res/layout/activity_draggable_hlistview.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/src/it/sephiroth/android/sample/horizontalvariablelistviewdemo/MainActivity.java b/demo/src/it/sephiroth/android/sample/horizontalvariablelistviewdemo/MainActivity.java
index 03dcad6..83b7775 100644
--- a/demo/src/it/sephiroth/android/sample/horizontalvariablelistviewdemo/MainActivity.java
+++ b/demo/src/it/sephiroth/android/sample/horizontalvariablelistviewdemo/MainActivity.java
@@ -12,30 +12,34 @@
public class MainActivity extends ListActivity {
- @Override
- protected void onCreate( final Bundle savedInstanceState ) {
- super.onCreate( savedInstanceState );
-
- List activities = new ArrayList();
- activities.add( "Simple List" );
- activities.add( "Expandable List" );
-
-
- setListAdapter( new ArrayAdapter( this, android.R.layout.simple_list_item_1, activities ) );
- }
-
- @Override
- protected void onListItemClick( final ListView l, final View v, final int position, final long id ) {
-
- switch( position ) {
- case 0:
- startActivity( new Intent( this, SimpleHListActivity.class ) );
- break;
- case 1:
- startActivity( new Intent( this, ExpandableListActivity.class ) );
- break;
- }
-
- super.onListItemClick( l, v, position, id );
- }
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ List activities = new ArrayList();
+ activities.add("Simple List");
+ activities.add("Expandable List");
+ activities.add("Draggable HListView");
+
+
+ setListAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, activities));
+ }
+
+ @Override
+ protected void onListItemClick(final ListView l, final View v, final int position, final long id) {
+
+ switch (position) {
+ case 0:
+ startActivity(new Intent(this, SimpleHListActivity.class));
+ break;
+ case 1:
+ startActivity(new Intent(this, ExpandableListActivity.class));
+ break;
+ case 2:
+ startActivity(new Intent(this, SimpleDraggableHListViewActivity.class));
+ break;
+ }
+
+ super.onListItemClick(l, v, position, id);
+ }
}
diff --git a/demo/src/it/sephiroth/android/sample/horizontalvariablelistviewdemo/SimpleDraggableHListViewActivity.java b/demo/src/it/sephiroth/android/sample/horizontalvariablelistviewdemo/SimpleDraggableHListViewActivity.java
new file mode 100644
index 0000000..140ec1f
--- /dev/null
+++ b/demo/src/it/sephiroth/android/sample/horizontalvariablelistviewdemo/SimpleDraggableHListViewActivity.java
@@ -0,0 +1,145 @@
+package it.sephiroth.android.sample.horizontalvariablelistviewdemo;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import it.sephiroth.android.library.widget.AdapterView;
+import it.sephiroth.android.library.widget.DraggableHorizontalListView;
+
+/**
+ * Created by robertwang on 9/11/14.
+ */
+public class SimpleDraggableHListViewActivity extends Activity implements AdapterView.OnItemClickListener {
+
+ private static final String TAG = SimpleDraggableHListViewActivity.class.getSimpleName();
+ private TestAdapter mAdapter;
+ private DraggableHorizontalListView listView;
+ private ArrayList items;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_draggable_hlistview);
+
+ items = new ArrayList();
+ for (int i = 0; i < 50; i++) {
+ items.add(String.valueOf(i));
+ }
+
+ listView.setList(items);
+
+ mAdapter = new TestAdapter(this, R.layout.test_item_1, android.R.id.text1, items);
+ listView.setHeaderDividersEnabled(true);
+ listView.setFooterDividersEnabled(true);
+
+ listView.setChoiceMode(ListView.CHOICE_MODE_NONE);
+
+ //if( listView.getChoiceMode() == ListView.CHOICE_MODE_MULTIPLE ) {
+ listView.setOnItemClickListener(this);
+ //}
+
+ listView.setAdapter(mAdapter);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ String finalContent = "";
+ for (String item : items) {
+ if (items.indexOf(item) == items.size() - 1) {
+ finalContent += item;
+ } else {
+ finalContent += item + ",";
+ }
+ }
+ Log.d(TAG, "Final content: " + finalContent);
+ }
+
+ @Override
+ public void onContentChanged() {
+ super.onContentChanged();
+ listView = (DraggableHorizontalListView) findViewById(R.id.draggable_hListView);
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+
+ }
+
+ class TestAdapter extends ArrayAdapter {
+
+ private static final long INVALID_ID = -1;
+ List mItems;
+ LayoutInflater mInflater;
+ int mResource;
+ int mTextResId;
+
+ public TestAdapter(Context context, int resourceId, int textViewResourceId, List objects) {
+ super(context, resourceId, textViewResourceId, objects);
+ mInflater = LayoutInflater.from(context);
+ mResource = resourceId;
+ mTextResId = textViewResourceId;
+ mItems = objects;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public long getItemId(int position) {
+
+ if (position < 0 || position >= mItems.size()) {
+ return INVALID_ID;
+ }
+ return getItem(position).hashCode();
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 3;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return position % 3;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+
+ if (null == convertView) {
+ convertView = mInflater.inflate(mResource, parent, false);
+ }
+
+ TextView textView = (TextView) convertView.findViewById(mTextResId);
+ textView.setText(getItem(position));
+
+ int type = getItemViewType(position);
+
+ ViewGroup.LayoutParams params = convertView.getLayoutParams();
+ if (type == 0) {
+ params.width = getResources().getDimensionPixelSize(R.dimen.item_size_1);
+ } else if (type == 1) {
+ params.width = getResources().getDimensionPixelSize(R.dimen.item_size_2);
+ } else {
+ params.width = getResources().getDimensionPixelSize(R.dimen.item_size_3);
+ }
+
+ return convertView;
+ }
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 46e8179..3affc9b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Mar 04 09:42:43 EST 2014
+#Fri Sep 12 10:32:57 CST 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip
diff --git a/library/build.gradle b/library/build.gradle
index 50608d5..35de9db 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -5,7 +5,7 @@ version VERSION_NAME
android {
compileSdkVersion 19
- buildToolsVersion "19.0.3"
+ buildToolsVersion "19.1.0"
defaultConfig {
minSdkVersion 9
diff --git a/library/src/main/java/it/sephiroth/android/library/widget/DraggableHorizontalListView.java b/library/src/main/java/it/sephiroth/android/library/widget/DraggableHorizontalListView.java
new file mode 100644
index 0000000..1b90774
--- /dev/null
+++ b/library/src/main/java/it/sephiroth/android/library/widget/DraggableHorizontalListView.java
@@ -0,0 +1,649 @@
+package it.sephiroth.android.library.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+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.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * This is a HorizontalListView trying to mimic DynamicListView from Google DevByte.
+ * Only this works horizontally.
+ *
+ * Created by robertwang on 9/11/14.
+ */
+
+@TargetApi(14)
+public class DraggableHorizontalListView extends HListView {
+
+ // =====================================================================================
+
+ private final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 15;
+ private final int MOVE_DURATION = 150;
+ private final int LINE_THICKNESS = 15;
+
+ private int mLastEventX = -1;
+
+ private int mDownY = -1;
+ private int mDownX = -1;
+
+ private int mTotalOffset = 0;
+
+ // Is the selected cell being dragged around?
+ private boolean mCellIsMobile = false;
+
+ // Is the ListView scrolling while cell being dragged?
+ private boolean mIsMobileScrolling = false;
+
+ private int mSmoothScrollAmountAtEdge = 0;
+
+ private final int INVALID_ID = -1;
+
+ // The id of the item is one the left side of the target cell
+ private long mLeftItemId = INVALID_ID;
+
+ // The id of the item is one the right side of the target cell
+ private long mRightItemId = INVALID_ID;
+
+ // The id of the target cell
+ private long mMobileItemId = INVALID_ID;
+
+ // A drawable for containing the screen shot of target cell
+ 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;
+
+ // 3 types of states: FLING, IDLE, TOUCH_SCROLL
+ private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+ private ArrayList mDataList;
+ private static final String TAG = DraggableHorizontalListView.class.getSimpleName();
+
+ // =====================================================================================
+
+ public DraggableHorizontalListView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public DraggableHorizontalListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public DraggableHorizontalListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ public void setList(ArrayList dataList) {
+ mDataList = dataList;
+ }
+
+ public void init(Context context) {
+ setOnItemLongClickListener(mOnItemLongClickListener);
+ setOnScrollListener(mScrollListener);
+
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ mSmoothScrollAmountAtEdge = (int) (SMOOTH_SCROLL_AMOUNT_AT_EDGE / metrics.density);
+ }
+
+ private AdapterView.OnItemLongClickListener mOnItemLongClickListener =
+ new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView> parent, View view, int pos, long id) {
+ mTotalOffset = 0;
+
+ int position = pointToPosition(mDownX, mDownY);
+ int itemNum = position - getFirstVisiblePosition();
+ Log.d(TAG, "position: " + position + " ,itemNum: " + itemNum);
+
+ View selectedView = getChildAt(itemNum);
+ mMobileItemId = getAdapter().getItemId(position);
+ Log.d(TAG, "mMobileItemId: " + mMobileItemId);
+
+ 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);
+ BaseAdapter adapter = ((BaseAdapter) getAdapter());
+ mLeftItemId = adapter.getItemId(position - 1);
+ mRightItemId = adapter.getItemId(position + 1);
+ }
+
+ /**
+ * Retrieves the view in the list corresponding to itemID
+ */
+ public View getViewForID(long itemID) {
+ int firstVisiblePosition = getFirstVisiblePosition();
+ BaseAdapter adapter = ((BaseAdapter) 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);
+
+ mLastEventX = (int) event.getX(pointerIndex);
+
+ int deltaX = mLastEventX - mDownX;
+
+ //int deltaY = mLastEventY - mDownY;
+
+ if (mCellIsMobile) {
+ mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left + deltaX + mTotalOffset,
+ mHoverCellOriginalBounds.top);
+ 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 deltaX = mLastEventX - mDownX;
+ int deltaXTotal = mHoverCellOriginalBounds.left + mTotalOffset + deltaX;
+
+ View rightView = getViewForID(mRightItemId);
+ View mobileView = getViewForID(mMobileItemId);
+ View leftView = getViewForID(mLeftItemId);
+
+ boolean isRight = (rightView != null) && (deltaXTotal > rightView.getLeft());
+ boolean isLeft = (leftView != null) && (deltaXTotal < leftView.getLeft());
+
+ if (isRight || isLeft) {
+
+ final long switchItemID = isRight ? mRightItemId : mLeftItemId;
+ View switchView = isRight ? rightView : leftView;
+ final int originalItem = getPositionForView(mobileView);
+
+ if (switchView == null) {
+ updateNeighborViewsForID(mMobileItemId);
+ return;
+ }
+
+ swapElements(mDataList, originalItem, getPositionForView(switchView));
+
+ ((BaseAdapter) getAdapter()).notifyDataSetChanged();
+
+ mDownX = mLastEventX;
+
+ final int switchViewStartLeft = switchView.getLeft();
+
+ mobileView.setVisibility(View.VISIBLE);
+ switchView.setVisibility(View.INVISIBLE);
+
+ updateNeighborViewsForID(mMobileItemId);
+
+ final ViewTreeObserver observer = getViewTreeObserver();
+ observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ public boolean onPreDraw() {
+ observer.removeOnPreDrawListener(this);
+
+ View switchView = getViewForID(switchItemID);
+
+ mTotalOffset += deltaX;
+
+ int switchViewNewLeft = switchView.getLeft();
+ int delta = switchViewStartLeft - switchViewNewLeft;
+
+ switchView.setTranslationX(delta);
+
+ ObjectAnimator animator = ObjectAnimator.ofFloat(switchView,
+ View.TRANSLATION_X, 0);
+ animator.setDuration(MOVE_DURATION);
+ animator.start();
+
+ return true;
+ }
+ });
+ }
+ }
+
+ private void swapElements(ArrayList arrayList, int indexOne, int indexTwo) {
+ Log.d(TAG, "Swapping " + indexOne + " & " + indexTwo);
+ Object temp = arrayList.get(indexOne);
+ arrayList.set(indexOne, arrayList.get(indexTwo));
+ arrayList.set(indexTwo, temp.toString());
+ }
+
+
+ /**
+ * Resets all the appropriate fields to a default state while also animating
+ * the hover cell back to its correct location.
+ */
+ private void touchEventsEnded() {
+
+ Log.d(TAG, "touchEventEnded()");
+
+ 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;
+ }
+
+ Log.d(TAG, "mHoverCellOriginalBounds.left: " + mHoverCellOriginalBounds.left);
+ Log.d(TAG, "mobileView.getTop(): " + mobileView.getTop());
+ Log.d(TAG, "mobileView.getLeft(): " + mobileView.getLeft());
+
+// mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left, mobileView.getTop());
+ mHoverCellCurrentBounds.offsetTo(mobileView.getLeft(), 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) {
+ mLeftItemId = INVALID_ID;
+ mMobileItemId = INVALID_ID;
+ mRightItemId = 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) {
+ mLeftItemId = INVALID_ID;
+ mMobileItemId = INVALID_ID;
+ mRightItemId = 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 sBoundEvaluator = new TypeEvaluator() {
+ 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 width = getWidth();
+ //
+// int height = getHeight();
+ int extent = computeVerticalScrollExtent();
+ int range = computeVerticalScrollRange();
+ //
+// int hoverViewTop = r.top;
+// int hoverHeight = r.height();
+ //
+ int hoverViewLeft = r.left;
+ int hoverWidth = r.width();
+
+
+// if (hoverViewTop <= 0 && offset > 0) {
+// smoothScrollBy(-mSmoothScrollAmountAtEdge, 0);
+// return true;
+// }
+//
+// if (hoverViewTop + hoverHeight >= height && (offset + extent) < range) {
+// smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
+// return true;
+// }
+
+
+ if (hoverViewLeft <= 0 && offset > 0) {
+ smoothScrollBy(-mSmoothScrollAmountAtEdge, 0);
+ return true;
+ }
+
+ if (hoverViewLeft + hoverWidth >= width && (offset + extent) < range) {
+ smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
+ return true;
+ }
+
+
+ return false;
+ }
+
+
+ /**
+ * This scroll listener is added to the listview in order to handle cell swapping
+ * when the cell is either at the left or right 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 OnScrollListener mScrollListener = new OnScrollListener() {
+
+ private int mPreviousFirstVisibleItem = -1;
+ private int mPreviousVisibleItemCount = -1;
+ private int mCurrentFirstVisibleItem;
+ private int mCurrentVisibleItemCount;
+ private int mCurrentScrollState;
+
+
+ @Override
+ public void onScrollStateChanged(AbsHListView view, int scrollState) {
+ mCurrentScrollState = scrollState;
+ mScrollState = scrollState;
+ isScrollCompleted();
+ }
+
+ @Override
+ public void onScroll(AbsHListView 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;
+ }
+
+
+ /**
+ * 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();
+ }
+ }
+ }
+ };
+
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+
+ //Log.d(TAG, "@ KeyEvent handled by me!" + event.toString());
+
+ switch (event.getAction()) {
+ case KeyEvent.ACTION_DOWN:
+ return super.onKeyDown(event.getKeyCode(), event);
+
+ case KeyEvent.ACTION_UP:
+ return super.onKeyUp(event.getKeyCode(), event);
+
+ case KeyEvent.ACTION_MULTIPLE:
+ return super.onKeyMultiple(event.getKeyCode(), 1, event);
+
+ default: // shouldn't happen
+ return false;
+ }
+ }
+}