diff --git a/api/current.xml b/api/current.xml
index 242737610381c..e7d5ff43f224e 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -238771,7 +238771,35 @@
>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index bb85894982442..6b41ce5cf0576 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -166,7 +166,7 @@ public class ViewConfiguration {
/**
* Max distance to overfling for edge effects
*/
- private static final int OVERFLING_DISTANCE = 4;
+ private static final int OVERFLING_DISTANCE = 12;
private final int mEdgeSlop;
private final int mFadingEdgeLength;
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index cd81e31ce8697..b9e59d617288f 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -17,9 +17,12 @@
package android.widget;
import android.content.Context;
-import android.graphics.Interpolator;
+import android.hardware.SensorManager;
+import android.util.FloatMath;
+import android.util.Log;
import android.view.ViewConfiguration;
import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
/**
* This class encapsulates scrolling with the ability to overshoot the bounds
@@ -27,65 +30,51 @@ import android.view.animation.AnimationUtils;
* {@link android.widget.Scroller} in most cases.
*/
public class OverScroller {
- int mMode;
+ private int mMode;
- private final MagneticOverScroller mScrollerX;
- private final MagneticOverScroller mScrollerY;
+ private final SplineOverScroller mScrollerX;
+ private final SplineOverScroller mScrollerY;
+
+ private final Interpolator mInterpolator;
- private float mDeceleration;
- private final float mPpi;
private final boolean mFlywheel;
- private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
- private static float ALPHA = 800; // pixels / seconds
- private static float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
- private static float END_TENSION = 1.0f - START_TENSION;
- private static final int NB_SAMPLES = 100;
- private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
- private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
-
private static final int DEFAULT_DURATION = 250;
private static final int SCROLL_MODE = 0;
private static final int FLING_MODE = 1;
- static {
- float x_min = 0.0f;
- float y_min = 0.0f;
- for (int i = 0; i < NB_SAMPLES; i++) {
- final float alpha = (float) i / NB_SAMPLES;
- {
- float x_max = 1.0f;
- float x, tx, coef;
- while (true) {
- x = x_min + (x_max - x_min) / 2.0f;
- coef = 3.0f * x * (1.0f - x);
- tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
- if (Math.abs(tx - alpha) < 1E-5) break;
- if (tx > alpha) x_max = x;
- else x_min = x;
- }
- SPLINE_POSITION[i] = coef + x * x * x;
- }
-
- {
- float y_max = 1.0f;
- float y, dy, coef;
- while (true) {
- y = y_min + (y_max - y_min) / 2.0f;
- coef = 3.0f * y * (1.0f - y);
- dy = coef + y * y * y;
- if (Math.abs(dy - alpha) < 1E-5) break;
- if (dy > alpha) y_max = y;
- else y_min = y;
- }
- SPLINE_TIME[i] = coef * ((1.0f - y) * START_TENSION + y * END_TENSION) + y * y * y;
- }
- }
- SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
+ /**
+ * Creates an OverScroller with a viscous fluid scroll interpolator.
+ * @param context
+ */
+ public OverScroller(Context context) {
+ this(context, null);
}
- public OverScroller(Context context) {
- this(context, null, 0.f, 0.f, true);
+ /**
+ * Creates an OverScroller with default edge bounce coefficients and flywheel enabled.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ */
+ public OverScroller(Context context, Interpolator interpolator) {
+ this(context, interpolator, SplineOverScroller.DEFAULT_BOUNCE_COEFFICIENT,
+ SplineOverScroller.DEFAULT_BOUNCE_COEFFICIENT);
+ }
+
+ /**
+ * Creates an OverScroller with flywheel enabled.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
+ * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
+ * means no bounce.
+ * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction.
+ */
+ public OverScroller(Context context, Interpolator interpolator,
+ float bounceCoefficientX, float bounceCoefficientY) {
+ this(context, interpolator, bounceCoefficientX, bounceCoefficientY, true);
}
/**
@@ -97,20 +86,21 @@ public class OverScroller {
* velocity which is preserved in the bounce when the horizontal edge is reached. A null value
* means no bounce.
* @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction.
+ * @param flywheel If true, successive fling motions will keep on increasing scroll speed.
*/
public OverScroller(Context context, Interpolator interpolator,
float bounceCoefficientX, float bounceCoefficientY, boolean flywheel) {
+ mInterpolator = interpolator;
mFlywheel = flywheel;
- mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
- mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
- mScrollerX = new MagneticOverScroller();
- mScrollerY = new MagneticOverScroller();
+ mScrollerX = new SplineOverScroller();
+ mScrollerY = new SplineOverScroller();
+
+ SplineOverScroller.initFromContext(context);
mScrollerX.setBounceCoefficient(bounceCoefficientX);
mScrollerY.setBounceCoefficient(bounceCoefficientY);
}
-
/**
* The amount of friction applied to flings. The default value
* is {@link ViewConfiguration#getScrollFriction}.
@@ -119,14 +109,8 @@ public class OverScroller {
* friction.
*/
public final void setFriction(float friction) {
- mDeceleration = computeDeceleration(friction);
- }
-
- private float computeDeceleration(float friction) {
- return 9.81f // g (m/s^2)
- * 39.37f // inch/meter
- * mPpi // pixels per inch
- * friction;
+ mScrollerX.setFriction(friction);
+ mScrollerY.setFriction(friction);
}
/**
@@ -178,7 +162,7 @@ public class OverScroller {
public float getCurrVelocity() {
float squaredNorm = mScrollerX.mCurrVelocity * mScrollerX.mCurrVelocity;
squaredNorm += mScrollerY.mCurrVelocity * mScrollerY.mCurrVelocity;
- return (float) Math.sqrt(squaredNorm);
+ return FloatMath.sqrt(squaredNorm);
}
/**
@@ -307,7 +291,11 @@ public class OverScroller {
if (elapsedTime < duration) {
float q = (float) (elapsedTime) / duration;
- q = Scroller.viscousFluid(q);
+ if (mInterpolator == null) {
+ q = Scroller.viscousFluid(q);
+ } else {
+ q = mInterpolator.getInterpolation(q);
+ }
mScrollerX.updateScroll(q);
mScrollerY.updateScroll(q);
@@ -496,9 +484,9 @@ public class OverScroller {
*/
public boolean isOverScrolled() {
return ((!mScrollerX.mFinished &&
- mScrollerX.mState != MagneticOverScroller.TO_EDGE) ||
+ mScrollerX.mState != SplineOverScroller.TO_EDGE) ||
(!mScrollerY.mFinished &&
- mScrollerY.mState != MagneticOverScroller.TO_EDGE));
+ mScrollerY.mState != SplineOverScroller.TO_EDGE));
}
/**
@@ -536,60 +524,126 @@ public class OverScroller {
Math.signum(yvel) == Math.signum(dy);
}
- class MagneticOverScroller {
+ static class SplineOverScroller {
// Initial position
- int mStart;
+ private int mStart;
// Current position
- int mCurrentPosition;
+ private int mCurrentPosition;
// Final position
- int mFinal;
+ private int mFinal;
// Initial velocity
- int mVelocity;
+ private int mVelocity;
// Current velocity
- float mCurrVelocity;
+ private float mCurrVelocity;
// Constant current deceleration
- float mDeceleration;
+ private float mDeceleration;
// Animation starting time, in system milliseconds
- long mStartTime;
+ private long mStartTime;
// Animation duration, in milliseconds
- int mDuration;
+ private int mDuration;
// Duration to complete spline component of animation
- int mSplineDuration;
+ private int mSplineDuration;
// Distance to travel along spline animation
- int mSplineDistance;
+ private int mSplineDistance;
// Whether the animation is currently in progress
- boolean mFinished;
+ private boolean mFinished;
+
+ // The allowed overshot distance before boundary is reached.
+ private int mOver;
+
+ // Fling friction
+ private float mFlingFriction = ViewConfiguration.getScrollFriction();
+
+ // Proportion of velocity preserved at the end of a bounce animation.
+ private float mBounceCoefficient = DEFAULT_BOUNCE_COEFFICIENT;
+
+ // Current state of the animation.
+ private int mState = TO_EDGE;
+
+ // Constant gravity value, used in the deceleration phase.
+ private static final float GRAVITY = 2000.0f;
+
+ // A device specific coefficient adjusted to physical values.
+ private static float PHYSICAL_COEF;
+
+ private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
+ private static final float INFLEXION = 0.3f; // Tension lines cross at (INFLEXION, 1)
+ private static final float START_TENSION = 0.7f;
+ private static final float END_TENSION = 0.8f;
+ private static final float P1 = START_TENSION * INFLEXION;
+ private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
+
+ private static final int NB_SAMPLES = 100;
+ private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
+ private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
private static final int TO_EDGE = 0;
private static final int TO_BOUNDARY = 1;
private static final int TO_BOUNCE = 2;
- private int mState = TO_EDGE;
-
- // The allowed overshot distance before boundary is reached.
- private int mOver;
-
// If the velocity is smaller than this value, no bounce is triggered
- // when the edge limits are reached (would result in a zero pixels
- // displacement anyway).
- private static final float MINIMUM_VELOCITY_FOR_BOUNCE = 140.0f; //Float.MAX_VALUE;//140.0f;
+ // when the edge limits are reached.
+ private static final float MINIMUM_VELOCITY_FOR_BOUNCE = Float.MAX_VALUE;//140.0f;
// Proportion of the velocity that is preserved when the edge is reached.
- private static final float DEFAULT_BOUNCE_COEFFICIENT = 0.36f;
+ private static final float DEFAULT_BOUNCE_COEFFICIENT = 0.16f;
- private float mBounceCoefficient = DEFAULT_BOUNCE_COEFFICIENT;
+ static {
+ float x_min = 0.0f;
+ float y_min = 0.0f;
+ for (int i = 0; i < NB_SAMPLES; i++) {
+ final float alpha = (float) i / NB_SAMPLES;
- MagneticOverScroller() {
+ float x_max = 1.0f;
+ float x, tx, coef;
+ while (true) {
+ x = x_min + (x_max - x_min) / 2.0f;
+ coef = 3.0f * x * (1.0f - x);
+ tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
+ if (Math.abs(tx - alpha) < 1E-5) break;
+ if (tx > alpha) x_max = x;
+ else x_min = x;
+ }
+ SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
+
+ float y_max = 1.0f;
+ float y, dy;
+ while (true) {
+ y = y_min + (y_max - y_min) / 2.0f;
+ coef = 3.0f * y * (1.0f - y);
+ dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
+ if (Math.abs(dy - alpha) < 1E-5) break;
+ if (dy > alpha) y_max = y;
+ else y_min = y;
+ }
+ SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
+ }
+ SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
+ }
+
+ static void initFromContext(Context context) {
+ final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
+ PHYSICAL_COEF = SensorManager.GRAVITY_EARTH // g (m/s^2)
+ * 39.37f // inch/meter
+ * ppi
+ * 0.84f; // look and feel tuning
+ }
+
+ void setFriction(float friction) {
+ mFlingFriction = friction;
+ }
+
+ SplineOverScroller() {
mFinished = true;
}
@@ -600,18 +654,18 @@ public class OverScroller {
/*
* Get a signed deceleration that will reduce the velocity.
*/
- float getDeceleration(int velocity) {
- return velocity > 0 ? -OverScroller.this.mDeceleration : OverScroller.this.mDeceleration;
+ static private float getDeceleration(int velocity) {
+ return velocity > 0 ? -GRAVITY : GRAVITY;
}
/*
* Modifies mDuration to the duration it takes to get from start to newFinal using the
* spline interpolation. The previous duration was needed to get to oldFinal.
*/
- void adjustDuration(int start, int oldFinal, int newFinal) {
+ private void adjustDuration(int start, int oldFinal, int newFinal) {
final int oldDistance = oldFinal - start;
final int newDistance = newFinal - start;
- final float x = (float) Math.abs((float) newDistance / oldDistance);
+ final float x = Math.abs((float) newDistance / oldDistance);
final int index = (int) (NB_SAMPLES * x);
if (index < NB_SAMPLES) {
final float x_inf = (float) index / NB_SAMPLES;
@@ -619,7 +673,6 @@ public class OverScroller {
final float t_inf = SPLINE_TIME[index];
final float t_sup = SPLINE_TIME[index + 1];
final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);
-
mDuration *= timeCoef;
}
}
@@ -696,7 +749,7 @@ public class OverScroller {
mCurrVelocity = mVelocity = velocity;
mDuration = mSplineDuration = 0;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
- mStart = start;
+ mCurrentPosition = mStart = start;
if (start > max || start < min) {
startAfterEdge(start, min, max, velocity);
@@ -707,10 +760,8 @@ public class OverScroller {
double totalDistance = 0.0;
if (velocity != 0) {
- final double l = Math.log(START_TENSION * Math.abs(velocity) / ALPHA);
- // Duration are expressed in milliseconds
- mDuration = mSplineDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
- totalDistance = (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
+ mDuration = mSplineDuration = getSplineFlingDuration(velocity);
+ totalDistance = getSplineFlingDistance(velocity);
}
mSplineDistance = (int) (totalDistance * Math.signum(velocity));
@@ -728,6 +779,23 @@ public class OverScroller {
}
}
+ private double getSplineDeceleration(int velocity) {
+ return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * PHYSICAL_COEF));
+ }
+
+ private double getSplineFlingDistance(int velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return mFlingFriction * PHYSICAL_COEF * Math.exp(DECELERATION_RATE / decelMinusOne * l);
+ }
+
+ /* Returns the duration, expressed in milliseconds */
+ private int getSplineFlingDuration(int velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return (int) (1000.0 * Math.exp(l / decelMinusOne));
+ }
+
private void fitOnBounceCurve(int start, int end, int velocity) {
// Simulate a bounce that started from edge
final float durationToApex = - velocity / mDeceleration;
@@ -748,6 +816,7 @@ public class OverScroller {
private void startAfterEdge(int start, int min, int max, int velocity) {
if (start > min && start < max) {
+ Log.e("OverScroller", "startAfterEdge called from a valid position");
mFinished = true;
return;
}
@@ -759,9 +828,7 @@ public class OverScroller {
// Will result in a bounce or a to_boundary depending on velocity.
startBounceAfterEdge(start, edge, velocity);
} else {
- final double l = Math.log(START_TENSION * Math.abs(velocity) / ALPHA);
- final double totalDistance =
- (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
+ final double totalDistance = getSplineFlingDistance(velocity);
if (totalDistance > Math.abs(overDistance)) {
fling(start, velocity, positive ? min : start, positive ? start : max, mOver);
} else {
@@ -771,11 +838,13 @@ public class OverScroller {
}
void notifyEdgeReached(int start, int end, int over) {
- mOver = over;
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
- // We were in fling/scroll mode before: current velocity is such that distance to edge
- // is increasing. Ensures that startAfterEdge will not start a new fling.
- startAfterEdge(start, end, end, (int) mCurrVelocity);
+ if (mState == TO_EDGE) {
+ mOver = over;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ // We were in fling/scroll mode before: current velocity is such that distance to
+ // edge is increasing. Ensures that startAfterEdge will not start a new fling.
+ startAfterEdge(start, end, end, (int) mCurrVelocity);
+ }
}
private void onEdgeReached() {