Implement bounded ripple animation

Bug: 19431322
Change-Id: I5dc1a28d8675cc6fb036b815d6227113c3f1aa4b
This commit is contained in:
Alan Viverette
2015-03-02 16:06:10 -08:00
parent 53776a2b3c
commit 6a67db4138
3 changed files with 144 additions and 59 deletions

View File

@@ -69,7 +69,6 @@ abstract class RippleComponent {
mDensity = density;
onSetup();
onTargetRadiusChanged(mTargetRadius);
}
@@ -82,7 +81,10 @@ abstract class RippleComponent {
cancel();
mSoftwareAnimator = createSoftwareEnter(fast);
mSoftwareAnimator.start();
if (mSoftwareAnimator != null) {
mSoftwareAnimator.start();
}
}
/**
@@ -250,14 +252,6 @@ abstract class RippleComponent {
// Stub.
}
/**
* Called during ripple setup, which occurs before the first enter
* animation.
*/
protected void onSetup() {
// Stub.
}
protected abstract Animator createSoftwareEnter(boolean fast);
protected abstract Animator createSoftwareExit();

View File

@@ -564,7 +564,9 @@ public class RippleDrawable extends LayerDrawable {
x = mHotspotBounds.exactCenterX();
y = mHotspotBounds.exactCenterY();
}
mRipple = new RippleForeground(this, mHotspotBounds, x, y);
final boolean isBounded = !isProjected();
mRipple = new RippleForeground(this, mHotspotBounds, x, y, isBounded);
}
mRipple.setup(mState.mMaxRadius, mDensity);

View File

