diff options
author | Alan Viverette <alanv@google.com> | 2014-05-01 14:22:33 -0700 |
---|---|---|
committer | Alan Viverette <alanv@google.com> | 2014-05-01 14:22:33 -0700 |
commit | 53d1cfe2d073bff7c7771d5f7dd9108062ddc706 (patch) | |
tree | cc870fd472d5ae93183c5a5e1e11e8f02bb605e6 /graphics | |
parent | 61bc9f37cc0e0921d2e205dccdd45df36c353a9c (diff) | |
download | frameworks_base-53d1cfe2d073bff7c7771d5f7dd9108062ddc706.zip frameworks_base-53d1cfe2d073bff7c7771d5f7dd9108062ddc706.tar.gz frameworks_base-53d1cfe2d073bff7c7771d5f7dd9108062ddc706.tar.bz2 |
Cleaning up TouchFeedbackDrawable and Ripple APIs
Change-Id: I73ce0507ce98140c01fe77cc277b0fea75350be9
Diffstat (limited to 'graphics')
-rw-r--r-- | graphics/java/android/graphics/drawable/Ripple.java | 478 | ||||
-rw-r--r-- | graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java | 135 |
2 files changed, 303 insertions, 310 deletions
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java index e3f57e9..207834a 100644 --- a/graphics/java/android/graphics/drawable/Ripple.java +++ b/graphics/java/android/graphics/drawable/Ripple.java @@ -16,20 +16,21 @@ package android.graphics.drawable; +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; -import android.util.MathUtils; -import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; /** * Draws a Quantum Paper ripple. */ class Ripple { - private static final TimeInterpolator INTERPOLATOR = new DecelerateInterpolator(2.0f); + private static final TimeInterpolator INTERPOLATOR = new DecelerateInterpolator(); /** Starting radius for a ripple. */ private static final int STARTING_RADIUS_DP = 16; @@ -37,6 +38,9 @@ class Ripple { /** Radius when finger is outside view bounds. */ private static final int OUTSIDE_RADIUS_DP = 16; + /** Radius when finger is inside view bounds. */ + private static final int INSIDE_RADIUS_DP = 96; + /** Margin when constraining outside touches (fraction of outer radius). */ private static final float OUTSIDE_MARGIN = 0.8f; @@ -44,15 +48,52 @@ class Ripple { private static final float OUTSIDE_RESISTANCE = 0.7f; /** Minimum alpha value during a pulse animation. */ - private static final int PULSE_MIN_ALPHA = 128; + private static final float PULSE_MIN_ALPHA = 0.5f; + + /** Duration for animating the trailing edge of the ripple. */ + private static final int EXIT_DURATION = 600; + + /** Duration for animating the leading edge of the ripple. */ + private static final int ENTER_DURATION = 400; + + /** Duration for animating the ripple alpha in and out. */ + private static final int FADE_DURATION = 50; + + /** Minimum elapsed time between start of enter and exit animations. */ + private static final int EXIT_MIN_DELAY = 200; + + /** Duration for animating between inside and outside touch. */ + private static final int OUTSIDE_DURATION = 300; + + /** Duration for animating pulses. */ + private static final int PULSE_DURATION = 400; + /** Interval between pulses while inside and fully entered. */ + private static final int PULSE_INTERVAL = 400; + + /** Delay before pulses start. */ + private static final int PULSE_DELAY = 500; + + private final Drawable mOwner; + + /** Bounds used for computing max radius and containment. */ private final Rect mBounds; - private final Rect mPadding; - private RippleAnimator mAnimator; + /** Configured maximum ripple radius when the center is outside the bounds. */ + private final int mMaxOutsideRadius; + + /** Configured maximum ripple radius. */ + private final int mMaxInsideRadius; + + private ObjectAnimator mEnter; + private ObjectAnimator mExit; + + /** Maximum ripple radius. */ + private int mMaxRadius; - private int mMinRadius; - private int mOutsideRadius; + private float mOuterRadius; + private float mInnerRadius; + private float mAlphaMultiplier; /** Center x-coordinate. */ private float mX; @@ -61,174 +102,253 @@ class Ripple { private float mY; /** Whether the center is within the parent bounds. */ - private boolean mInside; + private boolean mInsideBounds; /** Whether to pulse this ripple. */ - boolean mPulse; - - /** Enter state. A value in [0...1] or -1 if not set. */ - float mEnterState = -1; + private boolean mPulseEnabled; - /** Exit state. A value in [0...1] or -1 if not set. */ - float mExitState = -1; + /** Temporary hack since we can't check finished state of animator. */ + private boolean mExitFinished; - /** Outside state. A value in [0...1] or -1 if not set. */ - float mOutsideState = -1; - - /** Pulse state. A value in [0...1] or -1 if not set. */ - float mPulseState = -1; + /** Whether this ripple has ever moved. */ + private boolean mHasMoved; /** - * Creates a new ripple with the specified parent bounds, padding, initial - * position, and screen density. + * Creates a new ripple. */ - public Ripple(Rect bounds, Rect padding, float x, float y, float density, boolean pulse) { + public Ripple(Drawable owner, Rect bounds, float density, boolean pulseEnabled) { + mOwner = owner; mBounds = bounds; - mPadding = padding; - mInside = mBounds.contains((int) x, (int) y); - mPulse = pulse; + mPulseEnabled = pulseEnabled; - mX = x; - mY = y; + mOuterRadius = (int) (density * STARTING_RADIUS_DP + 0.5f); + mMaxOutsideRadius = (int) (density * OUTSIDE_RADIUS_DP + 0.5f); + mMaxInsideRadius = (int) (density * INSIDE_RADIUS_DP + 0.5f); + mMaxRadius = Math.min(mMaxInsideRadius, Math.max(bounds.width(), bounds.height())); + } + + public void setOuterRadius(float r) { + mOuterRadius = r; + invalidateSelf(); + } - mMinRadius = (int) (density * STARTING_RADIUS_DP + 0.5f); - mOutsideRadius = (int) (density * OUTSIDE_RADIUS_DP + 0.5f); + public float getOuterRadius() { + return mOuterRadius; } - - public void setMinRadius(int minRadius) { - mMinRadius = minRadius; + + public void setInnerRadius(float r) { + mInnerRadius = r; + invalidateSelf(); + } + + public float getInnerRadius() { + return mInnerRadius; } - - public void setOutsideRadius(int outsideRadius) { - mOutsideRadius = outsideRadius; + + public void setAlphaMultiplier(float a) { + mAlphaMultiplier = a; + invalidateSelf(); + } + + public float getAlphaMultiplier() { + return mAlphaMultiplier; } /** - * Updates the center coordinates. + * Returns whether this ripple has finished exiting. */ - public void move(float x, float y) { - mX = x; - mY = y; - - final boolean inside = mBounds.contains((int) x, (int) y); - if (mInside != inside) { - if (mAnimator != null) { - mAnimator.outside(); - } - mInside = inside; - } + public boolean isFinished() { + return mExitFinished; } + /** + * Called when the bounds change. + */ public void onBoundsChanged() { - final boolean inside = mBounds.contains((int) mX, (int) mY); - if (mInside != inside) { - if (mAnimator != null) { - mAnimator.outside(); - } - mInside = inside; - } + mMaxRadius = Math.min(mMaxInsideRadius, Math.max(mBounds.width(), mBounds.height())); + + updateInsideBounds(); } - public RippleAnimator animate() { - if (mAnimator == null) { - mAnimator = new RippleAnimator(this); + private void updateInsideBounds() { + final boolean insideBounds = mBounds.contains((int) (mX + 0.5f), (int) (mY + 0.5f)); + if (mInsideBounds != insideBounds || !mHasMoved) { + mInsideBounds = insideBounds; + mHasMoved = true; + + if (insideBounds) { + enter(); + } else { + outside(); + } } - return mAnimator; } + /** + * Draws the ripple using the specified paint. + */ public boolean draw(Canvas c, Paint p) { final Rect bounds = mBounds; - final Rect padding = mPadding; - final float dX = Math.max(mX - bounds.left, bounds.right - mX); - final float dY = Math.max(mY - bounds.top, bounds.bottom - mY); - final int maxRadius = (int) Math.ceil(Math.sqrt(dX * dX + dY * dY)); - - final float enterState = mEnterState; - final float exitState = mExitState; - final float outsideState = mOutsideState; - final float pulseState = mPulseState; - final float insideRadius = MathUtils.lerp(mMinRadius, maxRadius, enterState); - final float outerRadius = MathUtils.lerp(mOutsideRadius, insideRadius, - mInside ? outsideState : 1 - outsideState); + final float outerRadius = mOuterRadius; + final float innerRadius = mInnerRadius; + final float alphaMultiplier = mAlphaMultiplier; + + // Cache the paint alpha so we can restore it later. + final int paintAlpha = p.getAlpha(); + final int alpha = (int) (paintAlpha * alphaMultiplier + 0.5f); // Apply resistance effect when outside bounds. - final float x = looseConstrain(mX, bounds.left + padding.left, bounds.right - padding.right, - outerRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE); - final float y = looseConstrain(mY, bounds.top + padding.top, bounds.bottom - padding.bottom, - outerRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE); - - // Compute maximum alpha, taking pulse into account when active. - final int maxAlpha; - if (pulseState < 0 || pulseState >= 1) { - maxAlpha = 255; + final float x; + final float y; + if (mInsideBounds) { + x = mX; + y = mY; } else { - final float pulseAlpha; - if (pulseState > 0.5) { - // Pulsing in to max alpha. - pulseAlpha = MathUtils.lerp(PULSE_MIN_ALPHA, 255, (pulseState - .5f) * 2); - } else { - // Pulsing out to min alpha. - pulseAlpha = MathUtils.lerp(255, PULSE_MIN_ALPHA, pulseState * 2f); - } - - if (exitState > 0) { - // Animating exit, interpolate pulse with exit state. - maxAlpha = (int) (MathUtils.lerp(255, pulseAlpha, exitState) + 0.5f); - } else if (mInside) { - // No animation, no need to interpolate. - maxAlpha = (int) (pulseAlpha + 0.5f); - } else { - // Animating inside, interpolate pulse with inside state. - maxAlpha = (int) (MathUtils.lerp(pulseAlpha, 255, outsideState) + 0.5f); - } + // TODO: We need to do this outside of draw() so that our dirty + // bounds accurately reflect resistance. + x = looseConstrain(mX, bounds.left, bounds.right, + mOuterRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE); + y = looseConstrain(mY, bounds.top, bounds.bottom, + mOuterRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE); } - if (maxAlpha > 0) { - if (exitState <= 0) { - // Exit state isn't showing, so we can simplify to a solid - // circle. - if (outerRadius > 0) { - p.setAlpha(maxAlpha); - p.setStyle(Style.FILL); - c.drawCircle(x, y, outerRadius, p); - return true; - } - } else { - // Both states are showing, so we need a circular stroke. - final float innerRadius = MathUtils.lerp(0, outerRadius, exitState); - final float strokeWidth = outerRadius - innerRadius; - if (strokeWidth > 0) { - final float strokeRadius = innerRadius + strokeWidth / 2f; - final int alpha = (int) (MathUtils.lerp(maxAlpha, 0, exitState) + 0.5f); - if (alpha > 0) { - p.setAlpha(alpha); - p.setStyle(Style.STROKE); - p.setStrokeWidth(strokeWidth); - c.drawCircle(x, y, strokeRadius, p); - return true; - } - } - } + final boolean hasContent; + if (alphaMultiplier <= 0 || innerRadius >= outerRadius) { + // Nothing to draw. + hasContent = false; + } else if (innerRadius > 0) { + // Draw a ring. + final float strokeWidth = outerRadius - innerRadius; + final float strokeRadius = innerRadius + strokeWidth / 2.0f; + p.setAlpha(alpha); + p.setStyle(Style.STROKE); + p.setStrokeWidth(strokeWidth); + c.drawCircle(x, y, strokeRadius, p); + hasContent = true; + } else if (outerRadius > 0) { + // Draw a circle. + p.setAlpha(alpha); + p.setStyle(Style.FILL); + c.drawCircle(x, y, outerRadius, p); + hasContent = true; + } else { + hasContent = false; } - return false; + p.setAlpha(paintAlpha); + return hasContent; } + /** + * Returns the maximum bounds for this ripple. + */ public void getBounds(Rect bounds) { final int x = (int) mX; final int y = (int) mY; - final int dX = Math.max(x, mBounds.right - x); - final int dY = Math.max(x, mBounds.bottom - y); - final int maxRadius = (int) Math.ceil(Math.sqrt(dX * dX + dY * dY)); + final int maxRadius = mMaxRadius; bounds.set(x - maxRadius, y - maxRadius, x + maxRadius, y + maxRadius); } /** + * Updates the center coordinates. + */ + public void move(float x, float y) { + mX = x; + mY = y; + + updateInsideBounds(); + invalidateSelf(); + } + + /** + * Starts the exit animation. If {@link #enter()} was called recently, the + * animation may be postponed. + */ + public void exit() { + mExitFinished = false; + + final ObjectAnimator exit = ObjectAnimator.ofFloat(this, "innerRadius", 0, mMaxRadius); + exit.setAutoCancel(true); + exit.setDuration(EXIT_DURATION); + exit.setInterpolator(INTERPOLATOR); + exit.addListener(mAnimationListener); + + if (mEnter != null && mEnter.isStarted()) { + // If we haven't been running the enter animation for long enough, + // delay the exit animator. + final int elapsed = (int) (mEnter.getAnimatedFraction() * mEnter.getDuration()); + final int delay = Math.max(0, EXIT_MIN_DELAY - elapsed); + exit.setStartDelay(delay); + } + + exit.start(); + + final ObjectAnimator fade = ObjectAnimator.ofFloat(this, "alphaMultiplier", 0); + fade.setAutoCancel(true); + fade.setDuration(EXIT_DURATION); + fade.start(); + + mExit = exit; + } + + private void invalidateSelf() { + mOwner.invalidateSelf(); + } + + /** + * Starts the enter animation. + */ + private void enter() { + final ObjectAnimator enter = ObjectAnimator.ofFloat(this, "outerRadius", mMaxRadius); + enter.setAutoCancel(true); + enter.setDuration(ENTER_DURATION); + enter.setInterpolator(INTERPOLATOR); + enter.start(); + + final ObjectAnimator fade = ObjectAnimator.ofFloat(this, "alphaMultiplier", 1); + fade.setAutoCancel(true); + fade.setDuration(FADE_DURATION); + fade.start(); + + // TODO: Starting with a delay will still cancel the fade in. + if (false && mPulseEnabled) { + final ObjectAnimator pulse = ObjectAnimator.ofFloat( + this, "alphaMultiplier", 1, PULSE_MIN_ALPHA); + pulse.setAutoCancel(true); + pulse.setDuration(PULSE_DURATION + PULSE_INTERVAL); + pulse.setRepeatCount(ObjectAnimator.INFINITE); + pulse.setRepeatMode(ObjectAnimator.REVERSE); + pulse.setStartDelay(PULSE_DELAY); + pulse.start(); + } + + mEnter = enter; + } + + /** + * Starts the outside transition animation. + */ + private void outside() { + final float targetRadius = mMaxOutsideRadius; + final ObjectAnimator outside = ObjectAnimator.ofFloat(this, "outerRadius", targetRadius); + outside.setAutoCancel(true); + outside.setDuration(OUTSIDE_DURATION); + outside.setInterpolator(INTERPOLATOR); + outside.start(); + + final ObjectAnimator fade = ObjectAnimator.ofFloat(this, "alphaMultiplier", 1); + fade.setAutoCancel(true); + fade.setDuration(FADE_DURATION); + fade.start(); + } + + /** * Constrains a value within a specified asymptotic margin outside a minimum * and maximum. */ private static float looseConstrain(float value, float min, float max, float margin, float factor) { + // TODO: Can we use actual spring physics here? if (value < min) { return min - Math.min(margin, (float) Math.pow(min - value, factor)); } else if (value > max) { @@ -237,96 +357,28 @@ class Ripple { return value; } } - - public static class RippleAnimator { - /** Duration for animating the trailing edge of the ripple. */ - private static final int EXIT_DURATION = 600; - - /** Duration for animating the leading edge of the ripple. */ - private static final int ENTER_DURATION = 400; - - /** Minimum elapsed time between start of enter and exit animations. */ - private static final int EXIT_MIN_DELAY = 200; - - /** Duration for animating between inside and outside touch. */ - private static final int OUTSIDE_DURATION = 300; - - /** Duration for animating pulses. */ - private static final int PULSE_DURATION = 400; - - /** Interval between pulses while inside and fully entered. */ - private static final int PULSE_INTERVAL = 400; - /** Delay before pulses start. */ - private static final int PULSE_DELAY = 500; - - /** The target ripple being animated. */ - private final Ripple mTarget; - - /** When the ripple started appearing. */ - private long mEnterTime = -1; - - /** When the ripple started vanishing. */ - private long mExitTime = -1; - - /** When the ripple last transitioned between inside and outside touch. */ - private long mOutsideTime = -1; - - public RippleAnimator(Ripple target) { - mTarget = target; - } - - /** - * Starts the enter animation. - */ - public void enter() { - mEnterTime = AnimationUtils.currentAnimationTimeMillis(); - } - - /** - * Starts the exit animation. If {@link #enter()} was called recently, the - * animation may be postponed. - */ - public void exit() { - final long minTime = mEnterTime + EXIT_MIN_DELAY; - mExitTime = Math.max(minTime, AnimationUtils.currentAnimationTimeMillis()); + private final AnimatorListener mAnimationListener = new AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { } - /** - * Starts the outside transition animation. - */ - public void outside() { - mOutsideTime = AnimationUtils.currentAnimationTimeMillis(); + @Override + public void onAnimationRepeat(Animator animation) { } - /** - * Returns whether this ripple is currently animating. - */ - public boolean isRunning() { - final long currentTime = AnimationUtils.currentAnimationTimeMillis(); - return mEnterTime >= 0 && mEnterTime <= currentTime - && (mExitTime < 0 || currentTime <= mExitTime + EXIT_DURATION); + @Override + public void onAnimationEnd(Animator animation) { + if (animation == mExit) { + mExitFinished = true; + mOuterRadius = 0; + mInnerRadius = 0; + mAlphaMultiplier = 1; + } } - public void update() { - // Track three states: - // - Enter: touch begins, affects outer radius - // - Outside: touch moves outside bounds, affects maximum outer radius - // - Exit: touch ends, affects inner radius - final long currentTime = AnimationUtils.currentAnimationTimeMillis(); - mTarget.mEnterState = mEnterTime < 0 ? 0 : INTERPOLATOR.getInterpolation( - MathUtils.constrain((currentTime - mEnterTime) / (float) ENTER_DURATION, 0, 1)); - mTarget.mExitState = mExitTime < 0 ? 0 : INTERPOLATOR.getInterpolation( - MathUtils.constrain((currentTime - mExitTime) / (float) EXIT_DURATION, 0, 1)); - mTarget.mOutsideState = mOutsideTime < 0 ? 1 : INTERPOLATOR.getInterpolation( - MathUtils.constrain((currentTime - mOutsideTime) / (float) OUTSIDE_DURATION, 0, 1)); - - // Pulse is a little more complicated. - if (mTarget.mPulse) { - final long pulseTime = (currentTime - mEnterTime - ENTER_DURATION - PULSE_DELAY); - mTarget.mPulseState = pulseTime < 0 ? -1 - : (pulseTime % (PULSE_INTERVAL + PULSE_DURATION)) / (float) PULSE_DURATION; - } + @Override + public void onAnimationCancel(Animator animation) { } - } + }; } diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java index 0e8831f..9000e5a 100644 --- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java +++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java @@ -27,8 +27,6 @@ import android.graphics.PixelFormat; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.drawable.Ripple.RippleAnimator; -import android.os.SystemClock; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -40,7 +38,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.util.Arrays; /** * Documentation pending. @@ -54,7 +51,6 @@ public class TouchFeedbackDrawable extends LayerDrawable { private static final int MAX_RIPPLES = 10; private final Rect mTempRect = new Rect(); - private final Rect mPaddingRect = new Rect(); /** Current ripple effect bounds, used to constrain ripple effects. */ private final Rect mHotspotBounds = new Rect(); @@ -68,14 +64,11 @@ public class TouchFeedbackDrawable extends LayerDrawable { private final TouchFeedbackState mState; /** Lazily-created map of touch hotspot IDs to ripples. */ - private SparseArray<Ripple> mTouchedRipples; + private SparseArray<Ripple> mRipples; /** Lazily-created array of actively animating ripples. */ - private Ripple[] mActiveRipples; - private int mActiveRipplesCount = 0; - - /** Lazily-created runnable for scheduling invalidation. */ - private Runnable mAnimationRunnable; + private Ripple[] mAnimatingRipples; + private int mAnimatingRipplesCount = 0; /** Paint used to control appearance of ripples. */ private Paint mRipplePaint; @@ -86,9 +79,6 @@ public class TouchFeedbackDrawable extends LayerDrawable { /** Target density of the display into which ripples are drawn. */ private float mDensity = 1.0f; - /** Whether the animation runnable has been posted. */ - private boolean mAnimating; - /** Whether bounds are being overridden. */ private boolean mOverrideBounds; @@ -154,28 +144,19 @@ public class TouchFeedbackDrawable extends LayerDrawable { private void onHotspotBoundsChange() { final int x = mHotspotBounds.centerX(); final int y = mHotspotBounds.centerY(); - final int N = mActiveRipplesCount; + final int N = mAnimatingRipplesCount; for (int i = 0; i < N; i++) { if (mState.mPinned) { - mActiveRipples[i].move(x, y); + mAnimatingRipples[i].move(x, y); } - mActiveRipples[i].onBoundsChanged(); + mAnimatingRipples[i].onBoundsChanged(); } } @Override public boolean setVisible(boolean visible, boolean restart) { if (!visible) { - if (mTouchedRipples != null) { - mTouchedRipples.clear(); - } - - if (mActiveRipplesCount > 0) { - Arrays.fill(mActiveRipples, null); - mActiveRipplesCount = 0; - mAnimating = false; - unscheduleSelf(mAnimationRunnable); - } + clearHotspots(); } return super.setVisible(visible, restart); @@ -348,21 +329,18 @@ public class TouchFeedbackDrawable extends LayerDrawable { @Override public void setHotspot(int id, float x, float y) { - if (mTouchedRipples == null) { - mTouchedRipples = new SparseArray<Ripple>(); - mActiveRipples = new Ripple[MAX_RIPPLES]; + if (mRipples == null) { + mRipples = new SparseArray<Ripple>(); + mAnimatingRipples = new Ripple[MAX_RIPPLES]; } - if (mActiveRipplesCount >= MAX_RIPPLES) { + if (mAnimatingRipplesCount >= MAX_RIPPLES) { Log.e(LOG_TAG, "Max ripple count exceeded", new RuntimeException()); return; } - final Ripple ripple = mTouchedRipples.get(id); + final Ripple ripple = mRipples.get(id); if (ripple == null) { - final Rect padding = mPaddingRect; - getPadding(padding); - final Rect bounds = mHotspotBounds; if (mState.mPinned) { x = bounds.exactCenterX(); @@ -371,11 +349,11 @@ public class TouchFeedbackDrawable extends LayerDrawable { // TODO: Clean this up in the API. final boolean pulse = (id != R.attr.state_focused); - final Ripple newRipple = new Ripple(bounds, padding, x, y, mDensity, pulse); - newRipple.animate().enter(); + final Ripple newRipple = new Ripple(this, bounds, mDensity, pulse); + newRipple.move(x, y); - mActiveRipples[mActiveRipplesCount++] = newRipple; - mTouchedRipples.put(id, newRipple); + mAnimatingRipples[mAnimatingRipplesCount++] = newRipple; + mRipples.put(id, newRipple); } else if (mState.mPinned) { final Rect bounds = mHotspotBounds; x = bounds.exactCenterX(); @@ -384,41 +362,30 @@ public class TouchFeedbackDrawable extends LayerDrawable { } else { ripple.move(x, y); } - - scheduleAnimation(); } @Override public void removeHotspot(int id) { - if (mTouchedRipples == null) { + if (mRipples == null) { return; } - final Ripple ripple = mTouchedRipples.get(id); + final Ripple ripple = mRipples.get(id); if (ripple != null) { - ripple.animate().exit(); + ripple.exit(); - mTouchedRipples.remove(id); - scheduleAnimation(); + mRipples.remove(id); } } @Override public void clearHotspots() { - if (mTouchedRipples == null) { + if (mRipples == null) { return; } - final int n = mTouchedRipples.size(); - for (int i = 0; i < n; i++) { - // TODO: Use a fast exit, maybe just fade out? - mTouchedRipples.valueAt(i).animate().exit(); - } - - if (n > 0) { - mTouchedRipples.clear(); - scheduleAnimation(); - } + mRipples.clear(); + invalidateSelf(); } /** @@ -431,30 +398,6 @@ public class TouchFeedbackDrawable extends LayerDrawable { onHotspotBoundsChange(); } - /** - * Schedules the next animation, if necessary. - */ - private void scheduleAnimation() { - if (mActiveRipplesCount == 0) { - mAnimating = false; - } else if (!mAnimating) { - mAnimating = true; - - if (mAnimationRunnable == null) { - mAnimationRunnable = new Runnable() { - @Override - public void run() { - mAnimating = false; - scheduleAnimation(); - invalidateSelf(); - } - }; - } - - scheduleSelf(mAnimationRunnable, SystemClock.uptimeMillis() + 1000 / 60); - } - } - @Override public void draw(Canvas canvas) { final int N = mLayerState.mNum; @@ -501,12 +444,12 @@ public class TouchFeedbackDrawable extends LayerDrawable { } private int drawRippleLayer(Canvas canvas, Rect bounds, boolean maskOnly) { - final int ripplesCount = mActiveRipplesCount; - if (ripplesCount == 0) { + final int count = mAnimatingRipplesCount; + if (count == 0) { return -1; } - final Ripple[] activeRipples = mActiveRipples; + final Ripple[] ripples = mAnimatingRipples; final boolean projected = isProjected(); final Rect layerBounds = projected ? getDirtyBounds() : bounds; @@ -529,17 +472,15 @@ public class TouchFeedbackDrawable extends LayerDrawable { boolean drewRipples = false; int restoreToCount = -1; - int activeRipplesCount = 0; + int animatingCount = 0; - // Draw ripples. - for (int i = 0; i < ripplesCount; i++) { - final Ripple ripple = activeRipples[i]; - final RippleAnimator animator = ripple.animate(); - animator.update(); + // Draw ripples and update the animating ripples array. + for (int i = 0; i < count; i++) { + final Ripple ripple = ripples[i]; - // Mark and skip inactive ripples. - if (!animator.isRunning()) { - activeRipples[i] = null; + // Mark and skip finished ripples. + if (ripple.isFinished()) { + ripples[i] = null; continue; } @@ -565,11 +506,11 @@ public class TouchFeedbackDrawable extends LayerDrawable { drewRipples |= ripple.draw(canvas, ripplePaint); - activeRipples[activeRipplesCount] = activeRipples[i]; - activeRipplesCount++; + ripples[animatingCount] = ripples[i]; + animatingCount++; } - mActiveRipplesCount = activeRipplesCount; + mAnimatingRipplesCount = animatingCount; // If we created a layer with no content, merge it immediately. if (restoreToCount >= 0 && !drewRipples) { @@ -596,8 +537,8 @@ public class TouchFeedbackDrawable extends LayerDrawable { drawingBounds.setEmpty(); final Rect rippleBounds = mTempRect; - final Ripple[] activeRipples = mActiveRipples; - final int N = mActiveRipplesCount; + final Ripple[] activeRipples = mAnimatingRipples; + final int N = mAnimatingRipplesCount; for (int i = 0; i < N; i++) { activeRipples[i].getBounds(rippleBounds); drawingBounds.union(rippleBounds); |