1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
|
@SuppressLint("Recycle")
public class SwipeListViewTouchListener implements View.OnTouchListener {
// Cached ViewConfiguration and system-wide constant values
private int mSlop;
private int mMinFlingVelocity;
private int mMaxFlingVelocity;
private long mAnimationTime;
// Fixed properties
private ListView mListView;
private OnSwipeCallback mCallback;
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
@SuppressWarnings("unused")
private boolean dismissLeft = true;
@SuppressWarnings("unused")
private boolean dismissRight = true;
// Transient properties
private List < PendingSwipeData > mPendingSwipes = new ArrayList < PendingSwipeData > ();
private int mDismissAnimationRefCount = 0;
private float mDownX;
private boolean mSwiping;
private VelocityTracker mVelocityTracker;
private int mDownPosition;
private View mDownView;
private boolean mPaused;
boolean swipeRight;
/**
* The callback interface used by {@link SwipeListViewTouchListener} to inform its client
* about a successful swipe of one or more list item positions.
*/
public interface OnSwipeCallback {
/**
* Called when the user has swiped the list item to the left.
*
* @param listView The originating {@link ListView}.
* @param reverseSortedPositions An array of positions to dismiss, sorted in descending
* order for convenience.
*/
void onSwipeLeft(ListView listView, int[] reverseSortedPositions);
void onSwipeRight(ListView listView, int[] reverseSortedPositions);
}
/**
* Constructs a new swipe-to-action touch listener for the given list view.
*
* @param listView The list view whose items should be dismissable.
* @param callback The callback to trigger when the user has indicated that she would like to
* dismiss one or more list items.
*/
public SwipeListViewTouchListener(ListView listView, OnSwipeCallback callback) {
ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
mSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
mAnimationTime = listView.getContext().getResources().getInteger(
android.R.integer.config_shortAnimTime);
mListView = listView;
mCallback = callback;
}
/**
* Constructs a new swipe-to-action touch listener for the given list view.
*
* @param listView The list view whose items should be dismissable.
* @param callback The callback to trigger when the user has indicated that she would like to
* dismiss one or more list items.
* @param dismissLeft set if the dismiss animation is up when the user swipe to the left
* @param dismissRight set if the dismiss animation is up when the user swipe to the right
* @see #SwipeListViewTouchListener(ListView, OnSwipeCallback, boolean, boolean)
*/
public SwipeListViewTouchListener(ListView listView, OnSwipeCallback callback, boolean dismissLeft, boolean dismissRight) {
this(listView, callback);
this.dismissLeft = dismissLeft;
this.dismissRight = dismissRight;
}
/**
* Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
*
* @param enabled Whether or not to watch for gestures.
*/
public void setEnabled(boolean enabled) {
mPaused = !enabled;
}
/**
* Returns an {@link android.widget.AbsListView.OnScrollListener} to be added to the
* {@link ListView} using
* {@link ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}.
* If a scroll listener is already assigned, the caller should still pass scroll changes
* through to this listener. This will ensure that this
* {@link SwipeListViewTouchListener} is paused during list view scrolling.</p>
*
* @see {@link SwipeListViewTouchListener}
*/
public AbsListView.OnScrollListener makeScrollListener() {
return new AbsListView.OnScrollListener() {@
Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
}
@
Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {}
};
}
@
Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (mViewWidth < 2) {
mViewWidth = mListView.getWidth();
}
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
{
if (mPaused) {
return false;
}
// 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 = mListView.getChildCount();
int[] listViewCoords = new int[2];
mListView.getLocationOnScreen(listViewCoords);
int x = (int) motionEvent.getRawX() - listViewCoords[0];
int y = (int) motionEvent.getRawY() - listViewCoords[1];
View child;
for (int i = 0; i < childCount; i++) {
child = mListView.getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
mDownView = child;
break;
}
}
if (mDownView != null) {
mDownX = motionEvent.getRawX();
mDownPosition = mListView.getPositionForView(mDownView);
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);
try {
switch (CustomViewPager.currentPosition){
case 0:
if(swipeRight) mDownView.setBackgroundColor(Color.GREEN);
else mDownView.setBackgroundColor(Color.TRANSPARENT);
break;
case 1:
if(swipeRight) mDownView.setBackgroundColor(Color.GREEN);
else mDownView.setBackgroundColor(Color.RED);
break;
case 2:
if(swipeRight) mDownView.setBackgroundColor(Color.TRANSPARENT);
else mDownView.setBackgroundColor(Color.RED);
break;
}
} catch (NullPointerException e) {
// TODO: handle exception
}
}
view.onTouchEvent(motionEvent);
return true;
}
case MotionEvent.ACTION_UP:
{
if (mVelocityTracker == null) {
break;
}
float deltaX = motionEvent.getRawX() - mDownX;
mVelocityTracker.addMovement(motionEvent);
mVelocityTracker.computeCurrentVelocity(500); // 1000 by defaut but it was too much
float velocityX = Math.abs(mVelocityTracker.getXVelocity());
float velocityY = Math.abs(mVelocityTracker.getYVelocity());
boolean swipe = false;
swipeRight = false;
if (Math.abs(deltaX) > mViewWidth / 2) {
swipe = true;
swipeRight = deltaX > 0;
} else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity && velocityY < velocityX) {
swipe = true;
swipeRight = mVelocityTracker.getXVelocity() > 0;
}
if (swipe) {
// sufficent swipe value
final View downView = mDownView; // mDownView gets null'd before animation ends
final int downPosition = mDownPosition;
final boolean toTheRight = swipeRight;
++mDismissAnimationRefCount;
mDownView.animate()
.translationX(swipeRight ? mViewWidth : -mViewWidth)
.alpha(0)
.setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {@
Override
public void onAnimationEnd(Animator animation) {
switch (CustomViewPager.currentPosition){
case 0:
performSwipeAction(downView, downPosition, toTheRight, toTheRight ? true : false);
break;
case 1:
performSwipeAction(downView, downPosition, toTheRight, toTheRight ? true : true);
break;
case 2:
performSwipeAction(downView, downPosition, toTheRight, toTheRight ? false : true);
break;
}
}
});
} else {
// cancel
mDownView.animate()
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker = null;
mDownX = 0;
mDownView.setBackgroundColor(Color.TRANSPARENT);
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}
case MotionEvent.ACTION_MOVE:
{
if (mVelocityTracker == null || mPaused) {
break;
}
// int pos = CustomViewPager.currentPosition;
mVelocityTracker.addMovement(motionEvent);
float deltaX = motionEvent.getRawX() - mDownX;
if (Math.abs(deltaX) > mSlop) {
mSwiping = true;
mListView.requestDisallowInterceptTouchEvent(true);
// Cancel ListView's touch (un-highlighting the item)
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
(motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
mListView.onTouchEvent(cancelEvent);
}
if (mSwiping) {
mDownView.setTranslationX(deltaX);
try {
switch (CustomViewPager.currentPosition){
case 0:
if(swipeRight) mDownView.setBackgroundColor(Color.GREEN);
else mDownView.setBackgroundColor(Color.TRANSPARENT);
break;
case 1:
if(swipeRight) mDownView.setBackgroundColor(Color.GREEN);
else mDownView.setBackgroundColor(Color.RED);
break;
case 2:
if(swipeRight) mDownView.setBackgroundColor(Color.TRANSPARENT);
else mDownView.setBackgroundColor(Color.RED);
break;
}
} catch (NullPointerException e) {
// TODO: handle exception
}
mDownView.setAlpha(Math.max(0f, Math.min(1f,
1f - 2f * Math.abs(deltaX) / mViewWidth)));
return true;
}
break;
}
}
return false;
}
class PendingSwipeData implements Comparable < PendingSwipeData > {
public int position;
public View view;
public PendingSwipeData(int position, View view) {
this.position = position;
this.view = view;
}
@
Override
public int compareTo(PendingSwipeData other) {
// Sort by descending position
return other.position - position;
}
}
private void performSwipeAction(final View swipeView, final int swipePosition, boolean toTheRight, boolean dismiss) {
// Animate the dismissed list item to zero-height and fire the dismiss callback when
// all dismissed list item animations have completed. This triggers layout on each animation
// frame; in the future we may want to do something smarter and more performant.
final ViewGroup.LayoutParams lp = swipeView.getLayoutParams();
final int originalHeight = swipeView.getHeight();
final boolean swipeRight = toTheRight;
ValueAnimator animator;
if (dismiss)
animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
else
animator = ValueAnimator.ofInt(originalHeight, originalHeight - 1).setDuration(mAnimationTime);
animator.addListener(new AnimatorListenerAdapter() {@
Override
public void onAnimationEnd(Animator animation) {
--mDismissAnimationRefCount;
if (mDismissAnimationRefCount == 0) {
// No active animations, process all pending dismisses.
// Sort by descending position
Collections.sort(mPendingSwipes);
int[] swipePositions = new int[mPendingSwipes.size()];
for (int i = mPendingSwipes.size() - 1; i >= 0; i--) {
swipePositions[i] = mPendingSwipes.get(i).position;
}
if (swipeRight)
mCallback.onSwipeRight(mListView, swipePositions);
else
mCallback.onSwipeLeft(mListView, swipePositions);
ViewGroup.LayoutParams lp;
for (PendingSwipeData pendingDismiss: mPendingSwipes) {
// Reset view presentation
pendingDismiss.view.setAlpha(1f);
pendingDismiss.view.setTranslationX(0);
lp = pendingDismiss.view.getLayoutParams();
lp.height = originalHeight;
pendingDismiss.view.setLayoutParams(lp);
}
mPendingSwipes.clear();
}
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@
Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
swipeView.setLayoutParams(lp);
}
});
mPendingSwipes.add(new PendingSwipeData(swipePosition, swipeView));
animator.start();
}
} |
Partager