From 1f84a610b7f154237f3077ace177f16717e838aa Mon Sep 17 00:00:00 2001 From: dhleong Date: Wed, 21 May 2014 14:29:33 -0400 Subject: [PATCH 1/4] Add eclipse project files for anyone who needs it; cleanup lint errors There were several leaks --- EnhancedListView/.classpath | 5 + EnhancedListView/.project | 16 ++ EnhancedListView/build.gradle | 1 + EnhancedListView/src/main/.classpath | 10 ++ EnhancedListView/src/main/.project | 33 ++++ EnhancedListView/src/main/AndroidManifest.xml | 4 +- .../android/listview/EnhancedListView.java | 151 ++++++++++-------- EnhancedListView/src/main/project.properties | 16 ++ 8 files changed, 169 insertions(+), 67 deletions(-) create mode 100644 EnhancedListView/.classpath create mode 100644 EnhancedListView/.project create mode 100644 EnhancedListView/src/main/.classpath create mode 100644 EnhancedListView/src/main/.project create mode 100644 EnhancedListView/src/main/project.properties diff --git a/EnhancedListView/.classpath b/EnhancedListView/.classpath new file mode 100644 index 0000000..fb884b4 --- /dev/null +++ b/EnhancedListView/.classpath @@ -0,0 +1,5 @@ + + + + + diff --git a/EnhancedListView/.project b/EnhancedListView/.project new file mode 100644 index 0000000..9c02703 --- /dev/null +++ b/EnhancedListView/.project @@ -0,0 +1,16 @@ + + + EnhancedListView + + + + org.eclipse.jdt.core.javanature + + + + org.eclipse.jdt.core.javabuilder + + + + + diff --git a/EnhancedListView/build.gradle b/EnhancedListView/build.gradle index 5e85f1d..5c8b3f8 100644 --- a/EnhancedListView/build.gradle +++ b/EnhancedListView/build.gradle @@ -22,6 +22,7 @@ android { } apply plugin: 'maven' +apply plugin: 'eclipse' apply plugin: 'signing' version = android.defaultConfig.versionName diff --git a/EnhancedListView/src/main/.classpath b/EnhancedListView/src/main/.classpath new file mode 100644 index 0000000..88bb0ee --- /dev/null +++ b/EnhancedListView/src/main/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/EnhancedListView/src/main/.project b/EnhancedListView/src/main/.project new file mode 100644 index 0000000..7e4dbb7 --- /dev/null +++ b/EnhancedListView/src/main/.project @@ -0,0 +1,33 @@ + + + EnhancedListView + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/EnhancedListView/src/main/AndroidManifest.xml b/EnhancedListView/src/main/AndroidManifest.xml index 5f9f174..c3abe39 100644 --- a/EnhancedListView/src/main/AndroidManifest.xml +++ b/EnhancedListView/src/main/AndroidManifest.xml @@ -1,4 +1,6 @@ - + + + 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..0cee8bb 100644 --- a/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java +++ b/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java @@ -15,6 +15,15 @@ */ package de.timroes.android.listview; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +import android.annotation.TargetApi; import android.content.Context; import android.graphics.Rect; import android.os.Build; @@ -41,13 +50,6 @@ import com.nineoldandroids.view.ViewHelper; import com.nineoldandroids.view.ViewPropertyAnimator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; - /** * A {@link android.widget.ListView} offering enhanced features like Swipe To Dismiss and an * undo functionality. See the documentation on GitHub for more information. @@ -233,14 +235,14 @@ private class PendingDismissData implements Comparable { */ public View childView; - PendingDismissData(int position, View view, View childView) { + PendingDismissData(final int position, final View view, final View childView) { this.position = position; this.view = view; this.childView = childView; } @Override - public int compareTo(PendingDismissData other) { + public int compareTo(final PendingDismissData other) { // Sort by descending position return other.position - position; } @@ -255,7 +257,7 @@ private class UndoClickListener implements OnClickListener { * @param v The view that was clicked. */ @Override - public void onClick(View v) { + public void onClick(final View v) { if(!mUndoActions.isEmpty()) { switch(mUndoStyle) { case SINGLE_POPUP: @@ -264,7 +266,7 @@ public void onClick(View v) { break; case COLLAPSED_POPUP: Collections.reverse(mUndoActions); - for(Undoable undo : mUndoActions) { + for(final Undoable undo : mUndoActions) { undo.undo(); } mUndoActions.clear(); @@ -290,15 +292,25 @@ public void onClick(View v) { } } - private class HideUndoPopupHandler extends Handler { + private static class HideUndoPopupHandler extends Handler { + + WeakReference mRef; + + public HideUndoPopupHandler(final EnhancedListView ref) { + mRef = new WeakReference(ref); + } /** * Subclasses must implement this to receive messages. */ @Override - public void handleMessage(Message msg) { - if(msg.what == mValidDelayedMsgId) { - discardUndo(); + public void handleMessage(final Message msg) { + final EnhancedListView ref = mRef.get(); + if (ref == null) + return; // context gone + + if(msg.what == ref.mValidDelayedMsgId) { + ref.discardUndo(); } } } @@ -321,9 +333,9 @@ public void handleMessage(Message msg) { private int mUndoHideDelay = 5000; private int mSwipingLayout; - private List mUndoActions = new ArrayList(); - private SortedSet mPendingDismisses = new TreeSet(); - private List mAnimatedViews = new LinkedList(); + private final List mUndoActions = new ArrayList(); + private final SortedSet mPendingDismisses = new TreeSet(); + private final List mAnimatedViews = new LinkedList(); private int mDismissAnimationRefCount; private boolean mSwipePaused; @@ -338,15 +350,15 @@ public void handleMessage(Message msg) { private float mScreenDensity; private PopupWindow mUndoPopup; - private int mValidDelayedMsgId; - private Handler mHideUndoHandler = new HideUndoPopupHandler(); + int mValidDelayedMsgId; + private final Handler mHideUndoHandler = new HideUndoPopupHandler(this); private Button mUndoButton; // END Swipe-To-Dismiss /** * {@inheritDoc} */ - public EnhancedListView(Context context) { + public EnhancedListView(final Context context) { super(context); init(context); } @@ -354,7 +366,7 @@ public EnhancedListView(Context context) { /** * {@inheritDoc} */ - public EnhancedListView(Context context, AttributeSet attrs) { + public EnhancedListView(final Context context, final AttributeSet attrs) { super(context, attrs); init(context); } @@ -362,18 +374,18 @@ public EnhancedListView(Context context, AttributeSet attrs) { /** * {@inheritDoc} */ - public EnhancedListView(Context context, AttributeSet attrs, int defStyle) { + public EnhancedListView(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); init(context); } - private void init(Context ctx) { + private void init(final Context ctx) { if(isInEditMode()) { // Skip initializing when in edit mode (IDE preview). return; } - ViewConfiguration vc =ViewConfiguration.get(ctx); + final ViewConfiguration vc =ViewConfiguration.get(ctx); mSlop = getResources().getDimension(R.dimen.elv_touch_slop); mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); @@ -381,13 +393,13 @@ private void init(Context ctx) { android.R.integer.config_shortAnimTime); // Initialize undo popup - LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View undoView = inflater.inflate(R.layout.elv_undo_popup, null); + final LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + final View undoView = inflater.inflate(R.layout.elv_undo_popup, null); mUndoButton = (Button)undoView.findViewById(R.id.undo); mUndoButton.setOnClickListener(new UndoClickListener()); mUndoButton.setOnTouchListener(new OnTouchListener() { @Override - public boolean onTouch(View v, MotionEvent event) { + public boolean onTouch(final View v, final MotionEvent event) { // If the user touches the screen invalidate the current running delay by incrementing // the valid message id. So this delay won't hide the undo popup anymore mValidDelayedMsgId++; @@ -451,7 +463,7 @@ public EnhancedListView disableSwipeToDismiss() { * @param dismissCallback The callback used to handle dismisses of list items. * @return This {@link de.timroes.android.listview.EnhancedListView} */ - public EnhancedListView setDismissCallback(OnDismissCallback dismissCallback) { + public EnhancedListView setDismissCallback(final OnDismissCallback dismissCallback) { mDismissCallback = dismissCallback; return this; } @@ -462,7 +474,7 @@ public EnhancedListView setDismissCallback(OnDismissCallback dismissCallback) { * @param shouldSwipeCallback The callback used to handle swipes of list items. * @return This {@link de.timroes.android.listview.EnhancedListView} */ - public EnhancedListView setShouldSwipeCallback(OnShouldSwipeCallback shouldSwipeCallback) { + public EnhancedListView setShouldSwipeCallback(final OnShouldSwipeCallback shouldSwipeCallback) { mShouldSwipeCallback = shouldSwipeCallback; return this; } @@ -475,7 +487,7 @@ public EnhancedListView setShouldSwipeCallback(OnShouldSwipeCallback shouldSwipe * @param undoStyle The style of this listview. * @return This {@link de.timroes.android.listview.EnhancedListView} */ - public EnhancedListView setUndoStyle(UndoStyle undoStyle) { + public EnhancedListView setUndoStyle(final UndoStyle undoStyle) { mUndoStyle = undoStyle; return this; } @@ -489,7 +501,7 @@ public EnhancedListView setUndoStyle(UndoStyle undoStyle) { * @param hideDelay The delay in milliseconds. * @return This {@link de.timroes.android.listview.EnhancedListView} */ - public EnhancedListView setUndoHideDelay(int hideDelay) { + public EnhancedListView setUndoHideDelay(final int hideDelay) { mUndoHideDelay = hideDelay; return this; } @@ -503,7 +515,7 @@ public EnhancedListView setUndoHideDelay(int hideDelay) { * * @see #setUndoHideDelay(int) */ - public EnhancedListView setRequireTouchBeforeDismiss(boolean touchBeforeDismiss) { + public EnhancedListView setRequireTouchBeforeDismiss(final boolean touchBeforeDismiss) { mTouchBeforeAutoHide = touchBeforeDismiss; return this; } @@ -519,7 +531,7 @@ public EnhancedListView setRequireTouchBeforeDismiss(boolean touchBeforeDismiss) * @param direction The direction to which the swipe should be limited. * @return This {@link de.timroes.android.listview.EnhancedListView} */ - public EnhancedListView setSwipeDirection(SwipeDirection direction) { + public EnhancedListView setSwipeDirection(final SwipeDirection direction) { mSwipeDirection = direction; return this; } @@ -538,7 +550,7 @@ public EnhancedListView setSwipeDirection(SwipeDirection direction) { * @param swipingLayoutId The id (from R.id) of the view, that should be swiped. * @return This {@link de.timroes.android.listview.EnhancedListView} */ - public EnhancedListView setSwipingLayout(int swipingLayoutId) { + public EnhancedListView setSwipingLayout(final int swipingLayoutId) { mSwipingLayout = swipingLayoutId; return this; } @@ -550,7 +562,7 @@ public EnhancedListView setSwipingLayout(int swipingLayoutId) { * break your data consistency. */ public void discardUndo() { - for(Undoable undoable : mUndoActions) { + for(final Undoable undoable : mUndoActions) { undoable.discard(); } mUndoActions.clear(); @@ -574,14 +586,14 @@ public void discardUndo() { * @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) { + public void delete(final int position) { if(mDismissCallback == null) { throw new IllegalStateException("You must set an OnDismissCallback, before deleting items."); } 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()); + final View childView = getChildAt(position - getFirstVisiblePosition()); View view = null; if(mSwipingLayout > 0) { view = childView.findViewById(mSwipingLayout); @@ -601,7 +613,7 @@ public void delete(int position) { * @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) { + private void slideOutView(final View view, final View childView, final int position, final boolean toRightSide) { // Only start new animation, if this view isn't already animated (too fast swiping bug) synchronized(mAnimationLock) { @@ -618,14 +630,14 @@ private void slideOutView(final View view, final View childView, final int posit .setDuration(mAnimationTime) .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { performDismiss(view, childView, position); } }); } @Override - public boolean onTouchEvent(MotionEvent ev) { + public boolean onTouchEvent(final MotionEvent ev) { if (!mSwipeEnabled) { return super.onTouchEvent(ev); @@ -650,12 +662,12 @@ public boolean onTouchEvent(MotionEvent ev) { // TODO: ensure this is a finger, and set a flag // Find the child view that was touched (perform a hit test) - Rect rect = new Rect(); - int childCount = getChildCount(); - int[] listViewCoords = new int[2]; + final Rect rect = new Rect(); + final int childCount = getChildCount(); + final int[] listViewCoords = new int[2]; getLocationOnScreen(listViewCoords); - int x = (int) ev.getRawX() - listViewCoords[0]; - int y = (int) ev.getRawY() - listViewCoords[1]; + final int x = (int) ev.getRawX() - listViewCoords[0]; + final int y = (int) ev.getRawY() - listViewCoords[1]; View child; for (int i = getHeaderViewsCount(); i < childCount; i++) { child = getChildAt(i); @@ -664,7 +676,7 @@ public boolean onTouchEvent(MotionEvent ev) { if (rect.contains(x, y)) { // if a specific swiping layout has been giving, use this to swipe. if(mSwipingLayout > 0) { - View swipingView = child.findViewById(mSwipingLayout); + final View swipingView = child.findViewById(mSwipingLayout); if(swipingView != null) { mSwipeDownView = swipingView; mSwipeDownChild = child; @@ -680,12 +692,14 @@ public boolean onTouchEvent(MotionEvent ev) { if (mSwipeDownView != null) { // test if the item should be swiped - int position = getPositionForView(mSwipeDownView) - getHeaderViewsCount(); + final int position = getPositionForView(mSwipeDownView) - getHeaderViewsCount(); if ((mShouldSwipeCallback == null) || mShouldSwipeCallback.onShouldSwipe(this, position)) { mDownX = ev.getRawX(); mDownPosition = position; + if (mVelocityTracker != null) + mVelocityTracker.recycle(); mVelocityTracker = VelocityTracker.obtain(); mVelocityTracker.addMovement(ev); } else { @@ -702,11 +716,11 @@ public boolean onTouchEvent(MotionEvent ev) { break; } - float deltaX = ev.getRawX() - mDownX; + final float deltaX = ev.getRawX() - mDownX; mVelocityTracker.addMovement(ev); mVelocityTracker.computeCurrentVelocity(1000); - float velocityX = Math.abs(mVelocityTracker.getXVelocity()); - float velocityY = Math.abs(mVelocityTracker.getYVelocity()); + final float velocityX = Math.abs(mVelocityTracker.getXVelocity()); + final float velocityY = Math.abs(mVelocityTracker.getYVelocity()); boolean dismiss = false; boolean dismissRight = false; if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) { @@ -729,6 +743,9 @@ && velocityY < velocityX && mSwiping && isSwipeDirectionValid(mVelocityTracker.g .setDuration(mAnimationTime) .setListener(null); } + + if (mVelocityTracker != null) + mVelocityTracker.recycle(); // recycle first mVelocityTracker = null; mDownX = 0; mSwipeDownView = null; @@ -748,7 +765,7 @@ && velocityY < velocityX && mSwiping && isSwipeDirectionValid(mVelocityTracker.g float deltaX = ev.getRawX() - mDownX; // Only start swipe in correct direction if(isSwipeDirectionValid(deltaX)) { - ViewParent parent = getParent(); + final ViewParent parent = getParent(); if(parent != null) { // If we swipe don't allow parent to intercept touch (e.g. like NavigationDrawer does) // otherwise swipe would not be working. @@ -759,11 +776,12 @@ && velocityY < velocityX && mSwiping && isSwipeDirectionValid(mVelocityTracker.g requestDisallowInterceptTouchEvent(true); // Cancel ListView's touch (un-highlighting the item) - MotionEvent cancelEvent = MotionEvent.obtain(ev); + final MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); super.onTouchEvent(cancelEvent); + cancelEvent.recycle(); } } else { // If we swiped into wrong direction, act like this was the new @@ -798,12 +816,12 @@ private void performDismiss(final View dismissView, final View listItemView, fin final ViewGroup.LayoutParams lp = listItemView.getLayoutParams(); final int originalLayoutHeight = lp.height; - int originalHeight = listItemView.getHeight(); - ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime); + final int originalHeight = listItemView.getHeight(); + final ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime); animator.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { // Make sure no other animation is running. Remove animation from running list, that just finished boolean noAnimationLeft; @@ -816,14 +834,14 @@ public void onAnimationEnd(Animator animation) { if (noAnimationLeft) { // No active animations, process all pending dismisses. - for(PendingDismissData dismiss : mPendingDismisses) { + for(final PendingDismissData dismiss : mPendingDismisses) { if(mUndoStyle == UndoStyle.SINGLE_POPUP) { - for(Undoable undoable : mUndoActions) { + for(final Undoable undoable : mUndoActions) { undoable.discard(); } mUndoActions.clear(); } - Undoable undoable = mDismissCallback.onDismiss(EnhancedListView.this, dismiss.position); + final Undoable undoable = mDismissCallback.onDismiss(EnhancedListView.this, dismiss.position); if(undoable != null) { mUndoActions.add(undoable); } @@ -835,7 +853,7 @@ public void onAnimationEnd(Animator animation) { changeButtonLabel(); // Show undo popup - float yLocationOffset = getResources().getDimension(R.dimen.elv_undo_bottom_offset); + final float yLocationOffset = getResources().getDimension(R.dimen.elv_undo_bottom_offset); mUndoPopup.setWidth((int)Math.min(mScreenDensity * 400, getWidth() * 0.9f)); mUndoPopup.showAtLocation(EnhancedListView.this, Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, @@ -850,7 +868,7 @@ public void onAnimationEnd(Animator animation) { } ViewGroup.LayoutParams lp; - for (PendingDismissData pendingDismiss : mPendingDismisses) { + for (final PendingDismissData pendingDismiss : mPendingDismisses) { ViewHelper.setAlpha(pendingDismiss.view, 1f); ViewHelper.setTranslationX(pendingDismiss.view, 0); lp = pendingDismiss.childView.getLayoutParams(); @@ -865,7 +883,7 @@ public void onAnimationEnd(Animator animation) { animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { + public void onAnimationUpdate(final ValueAnimator valueAnimator) { lp.height = (Integer) valueAnimator.getAnimatedValue(); listItemView.setLayoutParams(lp); } @@ -912,12 +930,12 @@ private void changeButtonLabel() { private OnScrollListener makeScrollListener() { return new OnScrollListener() { @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { + public void onScrollStateChanged(final AbsListView view, final int scrollState) { mSwipePaused = scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL; } @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) { } }; } @@ -930,7 +948,8 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun * @param deltaX The delta of x coordinate of the swipe. * @return Whether the delta of a swipe is in the right direction. */ - private boolean isSwipeDirectionValid(float deltaX) { + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + private boolean isSwipeDirectionValid(final float deltaX) { int rtlSign = 1; // On API level 17 and above, check if we are in a Right-To-Left layout @@ -954,7 +973,7 @@ private boolean isSwipeDirectionValid(float deltaX) { } @Override - protected void onWindowVisibilityChanged(int visibility) { + protected void onWindowVisibilityChanged(final int visibility) { super.onWindowVisibilityChanged(visibility); /* diff --git a/EnhancedListView/src/main/project.properties b/EnhancedListView/src/main/project.properties new file mode 100644 index 0000000..8646ea8 --- /dev/null +++ b/EnhancedListView/src/main/project.properties @@ -0,0 +1,16 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-19 +android.library=true +android.library.reference.1=../../../../JakeWharton-NineOldAndroids/library From 92fb64076a37ba0a1a25aa44472d4b099c4fe2f1 Mon Sep 17 00:00:00 2001 From: dhleong Date: Wed, 21 May 2014 14:32:37 -0400 Subject: [PATCH 2/4] Fix offset error when ListView has headers, preventing swipe --- .../java/de/timroes/android/listview/EnhancedListView.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 0cee8bb..20d8da7 100644 --- a/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java +++ b/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java @@ -668,8 +668,9 @@ public boolean onTouchEvent(final MotionEvent ev) { getLocationOnScreen(listViewCoords); final int x = (int) ev.getRawX() - listViewCoords[0]; final int y = (int) ev.getRawY() - listViewCoords[1]; + final int offset = Math.max(0, getHeaderViewsCount() - getFirstVisiblePosition()); View child; - for (int i = getHeaderViewsCount(); i < childCount; i++) { + for (int i = offset; i < childCount; i++) { child = getChildAt(i); if(child != null) { child.getHitRect(rect); From 2af4137c67b2d39c124c97352052bda112a6c172 Mon Sep 17 00:00:00 2001 From: dhleong Date: Wed, 21 May 2014 17:46:35 -0400 Subject: [PATCH 3/4] Handle user-provided OnScrollListeners Otherwise, if a user passes one (for endless scrolling, for example), our "pause" functionality breaks --- .../android/listview/EnhancedListView.java | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 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 20d8da7..15ee984 100644 --- a/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java +++ b/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java @@ -322,6 +322,8 @@ public void handleMessage(final Message msg) { private long mAnimationTime; private final Object[] mAnimationLock = new Object[0]; + + private OnScrollListener mUserScrollListener; // Swipe-To-Dismiss private boolean mSwipeEnabled; @@ -355,6 +357,27 @@ public void handleMessage(final Message msg) { private Button mUndoButton; // END Swipe-To-Dismiss + private final OnScrollListener mScrollListener = new OnScrollListener() { + + @Override + public void onScrollStateChanged(final AbsListView view, final int scrollState) { + mSwipePaused = scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL; + + // call through to user's listener + final OnScrollListener userScrollListener = mUserScrollListener; + if (userScrollListener != null) + userScrollListener.onScrollStateChanged(view, scrollState); + } + + @Override + public void onScroll(final AbsListView view, final int firstVisibleItem, + final int visibleItemCount, final int totalItemCount) { + final OnScrollListener userScrollListener = mUserScrollListener; + if (userScrollListener != null) + userScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); + } + }; + /** * {@inheritDoc} */ @@ -414,7 +437,7 @@ public boolean onTouch(final View v, final MotionEvent event) { mScreenDensity = getResources().getDisplayMetrics().density; // END initialize undo popup - setOnScrollListener(makeScrollListener()); + super.setOnScrollListener(mScrollListener); } @@ -453,6 +476,14 @@ public EnhancedListView disableSwipeToDismiss() { mSwipeEnabled = false; return this; } + + @Override + public void setOnScrollListener(final OnScrollListener l) { + mUserScrollListener = l; + + // call super again with ours so it gets fired as expected + super.setOnScrollListener(mScrollListener); + } /** * Sets the callback to be called when the user dismissed an item from the list (either by @@ -928,19 +959,6 @@ private void changeButtonLabel() { mUndoButton.setText(msg); } - private OnScrollListener makeScrollListener() { - return new OnScrollListener() { - @Override - public void onScrollStateChanged(final AbsListView view, final int scrollState) { - mSwipePaused = scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL; - } - - @Override - public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) { - } - }; - } - /** * Checks whether the delta of a swipe indicates, that the swipe is in the * correct direction, regarding the direction set via From b92772f4d2a18ac97fd586007cf2cbcc0912e5af Mon Sep 17 00:00:00 2001 From: dhleong Date: Thu, 22 May 2014 11:03:37 -0400 Subject: [PATCH 4/4] Use local refs to prevent race condition-induced crashes --- .../android/listview/EnhancedListView.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 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 15ee984..7acf3f6 100644 --- a/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java +++ b/EnhancedListView/src/main/java/de/timroes/android/listview/EnhancedListView.java @@ -645,6 +645,8 @@ public void delete(final int position) { * @param toRightSide Whether it should slide out to the right side. */ private void slideOutView(final View view, final View childView, final int position, final boolean toRightSide) { + if (view == null) + return; // sanity check // Only start new animation, if this view isn't already animated (too fast swiping bug) synchronized(mAnimationLock) { @@ -722,9 +724,10 @@ public boolean onTouchEvent(final MotionEvent ev) { } } - if (mSwipeDownView != null) { + final View swipeDown = mSwipeDownView; + if (swipeDown != null) { // test if the item should be swiped - final int position = getPositionForView(mSwipeDownView) - getHeaderViewsCount(); + final int position = getPositionForView(swipeDown) - getHeaderViewsCount(); if ((mShouldSwipeCallback == null) || mShouldSwipeCallback.onShouldSwipe(this, position)) { mDownX = ev.getRawX(); @@ -764,12 +767,14 @@ && velocityY < velocityX && mSwiping && isSwipeDirectionValid(mVelocityTracker.g dismiss = true; dismissRight = mVelocityTracker.getXVelocity() > 0; } + + final View swipeDown = mSwipeDownView; if (dismiss) { // dismiss slideOutView(mSwipeDownView, mSwipeDownChild, mDownPosition, dismissRight); - } else if(mSwiping) { + } else if(mSwiping && swipeDown != null) { // Swipe back to regular position - ViewPropertyAnimator.animate(mSwipeDownView) + ViewPropertyAnimator.animate(swipeDown) .translationX(0) .alpha(1) .setDuration(mAnimationTime) @@ -789,11 +794,12 @@ && velocityY < velocityX && mSwiping && isSwipeDirectionValid(mVelocityTracker.g case MotionEvent.ACTION_MOVE: { - if (mVelocityTracker == null || mSwipePaused) { + final VelocityTracker tracker = mVelocityTracker; + if (tracker == null || mSwipePaused) { break; } - mVelocityTracker.addMovement(ev); + tracker.addMovement(ev); float deltaX = ev.getRawX() - mDownX; // Only start swipe in correct direction if(isSwipeDirectionValid(deltaX)) { @@ -822,9 +828,10 @@ && velocityY < velocityX && mSwiping && isSwipeDirectionValid(mVelocityTracker.g deltaX = 0; } - if (mSwiping) { - ViewHelper.setTranslationX(mSwipeDownView, deltaX); - ViewHelper.setAlpha(mSwipeDownView, Math.max(0f, Math.min(1f, + final View swipeDown = mSwipeDownView; + if (mSwiping && swipeDown != null) { + ViewHelper.setTranslationX(swipeDown, deltaX); + ViewHelper.setAlpha(swipeDown, Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX) / mViewWidth))); return true; } @@ -844,6 +851,10 @@ && velocityY < velocityX && mSwiping && isSwipeDirectionValid(mVelocityTracker.g * @param dismissPosition The position of the view inside the list. */ private void performDismiss(final View dismissView, final View listItemView, final int dismissPosition) { + + // sanity check + if (dismissView == null || listItemView == null) + return; final ViewGroup.LayoutParams lp = listItemView.getLayoutParams(); final int originalLayoutHeight = lp.height;