From c2610a1408be87ee8a1b6616cf0b6161e13faf65 Mon Sep 17 00:00:00 2001 From: Prem Nirmal Date: Thu, 3 Apr 2014 10:29:24 -0400 Subject: [PATCH] added a setLocationOffset method so that you can set a different offset for each listview as opposed to having a global offset for all enhancedListViews in the project --- .../android/listview/EnhancedListView.java | 169 ++++++++++-------- 1 file changed, 90 insertions(+), 79 deletions(-) diff --git a/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java b/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java index 45222f9..54d53e3 100644 --- a/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java +++ b/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java @@ -94,7 +94,7 @@ public enum UndoStyle { * Defines the direction in which list items can be swiped out to delete them. * Use {@link #setSwipeDirection(de.timroes.android.listview.EnhancedListView.SwipeDirection)} * to change the default behavior. - *

+ *

* Note: This method requires the Swipe to Dismiss feature enabled. Use * {@link #enableSwipeToDismiss()} * to enable the feature. @@ -133,7 +133,7 @@ public interface OnShouldSwipeCallback { /** * Called when the user is swiping an item from the list. - *

+ *

* If the user should get the possibility to swipe the item, return true. * Otherwise, return false to disable swiping for this item. * @@ -155,7 +155,7 @@ public interface OnDismissCallback { /** * Called when the user has deleted an item from the list. The item has been deleted from * the {@code listView} at {@code position}. Delete this item from your adapter. - *

+ *

* Don't return from this method, before your item has been deleted from the adapter, meaning * if you delete the item in another thread, you have to make sure, you don't return from * this method, before the item has been deleted. Since the way how you delete your item @@ -163,7 +163,7 @@ public interface OnDismissCallback { * cannot handle that synchronizing for you. If you return from this method before you removed * the view from the adapter, you will most likely get errors like exceptions and flashing * items in the list. - *

+ *

* If the user should get the possibility to undo this deletion, return an implementation * of {@link de.timroes.android.listview.EnhancedListView.Undoable} from this method. * If you return {@code null} no undo will be possible. You are free to return an {@code Undoable} @@ -172,7 +172,7 @@ public interface OnDismissCallback { * @param listView The {@link EnhancedListView} the item has been deleted from. * @param position The position of the item to delete from your adapter. * @return An {@link de.timroes.android.listview.EnhancedListView.Undoable}, if you want - * to give the user the possibility to undo the deletion. + * to give the user the possibility to undo the deletion. */ Undoable onDismiss(EnhancedListView listView, int position); @@ -191,7 +191,7 @@ public abstract static class Undoable { * This method must undo the deletion you've done in * {@link EnhancedListView.OnDismissCallback#onDismiss(EnhancedListView, int)} and reinsert * the element into the adapter. - *

+ *

* In the most implementations, you will only remove the list item from your adapter * in the {@code onDismiss} method and delete it from the database (or your permanent * storage) in {@link #discard()}. In that case you only need to reinsert the item @@ -217,7 +217,8 @@ public String getTitle() { * (whereas in {@link de.timroes.android.listview.EnhancedListView.OnDismissCallback#onKeyDown(int, android.view.KeyEvent)} * you should only remove it from the list adapter). */ - public void discard() { } + public void discard() { + } } @@ -256,15 +257,15 @@ private class UndoClickListener implements OnClickListener { */ @Override public void onClick(View v) { - if(!mUndoActions.isEmpty()) { - switch(mUndoStyle) { + if (!mUndoActions.isEmpty()) { + switch (mUndoStyle) { case SINGLE_POPUP: mUndoActions.get(0).undo(); mUndoActions.clear(); break; case COLLAPSED_POPUP: Collections.reverse(mUndoActions); - for(Undoable undo : mUndoActions) { + for (Undoable undo : mUndoActions) { undo.undo(); } mUndoActions.clear(); @@ -277,8 +278,8 @@ public void onClick(View v) { } // Dismiss dialog or change text - if(mUndoActions.isEmpty()) { - if(mUndoPopup.isShowing()) { + if (mUndoActions.isEmpty()) { + if (mUndoPopup.isShowing()) { mUndoPopup.dismiss(); } } else { @@ -297,8 +298,8 @@ private class HideUndoPopupHandler extends Handler { */ @Override public void handleMessage(Message msg) { - if(msg.what == mValidDelayedMsgId) { - discardUndo(); + if (msg.what == mValidDelayedMsgId) { + discardUndo(); } } } @@ -341,6 +342,7 @@ public void handleMessage(Message msg) { private int mValidDelayedMsgId; private Handler mHideUndoHandler = new HideUndoPopupHandler(); private Button mUndoButton; + private int mLocationOffset = 15; // 15dp // END Swipe-To-Dismiss /** @@ -369,21 +371,21 @@ public EnhancedListView(Context context, AttributeSet attrs, int defStyle) { private void init(Context ctx) { - if(isInEditMode()) { + if (isInEditMode()) { // Skip initializing when in edit mode (IDE preview). return; } - ViewConfiguration vc =ViewConfiguration.get(ctx); + ViewConfiguration vc = ViewConfiguration.get(ctx); mSlop = getResources().getDimension(R.dimen.elv_touch_slop); - mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); + mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); mAnimationTime = ctx.getResources().getInteger( android.R.integer.config_shortAnimTime); // Initialize undo popup - LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); View undoView = inflater.inflate(R.layout.elv_undo_popup, null); - mUndoButton = (Button)undoView.findViewById(R.id.undo); + mUndoButton = (Button) undoView.findViewById(R.id.undo); mUndoButton.setOnClickListener(new UndoClickListener()); mUndoButton.setOnTouchListener(new OnTouchListener() { @Override @@ -394,7 +396,7 @@ public boolean onTouch(View v, MotionEvent event) { return false; } }); - mUndoPopupTextView = (TextView)undoView.findViewById(R.id.text); + mUndoPopupTextView = (TextView) undoView.findViewById(R.id.text); mUndoPopup = new PopupWindow(undoView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, false); mUndoPopup.setAnimationStyle(R.style.elv_fade_animation); @@ -418,12 +420,12 @@ public boolean onTouch(View v, MotionEvent event) { * * @return The {@link de.timroes.android.listview.EnhancedListView} * @throws java.lang.IllegalStateException when you haven't passed an {@link EnhancedListView.OnDismissCallback} - * to {@link #setDismissCallback(EnhancedListView.OnDismissCallback)} before calling this - * method. + * to {@link #setDismissCallback(EnhancedListView.OnDismissCallback)} before calling this + * method. */ public EnhancedListView enableSwipeToDismiss() { - if(mDismissCallback == null) { + if (mDismissCallback == null) { throw new IllegalStateException("You must pass an OnDismissCallback to the list before enabling Swipe to Dismiss."); } @@ -500,7 +502,6 @@ public EnhancedListView setUndoHideDelay(int hideDelay) { * * @param touchBeforeDismiss Whether the screen needs to be touched before the countdown starts. * @return This {@link de.timroes.android.listview.EnhancedListView} - * * @see #setUndoHideDelay(int) */ public EnhancedListView setRequireTouchBeforeDismiss(boolean touchBeforeDismiss) { @@ -512,7 +513,7 @@ public EnhancedListView setRequireTouchBeforeDismiss(boolean touchBeforeDismiss) * Sets the directions in which a list item can be swiped to delete. * By default this is set to {@link SwipeDirection#BOTH} so that an item * can be swiped into both directions. - *

+ *

* Note: This method requires the Swipe to Dismiss feature enabled. Use * {@link #enableSwipeToDismiss()} to enable the feature. * @@ -531,7 +532,7 @@ public EnhancedListView setSwipeDirection(SwipeDirection direction) { * out, to stay where it is (and maybe explain that the item is going to be deleted). * If you never call this method (or call it with 0), the whole view will be swiped. Also if there * is no view in a list item, with the given id, the whole view will be swiped. - *

+ *

* Note: This method requires the Swipe to Dismiss feature enabled. Use * {@link #enableSwipeToDismiss()} to enable the feature. * @@ -550,11 +551,11 @@ public EnhancedListView setSwipingLayout(int swipingLayoutId) { * break your data consistency. */ public void discardUndo() { - for(Undoable undoable : mUndoActions) { + for (Undoable undoable : mUndoActions) { undoable.discard(); } mUndoActions.clear(); - if(mUndoPopup.isShowing()) { + if (mUndoPopup.isShowing()) { mUndoPopup.dismiss(); } } @@ -562,7 +563,7 @@ public void discardUndo() { /** * Delete the list item at the specified position. This will animate the item sliding out of the * list and then collapsing until it vanished (same as if the user slides out an item). - *

+ *

* NOTE: If you are using list headers, be aware, that the position argument must take care of * them. Meaning 0 references the first list header. So if you want to delete the first list * item, you have to pass the number of list headers as {@code position}. Most of the times @@ -571,22 +572,22 @@ public void discardUndo() { * * @param position The position of the item in the list. * @throws java.lang.IndexOutOfBoundsException when trying to delete an item outside of the list range. - * @throws java.lang.IllegalStateException when this method is called before an {@link EnhancedListView.OnDismissCallback} - * is set via {@link #setDismissCallback(de.timroes.android.listview.EnhancedListView.OnDismissCallback)}. - * */ + * @throws java.lang.IllegalStateException when this method is called before an {@link EnhancedListView.OnDismissCallback} + * is set via {@link #setDismissCallback(de.timroes.android.listview.EnhancedListView.OnDismissCallback)}. + */ public void delete(int position) { - if(mDismissCallback == null) { + if (mDismissCallback == null) { throw new IllegalStateException("You must set an OnDismissCallback, before deleting items."); } - if(position < 0 || position >= getCount()) { + if (position < 0 || position >= getCount()) { throw new IndexOutOfBoundsException(String.format("Tried to delete item %d. #items in list: %d", position, getCount())); } View childView = getChildAt(position - getFirstVisiblePosition()); View view = null; - if(mSwipingLayout > 0) { + if (mSwipingLayout > 0) { view = childView.findViewById(mSwipingLayout); } - if(view == null) { + if (view == null) { view = childView; } slideOutView(view, childView, position, true); @@ -596,16 +597,16 @@ public void delete(int position) { * Slide out a view to the right or left of the list. After the animation has finished, the * view will be dismissed by calling {@link #performDismiss(android.view.View, android.view.View, int)}. * - * @param view The view, that should be slided out. - * @param childView The whole view of the list item. - * @param position The item position of the item. + * @param view The view, that should be slided out. + * @param childView The whole view of the list item. + * @param position The item position of the item. * @param toRightSide Whether it should slide out to the right side. */ private void slideOutView(final View view, final View childView, final int position, boolean toRightSide) { // Only start new animation, if this view isn't already animated (too fast swiping bug) - synchronized(mAnimationLock) { - if(mAnimatedViews.contains(view)) { + synchronized (mAnimationLock) { + if (mAnimatedViews.contains(view)) { return; } ++mDismissAnimationRefCount; @@ -632,7 +633,7 @@ public boolean onTouchEvent(MotionEvent ev) { } // Send a delayed message to hide popup - if(mTouchBeforeAutoHide && mUndoPopup.isShowing()) { + if (mTouchBeforeAutoHide && mUndoPopup.isShowing()) { mHideUndoHandler.sendMessageDelayed(mHideUndoHandler.obtainMessage(mValidDelayedMsgId), mUndoHideDelay); } @@ -659,13 +660,13 @@ public boolean onTouchEvent(MotionEvent ev) { View child; for (int i = getHeaderViewsCount(); i < childCount; i++) { child = getChildAt(i); - if(child != null) { + if (child != null) { child.getHitRect(rect); if (rect.contains(x, y)) { // if a specific swiping layout has been giving, use this to swipe. - if(mSwipingLayout > 0) { + if (mSwipingLayout > 0) { View swipingView = child.findViewById(mSwipingLayout); - if(swipingView != null) { + if (swipingView != null) { mSwipeDownView = swipingView; mSwipeDownChild = child; break; @@ -682,12 +683,12 @@ public boolean onTouchEvent(MotionEvent ev) { // test if the item should be swiped int position = getPositionForView(mSwipeDownView) - getHeaderViewsCount(); if ((mShouldSwipeCallback == null) || - mShouldSwipeCallback.onShouldSwipe(this, position)) { - mDownX = ev.getRawX(); + mShouldSwipeCallback.onShouldSwipe(this, position)) { + mDownX = ev.getRawX(); mDownPosition = position; - mVelocityTracker = VelocityTracker.obtain(); - mVelocityTracker.addMovement(ev); + mVelocityTracker = VelocityTracker.obtain(); + mVelocityTracker.addMovement(ev); } else { // set back to null to revert swiping mSwipeDownView = mSwipeDownChild = null; @@ -721,7 +722,7 @@ && velocityY < velocityX && mSwiping && isSwipeDirectionValid(mVelocityTracker.g if (dismiss) { // dismiss slideOutView(mSwipeDownView, mSwipeDownChild, mDownPosition, dismissRight); - } else if(mSwiping) { + } else if (mSwiping) { // Swipe back to regular position ViewPropertyAnimator.animate(mSwipeDownView) .translationX(0) @@ -747,9 +748,9 @@ && velocityY < velocityX && mSwiping && isSwipeDirectionValid(mVelocityTracker.g mVelocityTracker.addMovement(ev); float deltaX = ev.getRawX() - mDownX; // Only start swipe in correct direction - if(isSwipeDirectionValid(deltaX)) { + if (isSwipeDirectionValid(deltaX)) { ViewParent parent = getParent(); - if(parent != null) { + if (parent != null) { // If we swipe don't allow parent to intercept touch (e.g. like NavigationDrawer does) // otherwise swipe would not be working. parent.requestDisallowInterceptTouchEvent(true); @@ -784,13 +785,22 @@ && velocityY < velocityX && mSwiping && isSwipeDirectionValid(mVelocityTracker.g return super.onTouchEvent(ev); } + /** + * Setting the location offset specifically for this ListView + * + * @param offSetInDp offset from bottom in DP + */ + public void setLocationOffset(int offSetInDp) { + mLocationOffset = offSetInPixels; + } + /** * Animate the dismissed list item to zero-height and fire the dismiss callback when * all dismissed list item animations have completed. * - * @param dismissView The view that has been slided out. - * @param listItemView The list item view. This is the whole view of the list item, and not just - * the part, that the user swiped. + * @param dismissView The view that has been slided out. + * @param listItemView The list item view. This is the whole view of the list item, and not just + * the part, that the user swiped. * @param dismissPosition The position of the view inside the list. */ private void performDismiss(final View dismissView, final View listItemView, final int dismissPosition) { @@ -807,7 +817,7 @@ public void onAnimationEnd(Animator animation) { // Make sure no other animation is running. Remove animation from running list, that just finished boolean noAnimationLeft; - synchronized(mAnimationLock) { + synchronized (mAnimationLock) { --mDismissAnimationRefCount; mAnimatedViews.remove(dismissView); noAnimationLeft = mDismissAnimationRefCount == 0; @@ -816,33 +826,34 @@ public void onAnimationEnd(Animator animation) { if (noAnimationLeft) { // No active animations, process all pending dismisses. - for(PendingDismissData dismiss : mPendingDismisses) { - if(mUndoStyle == UndoStyle.SINGLE_POPUP) { - for(Undoable undoable : mUndoActions) { + for (PendingDismissData dismiss : mPendingDismisses) { + if (mUndoStyle == UndoStyle.SINGLE_POPUP) { + for (Undoable undoable : mUndoActions) { undoable.discard(); } mUndoActions.clear(); } Undoable undoable = mDismissCallback.onDismiss(EnhancedListView.this, dismiss.position); - if(undoable != null) { + if (undoable != null) { mUndoActions.add(undoable); } mValidDelayedMsgId++; } - if(!mUndoActions.isEmpty()) { + if (!mUndoActions.isEmpty()) { changePopupText(); changeButtonLabel(); // Show undo popup - float yLocationOffset = getResources().getDimension(R.dimen.elv_undo_bottom_offset); - mUndoPopup.setWidth((int)Math.min(mScreenDensity * 400, getWidth() * 0.9f)); + DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics(); + float yLocationOffset = Math.round(mLocationOffset * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT)); + mUndoPopup.setWidth((int) Math.min(mScreenDensity * 400, getWidth() * 0.9f)); mUndoPopup.showAtLocation(EnhancedListView.this, Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, (int) yLocationOffset); // Queue the dismiss only if required - if(!mTouchBeforeAutoHide) { + if (!mTouchBeforeAutoHide) { // Send a delayed message to hide popup mHideUndoHandler.sendMessageDelayed(mHideUndoHandler.obtainMessage(mValidDelayedMsgId), mUndoHideDelay); @@ -882,14 +893,14 @@ public void onAnimationUpdate(ValueAnimator valueAnimator) { */ private void changePopupText() { String msg = null; - if(mUndoActions.size() > 1) { + if (mUndoActions.size() > 1) { msg = getResources().getString(R.string.elv_n_items_deleted, mUndoActions.size()); - } else if(mUndoActions.size() >= 1) { + } else if (mUndoActions.size() >= 1) { // Set title from single undoable or when no multiple deletion string // is given msg = mUndoActions.get(mUndoActions.size() - 1).getTitle(); - if(msg == null) { + if (msg == null) { msg = getResources().getString(R.string.elv_item_deleted); } } @@ -901,7 +912,7 @@ private void changePopupText() { */ private void changeButtonLabel() { String msg; - if(mUndoActions.size() > 1 && mUndoStyle == UndoStyle.COLLAPSED_POPUP) { + if (mUndoActions.size() > 1 && mUndoStyle == UndoStyle.COLLAPSED_POPUP) { msg = getResources().getString(R.string.elv_undo_all); } else { msg = getResources().getString(R.string.elv_undo); @@ -934,14 +945,14 @@ private boolean isSwipeDirectionValid(float deltaX) { int rtlSign = 1; // On API level 17 and above, check if we are in a Right-To-Left layout - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - if(getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { rtlSign = -1; } } // Check if swipe has been done in the correct direction - switch(mSwipeDirection) { + switch (mSwipeDirection) { default: case BOTH: return true; @@ -952,18 +963,18 @@ private boolean isSwipeDirectionValid(float deltaX) { } } - + @Override - protected void onWindowVisibilityChanged(int visibility) { - super.onWindowVisibilityChanged(visibility); - + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + /* * If the container window no longer visiable, * dismiss visible undo popup window so it won't leak, * cos the container window will be destroyed before dismissing the popup window. */ - if(visibility != View.VISIBLE) { - discardUndo(); - } - } + if (visibility != View.VISIBLE) { + discardUndo(); + } + } }