Merge "Sync fling physics between Scroller/OverScroller" into jb-mr1-dev

This commit is contained in:
Adam Powell
2012-08-09 17:22:29 -07:00
committed by Android (Google) Code Review
2 changed files with 88 additions and 42 deletions

View File

@@ -72,10 +72,8 @@ public class OverScroller {
public OverScroller(Context context, Interpolator interpolator, boolean flywheel) { public OverScroller(Context context, Interpolator interpolator, boolean flywheel) {
mInterpolator = interpolator; mInterpolator = interpolator;
mFlywheel = flywheel; mFlywheel = flywheel;
mScrollerX = new SplineOverScroller(); mScrollerX = new SplineOverScroller(context);
mScrollerY = new SplineOverScroller(); mScrollerY = new SplineOverScroller(context);
SplineOverScroller.initFromContext(context);
} }
/** /**
@@ -585,8 +583,8 @@ public class OverScroller {
// Constant gravity value, used in the deceleration phase. // Constant gravity value, used in the deceleration phase.
private static final float GRAVITY = 2000.0f; private static final float GRAVITY = 2000.0f;
// A device specific coefficient adjusted to physical values. // A context-specific coefficient adjusted to physical values.
private static float PHYSICAL_COEF; private float mPhysicalCoeff;
private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9)); private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1) private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
@@ -636,20 +634,17 @@ public class OverScroller {
SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f; 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) { void setFriction(float friction) {
mFlingFriction = friction; mFlingFriction = friction;
} }
SplineOverScroller() { SplineOverScroller(Context context) {
mFinished = true; mFinished = true;
final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* ppi
* 0.84f; // look and feel tuning
} }
void updateScroll(float q) { void updateScroll(float q) {
@@ -785,13 +780,13 @@ public class OverScroller {
} }
private double getSplineDeceleration(int velocity) { private double getSplineDeceleration(int velocity) {
return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * PHYSICAL_COEF)); return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
} }
private double getSplineFlingDistance(int velocity) { private double getSplineFlingDistance(int velocity) {
final double l = getSplineDeceleration(velocity); final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0; final double decelMinusOne = DECELERATION_RATE - 1.0;
return mFlingFriction * PHYSICAL_COEF * Math.exp(DECELERATION_RATE / decelMinusOne * l); return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
} }
/* Returns the duration, expressed in milliseconds */ /* Returns the duration, expressed in milliseconds */

View File

@@ -57,45 +57,70 @@ public class Scroller {
private boolean mFlywheel; private boolean mFlywheel;
private float mVelocity; private float mVelocity;
private float mCurrVelocity;
private int mDistance;
private float mFlingFriction = ViewConfiguration.getScrollFriction();
private static final int DEFAULT_DURATION = 250; private static final int DEFAULT_DURATION = 250;
private static final int SCROLL_MODE = 0; private static final int SCROLL_MODE = 0;
private static final int FLING_MODE = 1; private static final int FLING_MODE = 1;
private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9)); private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
private static float ALPHA = 800; // pixels / seconds private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
private static float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance) private static final float START_TENSION = 0.5f;
private static float END_TENSION = 1.0f - START_TENSION; private static final float END_TENSION = 1.0f;
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 int NB_SAMPLES = 100;
private static final float[] SPLINE = new float[NB_SAMPLES + 1]; private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
private float mDeceleration; private float mDeceleration;
private final float mPpi; private final float mPpi;
// A context-specific coefficient adjusted to physical values.
private float mPhysicalCoeff;
static { static {
float x_min = 0.0f; float x_min = 0.0f;
for (int i = 0; i <= NB_SAMPLES; i++) { float y_min = 0.0f;
final float t = (float) i / NB_SAMPLES; for (int i = 0; i < NB_SAMPLES; i++) {
final float alpha = (float) i / NB_SAMPLES;
float x_max = 1.0f; float x_max = 1.0f;
float x, tx, coef; float x, tx, coef;
while (true) { while (true) {
x = x_min + (x_max - x_min) / 2.0f; x = x_min + (x_max - x_min) / 2.0f;
coef = 3.0f * x * (1.0f - x); coef = 3.0f * x * (1.0f - x);
tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x; tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
if (Math.abs(tx - t) < 1E-5) break; if (Math.abs(tx - alpha) < 1E-5) break;
if (tx > t) x_max = x; if (tx > alpha) x_max = x;
else x_min = x; else x_min = x;
} }
final float d = coef + x * x * x; SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
SPLINE[i] = d;
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[NB_SAMPLES] = 1.0f; SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
// This controls the viscous fluid effect (how much of it) // This controls the viscous fluid effect (how much of it)
sViscousFluidScale = 8.0f; sViscousFluidScale = 8.0f;
// must be set to 1.0 (used in viscousFluid()) // must be set to 1.0 (used in viscousFluid())
sViscousFluidNormalize = 1.0f; sViscousFluidNormalize = 1.0f;
sViscousFluidNormalize = 1.0f / viscousFluid(1.0f); sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
} }
private static float sViscousFluidScale; private static float sViscousFluidScale;
@@ -129,6 +154,8 @@ public class Scroller {
mPpi = context.getResources().getDisplayMetrics().density * 160.0f; mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction()); mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
mFlywheel = flywheel; mFlywheel = flywheel;
mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
} }
/** /**
@@ -140,6 +167,7 @@ public class Scroller {
*/ */
public final void setFriction(float friction) { public final void setFriction(float friction) {
mDeceleration = computeDeceleration(friction); mDeceleration = computeDeceleration(friction);
mFlingFriction = friction;
} }
private float computeDeceleration(float friction) { private float computeDeceleration(float friction) {
@@ -202,7 +230,8 @@ public class Scroller {
* negative. * negative.
*/ */
public float getCurrVelocity() { public float getCurrVelocity() {
return mVelocity - mDeceleration * timePassed() / 2000.0f; return mMode == FLING_MODE ?
mCurrVelocity : mVelocity - mDeceleration * timePassed() / 2000.0f;
} }
/** /**
@@ -269,11 +298,18 @@ public class Scroller {
case FLING_MODE: case FLING_MODE:
final float t = (float) timePassed / mDuration; final float t = (float) timePassed / mDuration;
final int index = (int) (NB_SAMPLES * t); final int index = (int) (NB_SAMPLES * t);
final float t_inf = (float) index / NB_SAMPLES; float distanceCoef = 1.f;
final float t_sup = (float) (index + 1) / NB_SAMPLES; float velocityCoef = 0.f;
final float d_inf = SPLINE[index]; if (index < NB_SAMPLES) {
final float d_sup = SPLINE[index + 1]; final float t_inf = (float) index / NB_SAMPLES;
final float distanceCoef = d_inf + (t - t_inf) / (t_sup - t_inf) * (d_sup - d_inf); final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE_POSITION[index];
final float d_sup = SPLINE_POSITION[index + 1];
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
distanceCoef = d_inf + (t - t_inf) * velocityCoef;
}
mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX)); mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
// Pin to mMinX <= mCurrX <= mMaxX // Pin to mMinX <= mCurrX <= mMaxX
@@ -392,8 +428,7 @@ public class Scroller {
float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY); float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
mVelocity = velocity; mVelocity = velocity;
final double l = Math.log(START_TENSION * velocity / ALPHA); mDuration = getSplineFlingDuration(velocity);
mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
mStartTime = AnimationUtils.currentAnimationTimeMillis(); mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX; mStartX = startX;
mStartY = startY; mStartY = startY;
@@ -401,25 +436,41 @@ public class Scroller {
float coeffX = velocity == 0 ? 1.0f : velocityX / velocity; float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
float coeffY = velocity == 0 ? 1.0f : velocityY / velocity; float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
int totalDistance = double totalDistance = getSplineFlingDistance(velocity);
(int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l)); mDistance = (int) (totalDistance * Math.signum(velocity));
mMinX = minX; mMinX = minX;
mMaxX = maxX; mMaxX = maxX;
mMinY = minY; mMinY = minY;
mMaxY = maxY; mMaxY = maxY;
mFinalX = startX + Math.round(totalDistance * coeffX); mFinalX = startX + (int) Math.round(totalDistance * coeffX);
// Pin to mMinX <= mFinalX <= mMaxX // Pin to mMinX <= mFinalX <= mMaxX
mFinalX = Math.min(mFinalX, mMaxX); mFinalX = Math.min(mFinalX, mMaxX);
mFinalX = Math.max(mFinalX, mMinX); mFinalX = Math.max(mFinalX, mMinX);
mFinalY = startY + Math.round(totalDistance * coeffY); mFinalY = startY + (int) Math.round(totalDistance * coeffY);
// Pin to mMinY <= mFinalY <= mMaxY // Pin to mMinY <= mFinalY <= mMaxY
mFinalY = Math.min(mFinalY, mMaxY); mFinalY = Math.min(mFinalY, mMaxY);
mFinalY = Math.max(mFinalY, mMinY); mFinalY = Math.max(mFinalY, mMinY);
} }
private double getSplineDeceleration(float velocity) {
return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
}
private int getSplineFlingDuration(float velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return (int) (1000.0 * Math.exp(l / decelMinusOne));
}
private double getSplineFlingDistance(float velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
}
static float viscousFluid(float x) static float viscousFluid(float x)
{ {
x *= sViscousFluidScale; x *= sViscousFluidScale;