FileDocCategorySizeDatePackage
Ripple.javaAPI DocAndroid 5.1 API18194Thu Mar 12 22:22:30 GMT 2015android.graphics.drawable

Ripple

public class Ripple extends Object
Draws a Material ripple.

Fields Summary
private static final android.animation.TimeInterpolator
LINEAR_INTERPOLATOR
private static final android.animation.TimeInterpolator
DECEL_INTERPOLATOR
private static final float
GLOBAL_SPEED
private static final float
WAVE_TOUCH_DOWN_ACCELERATION
private static final float
WAVE_TOUCH_UP_ACCELERATION
private static final float
WAVE_OPACITY_DECAY_VELOCITY
private static final long
RIPPLE_ENTER_DELAY
private final ArrayList
mRunningAnimations
private final RippleDrawable
mOwner
private final android.graphics.Rect
mBounds
Bounds used for computing max radius.
private float
mOuterRadius
Maximum ripple radius.
private float
mDensity
Screen density used to adjust pixel-based velocities.
private float
mStartingX
private float
mStartingY
private float
mClampedStartingX
private float
mClampedStartingY
private android.graphics.CanvasProperty
mPropPaint
private android.graphics.CanvasProperty
mPropRadius
private android.graphics.CanvasProperty
mPropX
private android.graphics.CanvasProperty
mPropY
private android.animation.ObjectAnimator
mAnimRadius
private android.animation.ObjectAnimator
mAnimOpacity
private android.animation.ObjectAnimator
mAnimX
private android.animation.ObjectAnimator
mAnimY
private android.graphics.Paint
mTempPaint
private float
mOpacity
private float
mOuterX
private float
mOuterY
private float
mTweenRadius
private float
mTweenX
private float
mTweenY
private boolean
mHardwareAnimating
Whether we should be drawing hardware animations.
private boolean
mCanUseHardware
Whether we can use hardware acceleration for the exit animation.
private boolean
mHasMaxRadius
Whether we have an explicit maximum radius.
private boolean
mCanceled
Whether we were canceled externally and should avoid self-removal.
private boolean
mHasPendingHardwareExit
private int
mPendingRadiusDuration
private int
mPendingOpacityDuration
private final android.animation.AnimatorListenerAdapter
mAnimationListener
Constructors Summary
public Ripple(RippleDrawable owner, android.graphics.Rect bounds, float startingX, float startingY)
Creates a new ripple.


             
             
        mOwner = owner;
        mBounds = bounds;

        mStartingX = startingX;
        mStartingY = startingY;
    
Methods Summary
public voidcancel()
Cancels all animations. The caller is responsible for removing the ripple from the list of animating ripples.

        mCanceled = true;
        cancelSoftwareAnimations();
        cancelHardwareAnimations(false);
        mCanceled = false;
    
private voidcancelHardwareAnimations(boolean jumpToEnd)
Cancels any running hardware animations.

        final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
        final int N = runningAnimations.size();
        for (int i = 0; i < N; i++) {
            if (jumpToEnd) {
                runningAnimations.get(i).end();
            } else {
                runningAnimations.get(i).cancel();
            }
        }
        runningAnimations.clear();

        if (mHasPendingHardwareExit) {
            // If we had a pending hardware exit, jump to the end state.
            mHasPendingHardwareExit = false;

            if (jumpToEnd) {
                mOpacity = 0;
                mTweenX = 1;
                mTweenY = 1;
                mTweenRadius = 1;
            }
        }

        mHardwareAnimating = false;
    
private voidcancelSoftwareAnimations()

        if (mAnimRadius != null) {
            mAnimRadius.cancel();
            mAnimRadius = null;
        }

        if (mAnimOpacity != null) {
            mAnimOpacity.cancel();
            mAnimOpacity = null;
        }

        if (mAnimX != null) {
            mAnimX.cancel();
            mAnimX = null;
        }

        if (mAnimY != null) {
            mAnimY.cancel();
            mAnimY = null;
        }
    
private voidclampStartingPosition()

        final float cX = mBounds.exactCenterX();
        final float cY = mBounds.exactCenterY();
        final float dX = mStartingX - cX;
        final float dY = mStartingY - cY;
        final float r = mOuterRadius;
        if (dX * dX + dY * dY > r * r) {
            // Point is outside the circle, clamp to the circumference.
            final double angle = Math.atan2(dY, dX);
            mClampedStartingX = cX + (float) (Math.cos(angle) * r);
            mClampedStartingY = cY + (float) (Math.sin(angle) * r);
        } else {
            mClampedStartingX = mStartingX;
            mClampedStartingY = mStartingY;
        }
    
