Merge "Cleaning up TouchFeedbackDrawable and Ripple APIs"

This commit is contained in:
Alan Viverette
2014-05-01 21:26:07 +00:00
committed by Android (Google) Code Review
2 changed files with 309 additions and 316 deletions

View File

@@ -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;
private int mMinRadius;
private int mOutsideRadius;
/** Configured maximum ripple radius. */
private final int mMaxInsideRadius;
private ObjectAnimator mEnter;
private ObjectAnimator mExit;
/** Maximum ripple radius. */
private int mMaxRadius;
private float mOuterRadius;
private float mInnerRadius;
private float mAlphaMultiplier;
/** Center x-coordinate. */
private float mX;
@@ -61,46 +102,151 @@ 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()));
}
mMinRadius = (int) (density * STARTING_RADIUS_DP + 0.5f);
mOutsideRadius = (int) (density * OUTSIDE_RADIUS_DP + 0.5f);
public void setOuterRadius(float r) {
mOuterRadius = r;
invalidateSelf();
}
public void setMinRadius(int minRadius) {
mMinRadius = minRadius;
public float getOuterRadius() {
return mOuterRadius;
}
public void setOutsideRadius(int outsideRadius) {
mOutsideRadius = outsideRadius;
public void setInnerRadius(float r) {
mInnerRadius = r;
invalidateSelf();
}
public float getInnerRadius() {
return mInnerRadius;
}
public void setAlphaMultiplier(float a) {
mAlphaMultiplier = a;
invalidateSelf();
}
public float getAlphaMultiplier() {
return mAlphaMultiplier;
}
/**
* Returns whether this ripple has finished exiting.
*/
public boolean isFinished() {
return mExitFinished;
}
/**
* Called when the bounds change.
*/
public void onBoundsChanged() {
mMaxRadius = Math.min(mMaxInsideRadius, Math.max(mBounds.width(), mBounds.height()));
updateInsideBounds();
}
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();
}
}
}
/**
* Draws the ripple using the specified paint.
*/
public boolean draw(Canvas c, Paint p) {
final Rect bounds = mBounds;
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;
final float y;
if (mInsideBounds) {
x = mX;
y = mY;
} else {
// 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);
}
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;
}
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 maxRadius = mMaxRadius;
bounds.set(x - maxRadius, y - maxRadius, x + maxRadius, y + maxRadius);
}
/**
@@ -110,117 +256,90 @@ class Ripple {
mX = x;
mY = y;
final boolean inside = mBounds.contains((int) x, (int) y);
if (mInside != inside) {
if (mAnimator != null) {
mAnimator.outside();
}
mInside = inside;
}
updateInsideBounds();
invalidateSelf();
}
public void onBoundsChanged() {
final boolean inside = mBounds.contains((int) mX, (int) mY);
if (mInside != inside) {
if (mAnimator != null) {
mAnimator.outside();
}
mInside = inside;
/**
* 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;
}
public RippleAnimator animate() {
if (mAnimator == null) {
mAnimator = new RippleAnimator(this);
}
return mAnimator;
private void invalidateSelf() {
mOwner.invalidateSelf();
}
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));
/**
* 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 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 ObjectAnimator fade = ObjectAnimator.ofFloat(this, "alphaMultiplier", 1);
fade.setAutoCancel(true);
fade.setDuration(FADE_DURATION);
fade.start();
// 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;
} 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: 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();
}
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;
}
}
}
}
return false;
mEnter = enter;
}
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));
bounds.set(x - maxRadius, y - maxRadius, x + maxRadius, y + maxRadius);
/**
* 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();
}
/**
@@ -229,6 +348,7 @@ class Ripple {
*/
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;
private final AnimatorListener mAnimationListener = new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
/**
* Starts the enter animation.
*/
public void enter() {
mEnterTime = AnimationUtils.currentAnimationTimeMillis();
@Override
public void onAnimationRepeat(Animator animation) {
}
/**
* 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());
}
/**
* Starts the outside transition animation.
*/
public void outside() {
mOutsideTime = AnimationUtils.currentAnimationTimeMillis();
}
/**
* 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);
}
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 onAnimationEnd(Animator animation) {
if (animation == mExit) {
mExitFinished = true;
mOuterRadius = 0;
mInnerRadius = 0;
mAlphaMultiplier = 1;
}
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
};
}

View File

@@ -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);