summaryrefslogtreecommitdiffstats
path: root/src/com/android/contacts/widget
diff options
context:
space:
mode:
authorBrian Attwell <brianattwell@google.com>2014-06-18 11:58:23 -0700
committerBrian Attwell <brianattwell@google.com>2014-06-18 16:02:32 -0700
commitb442dc78781e5b9edc9425342525b573b048be13 (patch)
tree455a8222909f7558c15f1d5f2c2314f23a1103e9 /src/com/android/contacts/widget
parent5e0d0b9d2efe3c0a4a0172c590cfef94ec841146 (diff)
downloadpackages_apps_Contacts-b442dc78781e5b9edc9425342525b573b048be13.tar.gz
packages_apps_Contacts-b442dc78781e5b9edc9425342525b573b048be13.tar.bz2
packages_apps_Contacts-b442dc78781e5b9edc9425342525b573b048be13.zip
snapToBottom() uses non janky interpolator
Use an interpolator that starts with the velocity of the current scroll. This prevents a point of discontinuity. UX complained about the previous interpolator. Change-Id: I3b02f64446050197c14aebc9235d8a7ab1c60107
Diffstat (limited to 'src/com/android/contacts/widget')
-rw-r--r--src/com/android/contacts/widget/MultiShrinkScroller.java84
1 files changed, 74 insertions, 10 deletions
diff --git a/src/com/android/contacts/widget/MultiShrinkScroller.java b/src/com/android/contacts/widget/MultiShrinkScroller.java
index 53179f5b7..1a0438c88 100644
--- a/src/com/android/contacts/widget/MultiShrinkScroller.java
+++ b/src/com/android/contacts/widget/MultiShrinkScroller.java
@@ -6,19 +6,22 @@ import com.android.contacts.util.SchedulingUtils;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.hardware.display.DisplayManagerGlobal;
import android.util.AttributeSet;
+import android.view.Display;
+import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewConfiguration;
-import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.EdgeEffect;
import android.widget.ImageView;
@@ -46,6 +49,11 @@ public class MultiShrinkScroller extends LinearLayout {
*/
private static final int PIXELS_PER_SECOND = 1000;
+ /**
+ * Length of the acceleration animations. This value was taken from ValueAnimator.java.
+ */
+ private static final int EXIT_FLING_ANIMATION_DURATION_MS = 300;
+
private float[] mLastEventPosition = { 0, 0 };
private VelocityTracker mVelocityTracker;
private boolean mIsBeingDragged = false;
@@ -80,20 +88,22 @@ public class MultiShrinkScroller extends LinearLayout {
void onExitFullscreen();
}
- private final AnimatorListener mHeaderExpandAnimationListener = new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {}
-
+ private final AnimatorListener mHeaderExpandAnimationListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mPhotoView.setClickable(true);
}
+ };
+ private final AnimatorListener mSnapToBottomListener = new AnimatorListenerAdapter() {
@Override
- public void onAnimationCancel(Animator animation) {}
-
- @Override
- public void onAnimationRepeat(Animator animation) {}
+ public void onAnimationEnd(Animator animation) {
+ if (getScrollUntilOffBottom() > 0 && mListener != null) {
+ // Due to a rounding error, after the animation finished we haven't fully scrolled
+ // off the screen. Lie to the listener: tell it that we did scroll off the screen.
+ mListener.onScrolledOffBottom();
+ }
+ }
};
/**
@@ -354,11 +364,16 @@ public class MultiShrinkScroller extends LinearLayout {
*/
private void snapToBottom(int flingDelta) {
if (-getScroll_ignoreOversizedHeader() - flingDelta > 0) {
+ final Interpolator interpolator = new AcceleratingFlingInterpolator(
+ EXIT_FLING_ANIMATION_DURATION_MS, getCurrentVelocity(),
+ getScrollUntilOffBottom());
mScroller.forceFinished(true);
ObjectAnimator translateAnimation = ObjectAnimator.ofInt(this, "scroll",
getScroll() - getScrollUntilOffBottom());
translateAnimation.setRepeatCount(0);
- translateAnimation.setInterpolator(new AccelerateInterpolator());
+ translateAnimation.setInterpolator(interpolator);
+ translateAnimation.setDuration(EXIT_FLING_ANIMATION_DURATION_MS);
+ translateAnimation.addListener(mSnapToBottomListener);
translateAnimation.start();
}
}
@@ -616,4 +631,53 @@ public class MultiShrinkScroller extends LinearLayout {
mScroller.startScroll(0, getScroll(), 0, delta);
invalidate();
}
+
+ /**
+ * Interpolator that enforces a specific starting velocity. This is useful to avoid a
+ * discontinuity between dragging speed and flinging speed.
+ *
+ * Similar to a {@link android.view.animation.AccelerateInterpolator} in the sense that
+ * getInterpolation() is a quadratic function.
+ */
+ private static class AcceleratingFlingInterpolator implements Interpolator {
+
+ private final float mStartingSpeedPixelsPerFrame;
+ private final float mDurationMs;
+ private final int mPixelsDelta;
+ private final float mNumberFrames;
+
+ public AcceleratingFlingInterpolator(int durationMs, float startingSpeedPixelsPerSecond,
+ int pixelsDelta) {
+ mStartingSpeedPixelsPerFrame = startingSpeedPixelsPerSecond / getRefreshRate();
+ mDurationMs = durationMs;
+ mPixelsDelta = pixelsDelta;
+ mNumberFrames = mDurationMs / getFrameIntervalMs();
+ }
+
+ @Override
+ public float getInterpolation(float input) {
+ final float animationIntervalNumber = mNumberFrames * input;
+ final float linearDelta = (animationIntervalNumber * mStartingSpeedPixelsPerFrame)
+ / mPixelsDelta;
+ // Add the results of a linear interpolator (with the initial speed) with the
+ // results of a AccelerateInterpolator.
+ if (mStartingSpeedPixelsPerFrame > 0) {
+ return Math.min(input * input + linearDelta, 1);
+ } else {
+ // Initial fling was in the wrong direction, make sure that the quadratic component
+ // grows faster in order to make up for this.
+ return Math.min(input * (input - linearDelta) + linearDelta, 1);
+ }
+ }
+
+ private float getRefreshRate() {
+ DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
+ Display.DEFAULT_DISPLAY);
+ return di.refreshRate;
+ }
+
+ public long getFrameIntervalMs() {
+ return (long)(1000 / getRefreshRate());
+ }
+ }
}