private voidcreatePendingHardwareExit(int radiusDuration, int opacityDuration)

        mHasPendingHardwareExit = true;
        mPendingRadiusDuration = radiusDuration;
        mPendingOpacityDuration = opacityDuration;

        // The animation will start on the next draw().
        invalidateSelf();
    
public booleandraw(android.graphics.Canvas c, android.graphics.Paint p)
Draws the ripple centered at (0,0) using the specified paint.

        final boolean canUseHardware = c.isHardwareAccelerated();
        if (mCanUseHardware != canUseHardware && mCanUseHardware) {
            // We've switched from hardware to non-hardware mode. Panic.
            cancelHardwareAnimations(true);
        }
        mCanUseHardware = canUseHardware;

        final boolean hasContent;
        if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) {
            hasContent = drawHardware((HardwareCanvas) c, p);
        } else {
            hasContent = drawSoftware(c, p);
        }

        return hasContent;
    
private booleandrawHardware(android.view.HardwareCanvas c, android.graphics.Paint p)

        if (mHasPendingHardwareExit) {
            cancelHardwareAnimations(false);
            startPendingHardwareExit(c, p);
        }

        c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);

        return true;
    
private booleandrawSoftware(android.graphics.Canvas c, android.graphics.Paint p)

        boolean hasContent = false;

        final int paintAlpha = p.getAlpha();
        final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
        final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
        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);
            p.setAlpha(alpha);
            c.drawCircle(x, y, radius, p);
            p.setAlpha(paintAlpha);
            hasContent = true;
        }

        return hasContent;
    
private voidendSoftwareAnimations()

        if (mAnimRadius != null) {
            mAnimRadius.end();
            mAnimRadius = null;
        }

        if (mAnimOpacity != null) {
            mAnimOpacity.end();
            mAnimOpacity = null;
        }

        if (mAnimX != null) {
            mAnimX.end();
            mAnimX = null;
        }

        if (mAnimY != null) {
            mAnimY.end();
            mAnimY = null;
        }
    
public voidenter()
Starts the enter animation.

        cancel();

        final int radiusDuration = (int)
                (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);

        final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
        radius.setAutoCancel(true);
        radius.setDuration(radiusDuration);
        radius.setInterpolator(LINEAR_INTERPOLATOR);
        radius.setStartDelay(RIPPLE_ENTER_DELAY);

        final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1);
        cX.setAutoCancel(true);
        cX.setDuration(radiusDuration);
        cX.setInterpolator(LINEAR_INTERPOLATOR);
        cX.setStartDelay(RIPPLE_ENTER_DELAY);

        final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", 1);
        cY.setAutoCancel(true);
        cY.setDuration(radiusDuration);
        cY.setInterpolator(LINEAR_INTERPOLATOR);
        cY.setStartDelay(RIPPLE_ENTER_DELAY);

        mAnimRadius = radius;
        mAnimX = cX;
        mAnimY = cY;

        // Enter animations always run on the UI thread, since it's unlikely
        // that anything interesting is happening until the user lifts their
        // finger.
        radius.start();
        cX.start();
        cY.start();
    
public voidexit()
Starts the exit animation.

        final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
        final float remaining;
        if (mAnimRadius != null && mAnimRadius.isRunning()) {
            remaining = mOuterRadius - radius;
        } else {
            remaining = mOuterRadius;
        }

        cancel();

        final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
                + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
        final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);

        if (mCanUseHardware) {
            createPendingHardwareExit(radiusDuration, opacityDuration);
        } else {
            exitSoftware(radiusDuration, opacityDuration);
        }
    
private voidexitSoftware(int radiusDuration, int opacityDuration)

        final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
        radiusAnim.setAutoCancel(true);
        radiusAnim.setDuration(radiusDuration);
        radiusAnim.setInterpolator(DECEL_INTERPOLATOR);

        final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1);
        xAnim.setAutoCancel(true);
        xAnim.setDuration(radiusDuration);
        xAnim.setInterpolator(DECEL_INTERPOLATOR);

        final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1);
        yAnim.setAutoCancel(true);
        yAnim.setDuration(radiusDuration);
        yAnim.setInterpolator(DECEL_INTERPOLATOR);

        final ObjectAnimator opacityAnim = ObjectAnimator.ofFloat(this, "opacity", 0);
        opacityAnim.setAutoCancel(true);
        opacityAnim.setDuration(opacityDuration);
        opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
        opacityAnim.addListener(mAnimationListener);

        mAnimRadius = radiusAnim;
        mAnimOpacity = opacityAnim;
        mAnimX = xAnim;
        mAnimY = yAnim;

        radiusAnim.start();
        opacityAnim.start();
        xAnim.start();
        yAnim.start();
    
