diff --git a/library/src/main/java/com/etsy/android/grid/ExtendableListView.java b/library/src/main/java/com/etsy/android/grid/ExtendableListView.java index 280ab43..259530f 100644 --- a/library/src/main/java/com/etsy/android/grid/ExtendableListView.java +++ b/library/src/main/java/com/etsy/android/grid/ExtendableListView.java @@ -20,6 +20,7 @@ import android.content.Context; import android.database.DataSetObserver; import android.graphics.Rect; +import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.support.v4.util.SparseArrayCompat; @@ -138,6 +139,33 @@ public abstract class ExtendableListView extends AbsListView { protected boolean mClipToPadding; private PerformClick mPerformClick; + + private Runnable mPendingCheckForTap; + private CheckForLongPress mPendingCheckForLongPress; + + private class CheckForLongPress extends WindowRunnnable implements Runnable { + public void run() { + final int motionPosition = mMotionPosition; + final View child = getChildAt(motionPosition); + if (child != null) { + final int longPressPosition = mMotionPosition; + final long longPressId = mAdapter.getItemId(mMotionPosition + mFirstPosition); + + boolean handled = false; + if (sameWindow() && !mDataChanged) { + handled = performLongPress(child, longPressPosition + mFirstPosition, longPressId); + } + if (handled) { + mTouchMode = TOUCH_MODE_IDLE; + setPressed(false); + child.setPressed(false); + } else { + mTouchMode = TOUCH_MODE_DONE_WAITING; + } + + } + } + } /** * A class that represents a fixed view in a list, for example a header at the top @@ -812,6 +840,39 @@ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { super.requestDisallowInterceptTouchEvent(disallowIntercept); } + final class CheckForTap implements Runnable { + public void run() { + if (mTouchMode == TOUCH_MODE_DOWN) { + mTouchMode = TOUCH_MODE_TAP; + final View child = getChildAt(mMotionPosition); + if (child != null && !child.hasFocusable()) { + mLayoutMode = LAYOUT_NORMAL; + + if (!mDataChanged) { + layoutChildren(); + child.setPressed(true); + setPressed(true); + + final int longPressTimeout = ViewConfiguration.getLongPressTimeout(); + final boolean longClickable = isLongClickable(); + + if (longClickable) { + if (mPendingCheckForLongPress == null) { + mPendingCheckForLongPress = new CheckForLongPress(); + } + mPendingCheckForLongPress.rememberWindowAttachCount(); + postDelayed(mPendingCheckForLongPress, longPressTimeout); + } else { + mTouchMode = TOUCH_MODE_DONE_WAITING; + } + } else { + mTouchMode = TOUCH_MODE_DONE_WAITING; + } + } + } + } + } + private boolean onTouchDown(final MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); @@ -831,7 +892,10 @@ private boolean onTouchDown(final MotionEvent event) { // is it a tap or a scroll .. we don't know yet! mTouchMode = TOUCH_MODE_DOWN; - // TODO : add handling for a click removed from here + if (mPendingCheckForTap == null) { + mPendingCheckForTap = new CheckForTap(); + } + postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); if (event.getEdgeFlags() != 0 && motionPosition < 0) { // If we couldn't find a view to click on, but the down event was touching @@ -891,6 +955,12 @@ private boolean onTouchCancel(final MotionEvent event) { mTouchMode = TOUCH_MODE_IDLE; setPressed(false); invalidate(); // redraw selector + final Handler handler = getHandler(); + + if (handler != null) { + handler.removeCallbacks(mPendingCheckForLongPress); + } + recycleVelocityTracker(); mActivePointerId = INVALID_POINTER; return true; @@ -909,7 +979,14 @@ private boolean onTouchUp(final MotionEvent event) { setPressed(false); invalidate(); // redraw selector + + final Handler handler = getHandler(); + if (handler != null) { + handler.removeCallbacks(mPendingCheckForLongPress); + } + recycleVelocityTracker(); + mActivePointerId = INVALID_POINTER; return true; } @@ -945,17 +1022,58 @@ private boolean onTouchUpScrolling(final MotionEvent event) { } private boolean onTouchUpTap(final MotionEvent event) { - if (mPerformClick == null) { - invalidate(); - mPerformClick = new PerformClick(); - } final int motionPosition = mMotionPosition; - if (!mDataChanged && motionPosition >= 0 && mAdapter.isEnabled(motionPosition)) { - final PerformClick performClick = mPerformClick; - performClick.mClickMotionPosition = motionPosition; - performClick.rememberWindowAttachCount(); - performClick.run(); + if (motionPosition >= 0) { + final View child = getChildAt(motionPosition); + if (child != null && !child.hasFocusable()) { + if (mTouchMode != TOUCH_MODE_DOWN) { + child.setPressed(false); + } + + if (mPerformClick == null) { + invalidate(); + mPerformClick = new PerformClick(); + } + + final PerformClick performClick = mPerformClick; + performClick.mClickMotionPosition = motionPosition; + performClick.rememberWindowAttachCount(); + + // mResurrectToPosition = motionPosition; + + if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { + final Handler handler = getHandler(); + if (handler != null) { + handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? + mPendingCheckForTap : mPendingCheckForLongPress); + } + mLayoutMode = LAYOUT_NORMAL; + if (!mDataChanged && motionPosition >= 0 && mAdapter.isEnabled(motionPosition)) { + mTouchMode = TOUCH_MODE_TAP; + layoutChildren(); + child.setPressed(true); + setPressed(true); + postDelayed(new Runnable() { + public void run() { + child.setPressed(false); + setPressed(false); + if (!mDataChanged) { + post(performClick); + } + mTouchMode = TOUCH_MODE_IDLE; + } + }, ViewConfiguration.getPressedStateDuration()); + } else { + mTouchMode = TOUCH_MODE_IDLE; + } + return true; + } else if (!mDataChanged && motionPosition >= 0 && mAdapter.isEnabled(motionPosition)) { + post(performClick); + } + } } + mTouchMode = TOUCH_MODE_IDLE; + return true; } @@ -1011,7 +1129,10 @@ private boolean startScrollIfNeeded(final int y) { mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop; } - // TODO : LONG PRESS + final Handler handler = getHandler(); + if (handler != null) { + handler.removeCallbacks(mPendingCheckForLongPress); + } setPressed(false); View motionView = getChildAt(mMotionPosition - mFirstPosition); if (motionView != null) { @@ -2754,6 +2875,25 @@ public void run() { } } } + + private boolean performLongPress(final View child, + final int longPressPosition, final long longPressId) { + boolean handled = false; + + OnItemLongClickListener onItemLongClickListener = getOnItemLongClickListener(); + if (onItemLongClickListener != null) { + handled = onItemLongClickListener.onItemLongClick(ExtendableListView.this, child, + longPressPosition, longPressId); + } +// if (!handled) { +// mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId); +// handled = super.showContextMenuForChild(AbsListView.this); +// } + if (handled) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } + return handled; + } /** * A base class for Runnables that will check that their view is still attached to diff --git a/sample/src/main/java/com/etsy/android/sample/StaggeredGridActivity.java b/sample/src/main/java/com/etsy/android/sample/StaggeredGridActivity.java index e6d6acc..5d16147 100644 --- a/sample/src/main/java/com/etsy/android/sample/StaggeredGridActivity.java +++ b/sample/src/main/java/com/etsy/android/sample/StaggeredGridActivity.java @@ -16,7 +16,7 @@ import com.etsy.android.grid.StaggeredGridView; -public class StaggeredGridActivity extends Activity implements AbsListView.OnScrollListener, AbsListView.OnItemClickListener { +public class StaggeredGridActivity extends Activity implements AbsListView.OnScrollListener, AbsListView.OnItemClickListener, AdapterView.OnItemLongClickListener { private static final String TAG = "StaggeredGridActivity"; public static final String SAVED_DATA_KEY = "SAVED_DATA"; @@ -64,6 +64,7 @@ protected void onCreate(Bundle savedInstanceState) { mGridView.setAdapter(mAdapter); mGridView.setOnScrollListener(this); mGridView.setOnItemClickListener(this); + mGridView.setOnItemLongClickListener(this); } @Override @@ -131,4 +132,11 @@ private void onLoadMoreItems() { public void onItemClick(AdapterView adapterView, View view, int position, long id) { Toast.makeText(this, "Item Clicked: " + position, Toast.LENGTH_SHORT).show(); } + + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) + { + Toast.makeText(this, "Item Long Clicked: " + position, Toast.LENGTH_SHORT).show(); + return true; + } }