@@ -44,9 +44,16 @@ class RippleForeground extends RippleComponent {
private static final float WAVE_TOUCH_UP_ACCELERATION = 3400;
private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;
// Bounded ripple animation properties.
private static final int BOUNDED_ORIGIN_EXIT_DURATION = 300;
private static final int BOUNDED_RADIUS_EXIT_DURATION = 800;
private static final int BOUNDED_OPACITY_EXIT_DURATION = 400;
private static final float MAX_BOUNDED_RADIUS = 350;
private static final int RIPPLE_ENTER_DELAY = 80;
private static final int OPACITY_ENTER_DURATION_FAST = 120;
// Parent-relative values for starting position.
private float mStartingX;
private float mStartingY;
private float mClampedStartingX;
@@ -58,30 +65,41 @@ class RippleForeground extends RippleComponent {
private CanvasProperty<Float> mPropX;
private CanvasProperty<Float> mPropY;
// Target values for tween animations.
private float mTargetX = 0;
private float mTargetY = 0;
/** Ripple target radius used when bounded. Not used for clamping. */
private float mBoundedRadius = 0;
// Software rendering properties.
private float mOpacity = 1;
private float mOuterX;
private float mOuterY;
// Values used to tween between the start and end positions.
private float mTweenRadius = 0;
private float mTweenX = 0;
private float mTweenY = 0;
/** Whether this ripple is bounded. */
private boolean mIsBounded;
/** Whether this ripple has finished its exit animation. */
private boolean mHasFinishedExit;
public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY) {
public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
boolean isBounded) {
super(owner, bounds);
mIsBounded = isBounded;
mStartingX = startingX;
mStartingY = startingY;
}
@Override
public void onSetup() {
mOuterX = 0;
mOuterY = 0;
if (isBounded) {
mBoundedRadius = MAX_BOUNDED_RADIUS * 0.9f
+ (float) (MAX_BOUNDED_RADIUS * Math.random() * 0.1);
} else {
mBoundedRadius = 0;
}
}
@Override
@@ -95,12 +113,10 @@ class RippleForeground extends RippleComponent {
final int origAlpha = p.getAlpha();
final int alpha = (int) (origAlpha * mOpacity + 0.5f);
final float radius = MathUtils.lerp(0, mTargetRadius, mTweenRadius);
final float radius = getCurrentRadius();
if (alpha > 0 && radius > 0) {
final float x = MathUtils.lerp(
mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
final float y = MathUtils.lerp(
mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
final float x = getCurrentX();
final float y = getCurrentY();
p.setAlpha(alpha);
c.drawCircle(x, y, radius, p);
p.setAlpha(origAlpha);
@@ -120,8 +136,8 @@ class RippleForeground extends RippleComponent {
* Returns the maximum bounds of the ripple relative to the ripple center.
*/
public void getBounds(Rect bounds) {
final int outerX = (int) mOuterX;
final int outerY = (int) mOuterY;
final int outerX = (int) mTargetX;
final int outerY = (int) mTargetY;
final int r = (int) mTargetRadius + 1;
bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
}
@@ -146,14 +162,25 @@ class RippleForeground extends RippleComponent {
@Override
protected Animator createSoftwareEnter(boolean fast) {
// Bounded ripples don't have enter animations.
if (mIsBounded) {
return null;
}
final int duration = (int)
(1000 * Math.sqrt(mTargetRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
final ObjectAnimator tweenAll = ObjectAnimator.ofFloat(this, TWEEN_ALL, 1);
tweenAll.setAutoCancel(true);
tweenAll.setDuration(duration);
tweenAll.setInterpolator(LINEAR_INTERPOLATOR);
tweenAll.setStartDelay(RIPPLE_ENTER_DELAY);
final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
tweenRadius.setAutoCancel(true);
tweenRadius.setDuration(duration);
tweenRadius.setInterpolator(LINEAR_INTERPOLATOR);
tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY);
final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
tweenOrigin.setAutoCancel(true);
tweenOrigin.setDuration(duration);
tweenOrigin.setInterpolator(LINEAR_INTERPOLATOR);
tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY);
final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
opacity.setAutoCancel(true);
@@ -161,31 +188,68 @@ class RippleForeground extends RippleComponent {
opacity.setInterpolator(LINEAR_INTERPOLATOR);
final AnimatorSet set = new AnimatorSet();
set.play(tweenAll).with(opacity);
set.play(tweenOrigin).with(tweenRadius).with(opacity);
return set;
}
private float getCurrentX() {
return MathUtils.lerp(mClampedStartingX - mBounds.exactCenterX(), mTargetX, mTweenX);
}
private float getCurrentY() {
return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
}
private int getRadiusExitDuration() {
final float radius = MathUtils.lerp(0, mTargetRadius, mTweenRadius);
final float remaining = mTargetRadius - radius;
return (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
final float remainingRadius = mTargetRadius - getCurrentRadius();
return (int) (1000 * Math.sqrt(remainingRadius / (WAVE_TOUCH_UP_ACCELERATION
+ WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
}
private float getCurrentRadius() {
return MathUtils.lerp(0, mTargetRadius, mTweenRadius);
}
private int getOpacityExitDuration() {
return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
}
/**
* Compute target values that are dependent on bounding.
*/
private void computeBoundedTargetValues() {
mTargetX = (mClampedStartingX - mBounds.exactCenterX()) * .7f;
mTargetY = (mClampedStartingY - mBounds.exactCenterY()) * .7f;
mTargetRadius = mBoundedRadius;
}
@Override
protected Animator createSoftwareExit() {
final int radiusDuration = getRadiusExitDuration();
final int opacityDuration = getOpacityExitDuration();
final int radiusDuration;
final int originDuration;
final int opacityDuration;
if (mIsBounded) {
computeBoundedTargetValues();
final ObjectAnimator tweenAll = ObjectAnimator.ofFloat(this, TWEEN_ALL, 1);
tweenAll.setAutoCancel(true);
tweenAll.setDuration(radiusDuration);
tweenAll.setInterpolator(DECELERATE_INTERPOLATOR);
radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
} else {
radiusDuration = getRadiusExitDuration();
originDuration = radiusDuration;
opacityDuration = getOpacityExitDuration();
}
final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
tweenRadius.setAutoCancel(true);
tweenRadius.setDuration(radiusDuration);
tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
tweenOrigin.setAutoCancel(true);
tweenOrigin.setDuration(originDuration);
tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
opacity.setAutoCancel(true);
@@ -193,7 +257,7 @@ class RippleForeground extends RippleComponent {
opacity.setInterpolator(LINEAR_INTERPOLATOR);
final AnimatorSet set = new AnimatorSet();
set.play(tweenAll).with(opacity);
set.play(tweenOrigin).with(tweenRadius).with(opacity);
set.addListener(mAnimationListener);
return set;
@@ -201,15 +265,25 @@ class RippleForeground extends RippleComponent {
@Override
protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
final int radiusDuration = getRadiusExitDuration();
final int opacityDuration = getOpacityExitDuration();
final int radiusDuration;
final int originDuration;
final int opacityDuration;
if (mIsBounded) {
computeBoundedTargetValues();
final float startX = MathUtils.lerp(
mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
final float startY = MathUtils.lerp(
mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
} else {
radiusDuration = getRadiusExitDuration();
originDuration = radiusDuration;
opacityDuration = getOpacityExitDuration();
}
final float startX = getCurrentX();
final float startY = getCurrentY();
final float startRadius = getCurrentRadius();
final float startRadius = MathUtils.lerp(0, mTargetRadius, mTweenRadius);
p.setAlpha((int) (p.getAlpha() * mOpacity + 0.5f));
mPropPaint = CanvasProperty.createPaint(p);
@@ -221,12 +295,12 @@ class RippleForeground extends RippleComponent {
radius.setDuration(radiusDuration);
radius.setInterpolator(DECELERATE_INTERPOLATOR);
final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mOuterX);
x.setDuration(radiusDuration);
final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX);
x.setDuration(originDuration);
x.setInterpolator(DECELERATE_INTERPOLATOR);
final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mOuterY);
y.setDuration(radiusDuration);
final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY);
y.setDuration(originDuration);
y.setInterpolator(DECELERATE_INTERPOLATOR);
final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
@@ -307,16 +381,13 @@ class RippleForeground extends RippleComponent {
}
/**
* Property for animating radius, center X, and center Y between their
* initial and target values.
* Property for animating radius between its initial and target values.
*/
private static final FloatProperty<RippleForeground> TWEEN_ALL =
new FloatProperty<RippleForeground>("tweenAll") {
private static final FloatProperty<RippleForeground> TWEEN_RADIUS =
new FloatProperty<RippleForeground>("tweenRadius") {
@Override
public void setValue(RippleForeground object, float value) {
object.mTweenRadius = value;
object.mTweenX = value;
object.mTweenY = value;
object.invalidateSelf();
}
@@ -326,6 +397,24 @@ class RippleForeground extends RippleComponent {
}
};
/**
* Property for animating origin between its initial and target values.
*/
private static final FloatProperty<RippleForeground> TWEEN_ORIGIN =
new FloatProperty<RippleForeground>("tweenOrigin") {
@Override
public void setValue(RippleForeground object, float value) {
object.mTweenX = value;
object.mTweenY = value;
object.invalidateSelf();
}
@Override
public Float get(RippleForeground object) {
return object.mTweenX;
}
};
/**
* Property for animating opacity between 0 and its target value.
*/