public voidgetBounds(android.graphics.Rect bounds)
Returns the maximum bounds of the ripple relative to the ripple center.

        final int outerX = (int) mOuterX;
        final int outerY = (int) mOuterY;
        final int r = (int) mOuterRadius + 1;
        bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
    
public floatgetOpacity()

        return mOpacity;
    
public floatgetRadiusGravity()

        return mTweenRadius;
    
private android.graphics.PaintgetTempPaint(android.graphics.Paint original)

        if (mTempPaint == null) {
            mTempPaint = new Paint();
        }
        mTempPaint.set(original);
        return mTempPaint;
    
public floatgetXGravity()

        return mTweenX;
    
public floatgetYGravity()

        return mTweenY;
    
private voidinvalidateSelf()

        mOwner.invalidateSelf();
    
public booleanisHardwareAnimating()

        return mHardwareAnimating;
    
public voidjump()
Jump all animations to their end state. The caller is responsible for removing the ripple from the list of animating ripples.

        mCanceled = true;
        endSoftwareAnimations();
        cancelHardwareAnimations(true);
        mCanceled = false;
    
public voidmove(float x, float y)
Specifies the starting position relative to the drawable bounds. No-op if the ripple has already entered.

        mStartingX = x;
        mStartingY = y;

        clampStartingPosition();
    
public voidonHotspotBoundsChanged()

        if (!mHasMaxRadius) {
            final float halfWidth = mBounds.width() / 2.0f;
            final float halfHeight = mBounds.height() / 2.0f;
            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);

            clampStartingPosition();
        }
    
private voidremoveSelf()

        // The owner will invalidate itself.
        if (!mCanceled) {
            mOwner.removeRipple(this);
        }
    
public voidsetOpacity(float a)

        mOpacity = a;
        invalidateSelf();
    
public voidsetRadiusGravity(float r)

        mTweenRadius = r;
        invalidateSelf();
    
public voidsetXGravity(float x)

        mTweenX = x;
        invalidateSelf();
    
public voidsetYGravity(float y)

        mTweenY = y;
        invalidateSelf();
    
public voidsetup(int maxRadius, float density)

        if (maxRadius != RippleDrawable.RADIUS_AUTO) {
            mHasMaxRadius = true;
            mOuterRadius = maxRadius;
        } else {
            final float halfWidth = mBounds.width() / 2.0f;
            final float halfHeight = mBounds.height() / 2.0f;
            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
        }

        mOuterX = 0;
        mOuterY = 0;
        mDensity = density;

        clampStartingPosition();
    
private voidstartPendingHardwareExit(android.view.HardwareCanvas c, android.graphics.Paint p)

        mHasPendingHardwareExit = false;

        final int radiusDuration = mPendingRadiusDuration;
        final int opacityDuration = mPendingOpacityDuration;

        final float startX = MathUtils.lerp(
                mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
        final float startY = MathUtils.lerp(
                mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);

        final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
        final Paint paint = getTempPaint(p);
        paint.setAlpha((int) (paint.getAlpha() * mOpacity + 0.5f));
        mPropPaint = CanvasProperty.createPaint(paint);
        mPropRadius = CanvasProperty.createFloat(startRadius);
        mPropX = CanvasProperty.createFloat(startX);
        mPropY = CanvasProperty.createFloat(startY);

        final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius);
        radiusAnim.setDuration(radiusDuration);
        radiusAnim.setInterpolator(DECEL_INTERPOLATOR);
        radiusAnim.setTarget(c);
        radiusAnim.start();

        final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX);
        xAnim.setDuration(radiusDuration);
        xAnim.setInterpolator(DECEL_INTERPOLATOR);
        xAnim.setTarget(c);
        xAnim.start();

        final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY);
        yAnim.setDuration(radiusDuration);
        yAnim.setInterpolator(DECEL_INTERPOLATOR);
        yAnim.setTarget(c);
        yAnim.start();

        final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint,
                RenderNodeAnimator.PAINT_ALPHA, 0);
        opacityAnim.setDuration(opacityDuration);
        opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
        opacityAnim.addListener(mAnimationListener);
        opacityAnim.setTarget(c);
        opacityAnim.start();

        mRunningAnimations.add(radiusAnim);
        mRunningAnimations.add(opacityAnim);
        mRunningAnimations.add(xAnim);
        mRunningAnimations.add(yAnim);

        mHardwareAnimating = true;

        // Set up the software values to match the hardware end values.
        mOpacity = 0;
        mTweenX = 1;
        mTweenY = 1;
        mTweenRadius = 1;