kopia lustrzana https://github.com/ryukoposting/Signal-Android
154 wiersze
5.9 KiB
Java
154 wiersze
5.9 KiB
Java
package org.thoughtcrime.securesms.conversation;
|
|
|
|
import android.animation.ValueAnimator;
|
|
import android.content.res.Resources;
|
|
import android.view.View;
|
|
import android.view.animation.Interpolator;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
|
|
|
final class ConversationSwipeAnimationHelper {
|
|
|
|
static final float TRIGGER_DX = dpToPx(64);
|
|
static final float MAX_DX = dpToPx(96);
|
|
|
|
private static final float REPLY_SCALE_OVERSHOOT = 1.8f;
|
|
private static final float REPLY_SCALE_MAX = 1.2f;
|
|
private static final float REPLY_SCALE_MIN = 1f;
|
|
private static final long REPLY_SCALE_OVERSHOOT_DURATION = 200;
|
|
|
|
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);
|
|
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 REPLY_SCALE_INTERPOLATOR = new ClampingLinearInterpolator(REPLY_SCALE_MIN, REPLY_SCALE_MAX);
|
|
private static final Interpolator QUOTED_ALPHA_INTERPOLATOR = new ClampingLinearInterpolator(1f, 0f, 3f);
|
|
|
|
private ConversationSwipeAnimationHelper() {
|
|
}
|
|
|
|
public static void update(@NonNull ConversationItem conversationItem, float dx, float sign) {
|
|
float progress = dx / TRIGGER_DX;
|
|
|
|
updateBodyBubbleTransition(conversationItem.bodyBubble, dx, sign);
|
|
updateReactionsTransition(conversationItem.reactionsView, dx, sign);
|
|
updateQuotedIndicatorTransition(conversationItem.quotedIndicator, dx, progress, sign);
|
|
updateReplyIconTransition(conversationItem.reply, dx, progress, sign);
|
|
updateContactPhotoHolderTransition(conversationItem.contactPhotoHolder, progress, sign);
|
|
updateContactPhotoHolderTransition(conversationItem.badgeImageView, progress, sign);
|
|
}
|
|
|
|
public static void trigger(@NonNull ConversationItem conversationItem) {
|
|
triggerReplyIcon(conversationItem.reply);
|
|
}
|
|
|
|
private static void updateBodyBubbleTransition(@NonNull View bodyBubble, float dx, float sign) {
|
|
bodyBubble.setTranslationX(BUBBLE_INTERPOLATOR.getInterpolation(dx) * sign);
|
|
}
|
|
|
|
private static void updateReactionsTransition(@NonNull View reactionsContainer, float dx, float sign) {
|
|
reactionsContainer.setTranslationX(BUBBLE_INTERPOLATOR.getInterpolation(dx) * sign);
|
|
}
|
|
|
|
private static void updateQuotedIndicatorTransition(@Nullable View quotedIndicator, float dx, float progress, float sign) {
|
|
if (quotedIndicator != null) {
|
|
quotedIndicator.setTranslationX(BUBBLE_INTERPOLATOR.getInterpolation(dx) * sign);
|
|
quotedIndicator.setAlpha(QUOTED_ALPHA_INTERPOLATOR.getInterpolation(progress) * sign);
|
|
}
|
|
}
|
|
|
|
private static void updateReplyIconTransition(@NonNull View replyIcon, float dx, float progress, float sign) {
|
|
if (progress > 0.05f) {
|
|
replyIcon.setAlpha(REPLY_ALPHA_INTERPOLATOR.getInterpolation(progress));
|
|
} else replyIcon.setAlpha(0f);
|
|
|
|
replyIcon.setTranslationX(REPLY_TRANSITION_INTERPOLATOR.getInterpolation(progress) * sign);
|
|
|
|
if (dx < TRIGGER_DX) {
|
|
float scale = REPLY_SCALE_INTERPOLATOR.getInterpolation(progress);
|
|
replyIcon.setScaleX(scale);
|
|
replyIcon.setScaleY(scale);
|
|
}
|
|
}
|
|
|
|
private static void updateContactPhotoHolderTransition(@Nullable View contactPhotoHolder,
|
|
float progress,
|
|
float sign)
|
|
{
|
|
if (contactPhotoHolder == null) return;
|
|
contactPhotoHolder.setTranslationX(AVATAR_INTERPOLATOR.getInterpolation(progress) * sign);
|
|
}
|
|
|
|
private static void triggerReplyIcon(@NonNull View replyIcon) {
|
|
ValueAnimator animator = ValueAnimator.ofFloat(REPLY_SCALE_MAX, REPLY_SCALE_OVERSHOOT, REPLY_SCALE_MAX);
|
|
animator.setDuration(REPLY_SCALE_OVERSHOOT_DURATION);
|
|
animator.addUpdateListener(animation -> {
|
|
replyIcon.setScaleX((float) animation.getAnimatedValue());
|
|
replyIcon.setScaleY((float) animation.getAnimatedValue());
|
|
});
|
|
animator.start();
|
|
}
|
|
|
|
private static int dpToPx(int dp) {
|
|
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 final float slope;
|
|
private final float yIntercept;
|
|
private final float max;
|
|
private final float min;
|
|
|
|
ClampingLinearInterpolator(float start, float end) {
|
|
this(start, end, 1.0f);
|
|
}
|
|
|
|
ClampingLinearInterpolator(float start, float end, float scale) {
|
|
slope = (end - start) * scale;
|
|
yIntercept = start;
|
|
max = Math.max(start, end);
|
|
min = Math.min(start, end);
|
|
}
|
|
|
|
@Override
|
|
public float getInterpolation(float input) {
|
|
return Util.clamp(slope * input + yIntercept, min, max);
|
|
}
|
|
}
|
|
|
|
}
|