Updated the feel of swipe-to-reply.

This commit is contained in:
Greyson Parrelli 2019-09-26 13:43:38 -04:00
parent 4e2afa7362
commit 1d8e85fcad
2 changed files with 59 additions and 28 deletions

View File

@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.util.ServiceUtil;
class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback { class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
private static float SWIPE_SUCCESS_PROGRESS = ConversationSwipeAnimationHelper.PROGRESS_TRIGGER_POINT; private static float SWIPE_SUCCESS_DX = ConversationSwipeAnimationHelper.TRIGGER_DX;
private static long SWIPE_SUCCESS_VIBE_TIME_MS = 10; private static long SWIPE_SUCCESS_VIBE_TIME_MS = 10;
private boolean swipeBack; private boolean swipeBack;
@ -76,25 +76,26 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
@NonNull Canvas c, @NonNull Canvas c,
@NonNull RecyclerView recyclerView, @NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) float dx, float dy, int actionState, boolean isCurrentlyActive)
{ {
if (cannotSwipeViewHolder(viewHolder)) return; if (cannotSwipeViewHolder(viewHolder)) return;
float sign = getSignFromDirection(viewHolder.itemView); float sign = getSignFromDirection(viewHolder.itemView);
boolean isCorrectSwipeDir = sameSign(dX, sign); boolean isCorrectSwipeDir = sameSign(dx, sign);
float progress = Math.abs(dX) / (float) viewHolder.itemView.getWidth();
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && isCorrectSwipeDir) { if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && isCorrectSwipeDir) {
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, progress, sign); ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, Math.abs(dx), sign);
handleSwipeFeedback((ConversationItem) viewHolder.itemView, progress); handleSwipeFeedback((ConversationItem) viewHolder.itemView, Math.abs(dx));
setTouchListener(recyclerView, viewHolder, progress); setTouchListener(recyclerView, viewHolder, Math.abs(dx));
} else if (actionState == ItemTouchHelper.ACTION_STATE_IDLE || dx == 0) {
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, 0, 1);
} }
if (progress == 0) shouldTriggerSwipeFeedback = true; if (dx == 0) shouldTriggerSwipeFeedback = true;
} }
private void handleSwipeFeedback(@NonNull ConversationItem item, float progress) { private void handleSwipeFeedback(@NonNull ConversationItem item, float dx) {
if (progress > SWIPE_SUCCESS_PROGRESS && shouldTriggerSwipeFeedback) { if (dx > SWIPE_SUCCESS_DX && shouldTriggerSwipeFeedback) {
vibrate(item.getContext()); vibrate(item.getContext());
ConversationSwipeAnimationHelper.trigger(item); ConversationSwipeAnimationHelper.trigger(item);
shouldTriggerSwipeFeedback = false; shouldTriggerSwipeFeedback = false;
@ -112,7 +113,7 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
private void setTouchListener(@NonNull RecyclerView recyclerView, private void setTouchListener(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder,
float progress) float dx)
{ {
recyclerView.setOnTouchListener((v, event) -> { recyclerView.setOnTouchListener((v, event) -> {
switch (event.getAction()) { switch (event.getAction()) {
@ -120,7 +121,7 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
shouldTriggerSwipeFeedback = true; shouldTriggerSwipeFeedback = true;
break; break;
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
handleTouchActionUp(recyclerView, viewHolder, progress); handleTouchActionUp(recyclerView, viewHolder, dx);
case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_CANCEL:
swipeBack = true; swipeBack = true;
shouldTriggerSwipeFeedback = false; shouldTriggerSwipeFeedback = false;
@ -133,9 +134,9 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
private void handleTouchActionUp(@NonNull RecyclerView recyclerView, private void handleTouchActionUp(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder,
float progress) float dx)
{ {
if (progress > SWIPE_SUCCESS_PROGRESS) { if (dx > SWIPE_SUCCESS_DX) {
onSwiped(viewHolder); onSwiped(viewHolder);
if (shouldTriggerSwipeFeedback) { if (shouldTriggerSwipeFeedback) {
vibrate(viewHolder.itemView.getContext()); vibrate(viewHolder.itemView.getContext());

View File

@ -12,17 +12,16 @@ import org.thoughtcrime.securesms.util.Util;
final class ConversationSwipeAnimationHelper { final class ConversationSwipeAnimationHelper {
public static final float PROGRESS_TRIGGER_POINT = 0.375f; static final float TRIGGER_DX = dpToPx(64);
static final float MAX_DX = dpToPx(96);
private static final float PROGRESS_SCALE_FACTOR = 2.0f;
private static final float SCALED_PROGRESS_TRIGGER_POINT = PROGRESS_TRIGGER_POINT * PROGRESS_SCALE_FACTOR;
private static final float REPLY_SCALE_OVERSHOOT = 1.8f; private static final float REPLY_SCALE_OVERSHOOT = 1.8f;
private static final float REPLY_SCALE_MAX = 1.2f; private static final float REPLY_SCALE_MAX = 1.2f;
private static final float REPLY_SCALE_MIN = 1f; private static final float REPLY_SCALE_MIN = 1f;
private static final long REPLY_SCALE_OVERSHOOT_DURATION = 200; private static final long REPLY_SCALE_OVERSHOOT_DURATION = 200;
private static final Interpolator BUBBLE_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(48)); private static final Interpolator BUBBLE_INTERPOLATOR = new BubblePositionInterpolator(0f, TRIGGER_DX, MAX_DX);
private static final Interpolator REPLY_ALPHA_INTERPOLATOR = new ClampingLinearInterpolator(0f, 1f, 1f / SCALED_PROGRESS_TRIGGER_POINT); private static final Interpolator REPLY_ALPHA_INTERPOLATOR = new ClampingLinearInterpolator(0f, 1f, 1f);
private static final Interpolator REPLY_TRANSITION_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(10)); private static final Interpolator REPLY_TRANSITION_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(10));
private static final Interpolator AVATAR_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(8)); private static final Interpolator AVATAR_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(8));
private static final Interpolator REPLY_SCALE_INTERPOLATOR = new ClampingLinearInterpolator(REPLY_SCALE_MIN, REPLY_SCALE_MAX); private static final Interpolator REPLY_SCALE_INTERPOLATOR = new ClampingLinearInterpolator(REPLY_SCALE_MIN, REPLY_SCALE_MAX);
@ -30,29 +29,30 @@ final class ConversationSwipeAnimationHelper {
private ConversationSwipeAnimationHelper() { private ConversationSwipeAnimationHelper() {
} }
public static void update(@NonNull ConversationItem conversationItem, float progress, float sign) { public static void update(@NonNull ConversationItem conversationItem, float dx, float sign) {
float scaledProgress = Math.min(1f, progress * PROGRESS_SCALE_FACTOR); float progress = dx / TRIGGER_DX;
updateBodyBubbleTransition(conversationItem.bodyBubble, scaledProgress, sign);
updateReplyIconTransition(conversationItem.reply, scaledProgress, sign); updateBodyBubbleTransition(conversationItem.bodyBubble, dx, sign);
updateContactPhotoHolderTransition(conversationItem.contactPhotoHolder, scaledProgress, sign); updateReplyIconTransition(conversationItem.reply, dx, progress, sign);
updateContactPhotoHolderTransition(conversationItem.contactPhotoHolder, progress, sign);
} }
public static void trigger(@NonNull ConversationItem conversationItem) { public static void trigger(@NonNull ConversationItem conversationItem) {
triggerReplyIcon(conversationItem.reply); triggerReplyIcon(conversationItem.reply);
} }
private static void updateBodyBubbleTransition(@NonNull View bodyBubble, float progress, float sign) { private static void updateBodyBubbleTransition(@NonNull View bodyBubble, float dx, float sign) {
bodyBubble.setTranslationX(BUBBLE_INTERPOLATOR.getInterpolation(progress) * sign); bodyBubble.setTranslationX(BUBBLE_INTERPOLATOR.getInterpolation(dx) * sign);
} }
private static void updateReplyIconTransition(@NonNull View replyIcon, float progress, float sign) { private static void updateReplyIconTransition(@NonNull View replyIcon, float dx, float progress, float sign) {
if (progress > 0.05f) { if (progress > 0.05f) {
replyIcon.setAlpha(REPLY_ALPHA_INTERPOLATOR.getInterpolation(progress)); replyIcon.setAlpha(REPLY_ALPHA_INTERPOLATOR.getInterpolation(progress));
} else replyIcon.setAlpha(0f); } else replyIcon.setAlpha(0f);
replyIcon.setTranslationX(REPLY_TRANSITION_INTERPOLATOR.getInterpolation(progress) * sign); replyIcon.setTranslationX(REPLY_TRANSITION_INTERPOLATOR.getInterpolation(progress) * sign);
if (progress < SCALED_PROGRESS_TRIGGER_POINT) { if (dx < TRIGGER_DX) {
float scale = REPLY_SCALE_INTERPOLATOR.getInterpolation(progress); float scale = REPLY_SCALE_INTERPOLATOR.getInterpolation(progress);
replyIcon.setScaleX(scale); replyIcon.setScaleX(scale);
replyIcon.setScaleY(scale); replyIcon.setScaleY(scale);
@ -81,6 +81,36 @@ final class ConversationSwipeAnimationHelper {
return (int) (dp * Resources.getSystem().getDisplayMetrics().density); return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
} }
private static final class BubblePositionInterpolator implements Interpolator {
private final float start;
private final float middle;
private final float end;
private BubblePositionInterpolator(float start, float middle, float end) {
this.start = start;
this.middle = middle;
this.end = end;
}
@Override
public float getInterpolation(float input) {
if (input < start) {
return start;
} else if (input < middle) {
return input;
} else {
float segmentLength = end - middle;
float segmentTraveled = input - middle;
float segmentCompletion = segmentTraveled / segmentLength;
float scaleDownFactor = middle / (input * 2);
float output = middle + (segmentLength * segmentCompletion * scaleDownFactor);
return Math.min(output, end);
}
}
}
private static final class ClampingLinearInterpolator implements Interpolator { private static final class ClampingLinearInterpolator implements Interpolator {
private final float slope; private final float slope;