summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
authorGeorge Mount <mount@google.com>2021-03-04 23:45:44 +0000
committerGeorge Mount <mount@google.com>2021-03-09 16:47:01 +0000
commit61324dbfccf5166f355980f59a2fb950126c8410 (patch)
treec407303458a19dd0209ecb29b0ad6dc286ab4c95 /core/java
parentaf1e8b4ea981a81691c3259209efff8a8bd49898 (diff)
Use spring animation for stretch overscroll
Bug: 171228096 Use the spring animation from the prototype in EdgeEffect. Test: manual testing for fling and recede Change-Id: I7eec03a98f5d26153512ba402d6bb5bc5dc70ffb
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/widget/EdgeEffect.java139
1 files changed, 102 insertions, 37 deletions
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index dc42ad583543..e74b071aeda7 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -76,6 +76,28 @@ public class EdgeEffect {
*/
public static final int TYPE_STRETCH = 1;
+ /**
+ * The velocity threshold before the spring animation is considered settled.
+ * The idea here is that velocity should be less than 1 pixel per frame (~16ms).
+ */
+ private static final double VELOCITY_THRESHOLD = 1.0 / 0.016;
+
+ /**
+ * The value threshold before the spring animation is considered close enough to
+ * the destination to be settled. This should be around 1 pixel.
+ */
+ private static final double VALUE_THRESHOLD = 1;
+
+ /**
+ * The natural frequency of the stretch spring.
+ */
+ private static final double NATURAL_FREQUENCY = 14.4222;
+
+ /**
+ * The damping ratio of the stretch spring.
+ */
+ private static final double DAMPING_RATIO = 0.875;
+
/** @hide */
@IntDef({TYPE_GLOW, TYPE_STRETCH})
@Retention(RetentionPolicy.SOURCE)
@@ -119,13 +141,12 @@ public class EdgeEffect {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private float mGlowScaleY;
private float mDistance;
+ private float mVelocity; // only for stretch animations
private float mGlowAlphaStart;
private float mGlowAlphaFinish;
private float mGlowScaleYStart;
private float mGlowScaleYFinish;
- private float mDistanceStart;
- private float mDistanceFinish;
private long mStartTime;
private float mDuration;
@@ -239,6 +260,8 @@ public class EdgeEffect {
*/
public void finish() {
mState = STATE_IDLE;
+ mDistance = 0;
+ mVelocity = 0;
}
/**
@@ -293,7 +316,8 @@ public class EdgeEffect {
mDuration = PULL_TIME;
mPullDistance += deltaDistance;
- mDistanceStart = mDistanceFinish = mDistance = Math.max(0f, mPullDistance);
+ mDistance = Math.max(0f, mPullDistance);
+ mVelocity = 0;
final float absdd = Math.abs(deltaDistance);
mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
@@ -387,11 +411,10 @@ public class EdgeEffect {
mState = STATE_RECEDE;
mGlowAlphaStart = mGlowAlpha;
mGlowScaleYStart = mGlowScaleY;
- mDistanceStart = mDistance;
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
- mDistanceFinish = 0.f;
+ mVelocity = 0.f;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mDuration = RECEDE_TIME;
@@ -408,30 +431,36 @@ public class EdgeEffect {
* @param velocity Velocity at impact in pixels per second.
*/
public void onAbsorb(int velocity) {
- mState = STATE_ABSORB;
- velocity = Math.min(Math.max(MIN_VELOCITY, Math.abs(velocity)), MAX_VELOCITY);
-
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
- mDuration = 0.15f + (velocity * 0.02f);
-
- // The glow depends more on the velocity, and therefore starts out
- // nearly invisible.
- mGlowAlphaStart = GLOW_ALPHA_START;
- mGlowScaleYStart = Math.max(mGlowScaleY, 0.f);
- mDistanceStart = mDistance;
-
- // Growth for the size of the glow should be quadratic to properly
- // respond
- // to a user's scrolling speed. The faster the scrolling speed, the more
- // intense the effect should be for both the size and the saturation.
- mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f) / 2, 1.f);
- // Alpha should change for the glow as well as size.
- mGlowAlphaFinish = Math.max(
- mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
- mTargetDisplacement = 0.5f;
-
- // Use glow values to estimate the absorption for stretch distance.
- mDistanceFinish = calculateDistanceFromGlowValues(mGlowScaleYFinish, mGlowAlphaFinish);
+ if (mEdgeEffectType == TYPE_STRETCH) {
+ mState = STATE_RECEDE;
+ mVelocity = velocity / mHeight;
+ mDistance = 0;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ } else {
+ mState = STATE_ABSORB;
+ mVelocity = 0;
+ velocity = Math.min(Math.max(MIN_VELOCITY, Math.abs(velocity)), MAX_VELOCITY);
+
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = 0.15f + (velocity * 0.02f);
+
+ // The glow depends more on the velocity, and therefore starts out
+ // nearly invisible.
+ mGlowAlphaStart = GLOW_ALPHA_START;
+ mGlowScaleYStart = Math.max(mGlowScaleY, 0.f);
+
+ // Growth for the size of the glow should be quadratic to properly
+ // respond
+ // to a user's scrolling speed. The faster the scrolling speed, the more
+ // intense the effect should be for both the size and the saturation.
+ mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f) / 2,
+ 1.f);
+ // Alpha should change for the glow as well as size.
+ mGlowAlphaFinish = Math.max(
+ mGlowAlphaStart,
+ Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
+ mTargetDisplacement = 0.5f;
+ }
}
/**
@@ -539,8 +568,8 @@ public class EdgeEffect {
canvas.drawCircle(centerX, centerY, mRadius, mPaint);
canvas.restoreToCount(count);
} else if (canvas instanceof RecordingCanvas) {
- if (mState != STATE_PULL) {
- update();
+ if (mState == STATE_RECEDE) {
+ updateSpring();
}
RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
if (mTmpMatrix == null) {
@@ -597,7 +626,7 @@ public class EdgeEffect {
}
boolean oneLastFrame = false;
- if (mState == STATE_RECEDE && mDistance == 0) {
+ if (mState == STATE_RECEDE && mDistance == 0 && mVelocity == 0) {
mState = STATE_IDLE;
oneLastFrame = true;
}
@@ -634,7 +663,7 @@ public class EdgeEffect {
mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
- mDistance = mDistanceStart + (mDistanceFinish - mDistanceStart) * interp;
+ mDistance = calculateDistanceFromGlowValues(mGlowScaleY, mGlowAlpha);
mDisplacement = (mDisplacement + mTargetDisplacement) / 2;
if (t >= 1.f - EPSILON) {
@@ -646,12 +675,10 @@ public class EdgeEffect {
mGlowAlphaStart = mGlowAlpha;
mGlowScaleYStart = mGlowScaleY;
- mDistanceStart = mDistance;
// After absorb, the glow should fade to nothing.
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
- mDistanceFinish = 0.f;
break;
case STATE_PULL:
mState = STATE_PULL_DECAY;
@@ -660,12 +687,10 @@ public class EdgeEffect {
mGlowAlphaStart = mGlowAlpha;
mGlowScaleYStart = mGlowScaleY;
- mDistanceStart = mDistance;
// After pull, the glow should fade to nothing.
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
- mDistanceFinish = 0.f;
break;
case STATE_PULL_DECAY:
mState = STATE_RECEDE;
@@ -677,6 +702,35 @@ public class EdgeEffect {
}
}
+ private void updateSpring() {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final float deltaT = (time - mStartTime) / 1000f; // Convert from millis to seconds
+ if (deltaT < 0.001f) {
+ return; // Must have at least 1 ms difference
+ }
+ final double mDampedFreq = NATURAL_FREQUENCY * Math.sqrt(1 - DAMPING_RATIO * DAMPING_RATIO);
+
+ // We're always underdamped, so we can use only those equations:
+ double cosCoeff = mDistance;
+ double sinCoeff = (1 / mDampedFreq) * (DAMPING_RATIO * NATURAL_FREQUENCY
+ * mDistance + mVelocity);
+ double distance = Math.pow(Math.E, -DAMPING_RATIO * NATURAL_FREQUENCY * deltaT)
+ * (cosCoeff * Math.cos(mDampedFreq * deltaT)
+ + sinCoeff * Math.sin(mDampedFreq * deltaT));
+ double velocity = distance * (-NATURAL_FREQUENCY) * DAMPING_RATIO
+ + Math.pow(Math.E, -DAMPING_RATIO * NATURAL_FREQUENCY * deltaT)
+ * (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT)
+ + mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
+ mDistance = (float) distance;
+ mVelocity = (float) velocity;
+ mStartTime = time;
+ if (isAtEquilibrium()) {
+ mState = STATE_IDLE;
+ mDistance = 0;
+ mVelocity = 0;
+ }
+ }
+
/**
* @return The estimated pull distance as calculated from mGlowScaleY.
*/
@@ -692,4 +746,15 @@ public class EdgeEffect {
}
return alpha / PULL_DISTANCE_ALPHA_GLOW_FACTOR;
}
+
+ /**
+ * @return true if the spring used for calculating the stretch animation is
+ * considered at rest or false if it is still animating.
+ */
+ private boolean isAtEquilibrium() {
+ double velocity = mVelocity * mHeight; // in pixels/second
+ double displacement = mDistance * mHeight; // in pixels
+ return Math.abs(velocity) < VELOCITY_THRESHOLD
+ && Math.abs(displacement) < VALUE_THRESHOLD;
+ }
}