Merge "Improve swipe mechanics."
diff --git a/res/values/dimen.xml b/res/values/dimen.xml
index 714532b..78f7467 100644
--- a/res/values/dimen.xml
+++ b/res/values/dimen.xml
@@ -81,4 +81,7 @@
<dimen name="widget_margin_bottom">22dip</dimen>
<dimen name="conversation_item_height">70sp</dimen>
<dimen name="conversation_item_height_wide">64sp</dimen>
+ <dimen name="min_swipe">10dip</dimen>
+ <dimen name="min_vert">10dip</dimen>
+ <dimen name="min_lock">20dip</dimen>
</resources>
diff --git a/src/com/android/mail/ui/AnimatedAdapter.java b/src/com/android/mail/ui/AnimatedAdapter.java
index d0f7b17..1e8f068 100644
--- a/src/com/android/mail/ui/AnimatedAdapter.java
+++ b/src/com/android/mail/ui/AnimatedAdapter.java
@@ -251,7 +251,7 @@
// The undo animation consists of fading in the conversation that
// had been destroyed.
ConversationItemView convView = (ConversationItemView) super.getView(position, null,
- mListView);
+ parent);
convView.bind(conversation, mViewMode, mBatchConversations, mFolder,
mCachedSettings != null ? !mCachedSettings.hideCheckboxes : false,
mSwipeEnabled);
diff --git a/src/com/android/mail/ui/SwipeHelper.java b/src/com/android/mail/ui/SwipeHelper.java
index 8a50d49..bd513cd 100644
--- a/src/com/android/mail/ui/SwipeHelper.java
+++ b/src/com/android/mail/ui/SwipeHelper.java
@@ -25,10 +25,10 @@
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.graphics.RectF;
import android.util.Log;
-import android.view.animation.LinearInterpolator;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
+import android.view.animation.LinearInterpolator;
import com.android.mail.browse.ConversationItemView;
@@ -42,6 +42,7 @@
private static final boolean CONSTRAIN_SWIPE = true;
private static final boolean FADE_OUT_DURING_SWIPE = true;
private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
+ private static final boolean LOG_SWIPE_DISMISS_VELOCITY = false; // STOPSHIP - DEBUG ONLY
public static final int X = 0;
public static final int Y = 1;
@@ -52,7 +53,7 @@
private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
private int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
private int MAX_DISMISS_VELOCITY = 2000; // dp/sec
- private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
+ private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 1; // ms
public static float ALPHA_FADE_START = 0f; // fraction of thumbnail width
// where fade starts
@@ -65,7 +66,7 @@
private int mSwipeDirection;
private VelocityTracker mVelocityTracker;
- private float mInitialTouchPos;
+ private float mInitialTouchPosX;
private boolean mDragging;
private ConversationItemView mCurrView;
private View mCurrAnimView;
@@ -74,15 +75,22 @@
private float mLastY;
private Collection<ConversationItemView> mAssociatedViews;
private final float mScrollSlop;
+ private float mInitialTouchPosY;
+ private float mMinSwipe;
+ private float mMinVert;
+ private float mMinLock;
public SwipeHelper(int swipeDirection, Callback callback, float densityScale,
- float pagingTouchSlop, float scrollSlop) {
+ float pagingTouchSlop, float scrollSlop, float minSwipe, float minVert, float minLock) {
mCallback = callback;
mSwipeDirection = swipeDirection;
mVelocityTracker = VelocityTracker.obtain();
mDensityScale = densityScale;
mPagingTouchSlop = pagingTouchSlop;
mScrollSlop = scrollSlop;
+ mMinSwipe = minSwipe;
+ mMinVert = minVert;
+ mMinLock = minLock;
}
public void setDensityScale(float densityScale) {
@@ -198,7 +206,8 @@
mCurrAnimView = mCallback.getChildContentView(mCurrView);
mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
mVelocityTracker.addMovement(ev);
- mInitialTouchPos = getPos(ev);
+ mInitialTouchPosX = getPos(ev);
+ mInitialTouchPosY = ev.getY();
}
break;
case MotionEvent.ACTION_MOVE:
@@ -214,14 +223,15 @@
}
mVelocityTracker.addMovement(ev);
float pos = getPos(ev);
- float delta = pos - mInitialTouchPos;
+ float delta = pos - mInitialTouchPosX;
if (Math.abs(delta) > mPagingTouchSlop) {
if (mCallback.getSelectionSet().isEmpty()
|| (!mCallback.getSelectionSet().isEmpty()
&& mCurrView.isChecked())) {
mCallback.onBeginDrag(mCurrView);
mDragging = true;
- mInitialTouchPos = getPos(ev) - getTranslation(mCurrAnimView);
+ mInitialTouchPosX = getPos(ev) - getTranslation(mCurrAnimView);
+ mInitialTouchPosY = ev.getY();
}
}
}
@@ -260,12 +270,14 @@
animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator anim = createDismissAnimation(animView, newPos, duration);
anim.addListener(new AnimatorListenerAdapter() {
+ @Override
public void onAnimationEnd(Animator animation) {
mCallback.onChildDismissed(view);
mCurrView.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
public void onAnimationUpdate(ValueAnimator animation) {
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
animView.setAlpha(getAlphaForOffset(animView));
@@ -278,6 +290,7 @@
private void dismissChildren(final Collection<ConversationItemView> views, float velocity) {
AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+ @Override
public void onAnimationEnd(Animator animation) {
mCallback.onChildrenDismissed(views);
mCurrView.setLayerType(View.LAYER_TYPE_NONE, null);
@@ -293,6 +306,7 @@
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
anim = createDismissAnimation(view, newPos, duration);
anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
public void onAnimationUpdate(ValueAnimator animation) {
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
view.setAlpha(getAlphaForOffset(view));
@@ -340,6 +354,7 @@
int duration = SNAP_ANIM_LEN;
anim.setDuration(duration);
anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
public void onAnimationUpdate(ValueAnimator animation) {
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
animView.setAlpha(getAlphaForOffset(animView));
@@ -365,27 +380,41 @@
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_MOVE:
if (mCurrView != null) {
- float delta = getPos(ev) - mInitialTouchPos;
- // don't let items that can't be dismissed be dragged more than
- // maxScrollDistance
+ float deltaX = getPos(ev) - mInitialTouchPosX;
+ float deltaY = Math.abs(ev.getY() - mInitialTouchPosY);
+ // If the user has gone vertical and not gone horizontal AT
+ // LEAST minBeforeLock, switch to scroll. Otherwise, cancel
+ // the swipe.
+ if (deltaY > mMinVert && (Math.abs(deltaX)) < mMinLock) {
+ return false;
+ }
+ float minDistance = mMinSwipe;
+ if (Math.abs(deltaX) < minDistance) {
+ // Don't start the drag until at least X distance has
+ // occurred.
+ return true;
+ }
+ // don't let items that can't be dismissed be dragged more
+ // than maxScrollDistance
if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
float size = getSize(mCurrAnimView);
float maxScrollDistance = 0.15f * size;
- if (Math.abs(delta) >= size) {
- delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
+ if (Math.abs(deltaX) >= size) {
+ deltaX = deltaX > 0 ? maxScrollDistance : -maxScrollDistance;
} else {
- delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2));
+ deltaX = maxScrollDistance
+ * (float) Math.sin((deltaX / size) * (Math.PI / 2));
}
}
if (mAssociatedViews != null && mAssociatedViews.size() > 1) {
for (View v : mAssociatedViews) {
- setTranslation(v, delta);
+ setTranslation(v, deltaX);
}
} else {
- setTranslation(mCurrAnimView, delta);
+ setTranslation(mCurrAnimView, deltaX);
}
if (FADE_OUT_DURING_SWIPE && mCanCurrViewBeDimissed) {
- if (mAssociatedViews != null && mAssociatedViews.size() > 1) {
+ if (mAssociatedViews != null && mAssociatedViews.size() > 1) {
for (View v : mAssociatedViews) {
v.setAlpha(getAlphaForOffset(mCurrAnimView));
}
@@ -406,14 +435,27 @@
float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker);
// Decide whether to dismiss the current view
- boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
- Math.abs(getTranslation(mCurrAnimView)) > 0.4 * getSize(mCurrAnimView);
- boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
- (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
- (velocity > 0) == (getTranslation(mCurrAnimView) > 0);
+ // Tweak constants below as required to prevent erroneous
+ // swipe/dismiss
+ float translation = Math.abs(getTranslation(mCurrAnimView));
+ float currAnimViewSize = getSize(mCurrAnimView);
+ // Long swipe = translation of .4 * width
+ boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH
+ && translation > 0.4 * currAnimViewSize;
+ // Fast swipe = > escapeVelocity and translation of .1 *
+ // width
+ boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity)
+ && (Math.abs(velocity) > Math.abs(perpendicularVelocity))
+ && (velocity > 0) == (getTranslation(mCurrAnimView) > 0)
+ && translation > 0.05 * currAnimViewSize;
+ if (LOG_SWIPE_DISMISS_VELOCITY) {
+ Log.v(TAG, "Swipe/Dismiss: " + velocity + "/" + escapeVelocity + "/"
+ + perpendicularVelocity + ", x: " + translation + "/"
+ + currAnimViewSize);
+ }
- boolean dismissChild = mCallback.canChildBeDismissed(mCurrView) &&
- (childSwipedFastEnough || childSwipedFarEnough);
+ boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
+ && (childSwipedFastEnough || childSwipedFarEnough);
if (dismissChild) {
if (mAssociatedViews != null && mAssociatedViews.size() > 1) {
diff --git a/src/com/android/mail/ui/SwipeableListView.java b/src/com/android/mail/ui/SwipeableListView.java
index 5f53656..a4733f4 100644
--- a/src/com/android/mail/ui/SwipeableListView.java
+++ b/src/com/android/mail/ui/SwipeableListView.java
@@ -61,8 +61,11 @@
super(context, attrs, defStyle);
float densityScale = getResources().getDisplayMetrics().density;
float scrollSlop = context.getResources().getInteger(R.integer.swipeScrollSlop);
+ float minSwipe = context.getResources().getDimension(R.dimen.min_swipe);
+ float minVert = context.getResources().getDimension(R.dimen.min_vert);
+ float minLock = context.getResources().getDimension(R.dimen.min_lock);
mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, densityScale,
- scrollSlop);
+ scrollSlop, minSwipe, minVert, minLock);
}
/**
@@ -183,6 +186,9 @@
private Conversation getConversation(View view) {
Conversation c = ((ConversationItemView) view).getConversation();
+ if (view.getParent() == null) {
+ return c;
+ }
c.position = getPositionForView(view);
return c;
}