/*
* ******************************************************************************
* Copyright (c) 2013-2014 Gabriele Mariotti.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* *****************************************************************************
*/
package it.gmariotti.cardslib.library.prototypes;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.ListView;
import it.gmariotti.cardslib.library.R;
import it.gmariotti.cardslib.library.internal.Card;
/**
* @author Gabriele Mariotti (gabri.mariotti@gmail.com)
*/
public class SwipeDismissListItemViewTouchListener implements View.OnTouchListener {
/**
* The callback interface used by {@link SwipeDismissListItemViewTouchListener} to inform its client
* about a successful dismissal of one or more list item positions.
*/
public interface DismissCallbacks {
/**
* Called to determine whether the given position can be dismissed.
*/
boolean canDismiss(int position, Card card, CardWithList.ListObject listObject);
/**
* Called when the user has indicated they she would like to dismiss one or more list item
* positions.
*
* @param listView The originating {@link ListView}.
* @param position position to dismiss
*/
void onDismiss(LinearListView listView, int position,boolean dismissRight);
}
private CardWithList.ListObject mListObject;
private LinearListView mListView;
// Cached ViewConfiguration and system-wide constant values
private int mSlop;
private int mMinFlingVelocity;
private int mMaxFlingVelocity;
private long mAnimationTime;
// Fixed properties
private View itemView;
private DismissCallbacks mCallbacks;
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
// Transient properties
private float mDownX;
private float mDownY;
//private Card mToken;
private boolean mSwiping;
private VelocityTracker mVelocityTracker;
private boolean mPaused;
private float mTranslationX;
private int mSwipingSlop;
private int mDownPosition;
private View mDownView;
private int swipeDistanceDivisor = 2;
public SwipeDismissListItemViewTouchListener(LinearListView listView, DismissCallbacks callbacks) {
ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
mSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
mAnimationTime = listView.getContext().getResources().getInteger(
android.R.integer.config_shortAnimTime);
this.mListView = listView;
mCallbacks = callbacks;
swipeDistanceDivisor = listView.getContext().getResources().getInteger(R.integer.list_card_swipe_distance_divisor);
}
@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;
}
if (mSwiping) {
return true;
}
// Find the child view that was touched (perform a hit test)
Rect rect = new Rect();
int childCount = mListView.getChildCount();
int headerCount = 0;
int footerCount = 0;
int[] listViewCoords = new int[2];
mListView.getLocationOnScreen(listViewCoords);
int x = (int) motionEvent.getRawX() - listViewCoords[0];
int y = (int) motionEvent.getRawY() - listViewCoords[1];
View child=null;
for (int i = headerCount; i < (childCount - footerCount); i++) {
child = mListView.getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
mDownView = child;
break;
}
}
if (mDownView != null) {
mDownX = motionEvent.getRawX();
mDownY = motionEvent.getRawY();
mDownPosition = mListView.getPositionForView(mDownView);
CardWithList.ListObject object = mListView.getAdapter().getItem(mDownPosition);
if (mCallbacks.canDismiss(mDownPosition, object.getParentCard(), object)) {
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);
} else {
mDownView = null;
}
}
view.onTouchEvent(motionEvent);
return false;
}
case MotionEvent.ACTION_MOVE: {
if (mVelocityTracker == null || mPaused) {
break;
}
mVelocityTracker.addMovement(motionEvent);
float deltaX = motionEvent.getRawX() - mDownX;
float deltaY = motionEvent.getRawY() - mDownY;
if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) {
mSwiping = true;
mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
mDownView.getParent().requestDisallowInterceptTouchEvent(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));
mDownView.onTouchEvent(cancelEvent);
cancelEvent.recycle();
}
if (mSwiping) {
mDownView.setTranslationX(deltaX - mSwipingSlop);
/* mDownView.setAlpha(Math.max(0f, Math.min(1f,
1f - 2f * Math.abs(deltaX) / mViewWidth)));
*/
return true;
}
break;
}
case MotionEvent.ACTION_UP: {
if (mVelocityTracker == null) {
break;
}
float deltaX = motionEvent.getRawX() - mDownX;
mVelocityTracker.addMovement(motionEvent);
mVelocityTracker.computeCurrentVelocity(1000);
float velocityX = mVelocityTracker.getXVelocity();
float absVelocityX = Math.abs(velocityX);
float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
boolean dismiss = false;
boolean dismissRight = false;
if (Math.abs(deltaX) > mViewWidth / swipeDistanceDivisor) {
dismiss = true;
dismissRight = deltaX > 0;
} else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
&& absVelocityY < absVelocityX && mSwiping) {
// dismiss only if flinging in the same direction as dragging
dismiss = (velocityX < 0) == (deltaX < 0);
dismissRight = mVelocityTracker.getXVelocity() > 0;
}
if (dismiss && mDownPosition != ListView.INVALID_POSITION) {
// dismiss
dismiss(mDownView, mDownPosition, dismissRight);
} else {
// cancel
mDownView.animate().translationX(0).alpha(1)
.setDuration(mAnimationTime).setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
if (mSwiping){
//To prevent onClick event with a fast swipe
mSwiping = false;
return true;
}
mSwiping = false;
break;
}
case MotionEvent.ACTION_CANCEL: {
if (mVelocityTracker == null) {
break;
}
if (mDownView != null) {
// cancel
mDownView.animate()
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}
}
return false;
}
private void dismiss(final View view, final int position, final boolean dismissRight) {
if (view == null) {
// No view, shortcut to calling onDismiss to let it deal with adapter
// updates and all that.
mCallbacks.onDismiss(mListView, position, dismissRight );
return;
}
view.animate()
.translationX(dismissRight ? mViewWidth : -mViewWidth)
.alpha(0)
.setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finalizeDismiss(view, position,dismissRight);
}
});
}
private void finalizeDismiss(final View dismissView, final int dismissPosition,final boolean dismissRight ) {
mCallbacks.onDismiss(mListView, dismissPosition,dismissRight);
}
}