summaryrefslogtreecommitdiffstats
path: root/graphics
diff options
context:
space:
mode:
authorAlan Viverette <alanv@google.com>2014-05-01 14:22:33 -0700
committerAlan Viverette <alanv@google.com>2014-05-01 14:22:33 -0700
commit53d1cfe2d073bff7c7771d5f7dd9108062ddc706 (patch)
treecc870fd472d5ae93183c5a5e1e11e8f02bb605e6 /graphics
parent61bc9f37cc0e0921d2e205dccdd45df36c353a9c (diff)
downloadframeworks_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.java478
-rw-r--r--graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java135
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);