FileDocCategorySizeDatePackage
RippleBackground.javaAPI DocAndroid 5.1 API16116Thu Mar 12 22:22:30 GMT 2015android.graphics.drawable

RippleBackground

public class RippleBackground extends Object
Draws a Material ripple.

Fields Summary
private static final android.animation.TimeInterpolator
LINEAR_INTERPOLATOR
private static final float
GLOBAL_SPEED
private static final float
WAVE_OPACITY_DECAY_VELOCITY
private static final float
WAVE_OUTER_OPACITY_EXIT_VELOCITY_MAX
private static final float
WAVE_OUTER_OPACITY_EXIT_VELOCITY_MIN
private static final float
WAVE_OUTER_SIZE_INFLUENCE_MAX
private static final float
WAVE_OUTER_SIZE_INFLUENCE_MIN
private static final int
ENTER_DURATION
private static final int
ENTER_DURATION_FAST
private final ArrayList
mRunningAnimations
private final RippleDrawable
mOwner
private final android.graphics.Rect
mBounds
Bounds used for computing max radius.
private int
mColor
ARGB color for drawing this ripple.
private float
mOuterRadius
Maximum ripple radius.
private float
mDensity
Screen density used to adjust pixel-based velocities.
private android.graphics.CanvasProperty
mPropOuterPaint
private android.graphics.CanvasProperty
mPropOuterRadius
private android.graphics.CanvasProperty
mPropOuterX
private android.graphics.CanvasProperty
mPropOuterY
private android.animation.ObjectAnimator
mAnimOuterOpacity
private android.graphics.Paint
mTempPaint
private float
mOuterOpacity
private float
mOuterX
private float
mOuterY
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
mHasPendingHardwareExit
private int
mPendingOpacityDuration
private int
mPendingInflectionDuration
private int
mPendingInflectionOpacity
private final android.animation.AnimatorListenerAdapter
mAnimationListener
Constructors Summary
public RippleBackground(RippleDrawable owner, android.graphics.Rect bounds)
Creates a new ripple.


             
         
        mOwner = owner;
        mBounds = bounds;
    
Methods Summary
public voidcancel()
Cancel all animations. The caller is responsible for removing the ripple from the list of animating ripples.

        cancelSoftwareAnimations();
        cancelHardwareAnimations(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) {
                mOuterOpacity = 0;
            }
        }

        mHardwareAnimating = false;
    
private voidcancelSoftwareAnimations()

        if (mAnimOuterOpacity != null) {
            mAnimOuterOpacity.cancel();
            mAnimOuterOpacity = null;
        }
    
private voidcreatePendingHardwareExit(int opacityDuration, int inflectionDuration, int inflectionOpacity)

        mHasPendingHardwareExit = true;
        mPendingOpacityDuration = opacityDuration;
        mPendingInflectionDuration = inflectionDuration;
        mPendingInflectionOpacity = inflectionOpacity;

        // 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.

        mColor = p.getColor();

        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(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);

        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 * mOuterOpacity + 0.5f);
        final float radius = mOuterRadius;
        if (alpha > 0 && radius > 0) {
            p.setAlpha(alpha);
            c.drawCircle(mOuterX, mOuterY, radius, p);
            p.setAlpha(paintAlpha);
            hasContent = true;
        }

        return hasContent;
    
private voidendSoftwareAnimations()

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

        cancel();

        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
        opacity.setAutoCancel(true);
        opacity.setDuration(fast ? ENTER_DURATION_FAST : ENTER_DURATION);
        opacity.setInterpolator(LINEAR_INTERPOLATOR);

        mAnimOuterOpacity = opacity;

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

        cancel();

        // Scale the outer max opacity and opacity velocity based
        // on the size of the outer radius.
        final int opacityDuration = (int) (1000 / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
        final float outerSizeInfluence = MathUtils.constrain(
                (mOuterRadius - WAVE_OUTER_SIZE_INFLUENCE_MIN * mDensity)
                / (WAVE_OUTER_SIZE_INFLUENCE_MAX * mDensity), 0, 1);
        final float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_EXIT_VELOCITY_MIN,
                WAVE_OUTER_OPACITY_EXIT_VELOCITY_MAX, outerSizeInfluence);

        // Determine at what time the inner and outer opacity intersect.
        // inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000
        // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
        final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
                / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
        final int inflectionOpacity = (int) (Color.alpha(mColor) * (mOuterOpacity
                + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);

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

        final ObjectAnimator outerOpacityAnim;
        if (inflectionDuration > 0) {
            // Outer opacity continues to increase for a bit.
            outerOpacityAnim = ObjectAnimator.ofFloat(this,
                    "outerOpacity", inflectionOpacity / 255.0f);
            outerOpacityAnim.setAutoCancel(true);
            outerOpacityAnim.setDuration(inflectionDuration);
            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);

            // Chain the outer opacity exit animation.
            final int outerDuration = opacityDuration - inflectionDuration;
            if (outerDuration > 0) {
                outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(
                                RippleBackground.this, "outerOpacity", 0);
                        outerFadeOutAnim.setAutoCancel(true);
                        outerFadeOutAnim.setDuration(outerDuration);
                        outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
                        outerFadeOutAnim.addListener(mAnimationListener);

                        mAnimOuterOpacity = outerFadeOutAnim;

                        outerFadeOutAnim.start();
                    }

                    @Override
                    public void onAnimationCancel(Animator animation) {
                        animation.removeListener(this);
                    }
                });
            } else {
                outerOpacityAnim.addListener(mAnimationListener);
            }
        } else {
            outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
            outerOpacityAnim.setAutoCancel(true);
            outerOpacityAnim.setDuration(opacityDuration);
            outerOpacityAnim.addListener(mAnimationListener);
        }

        mAnimOuterOpacity = outerOpacityAnim;

        outerOpacityAnim.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 floatgetOuterOpacity()

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

        if (mTempPaint == null) {
            mTempPaint = new Paint();
        }
        mTempPaint.set(original);
        return mTempPaint;
    
private voidinvalidateSelf()

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

        endSoftwareAnimations();
        cancelHardwareAnimations(true);
    
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);
        }
    
public voidsetOuterOpacity(float a)

        mOuterOpacity = a;
        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;
    
public booleanshouldDraw()

        return (mCanUseHardware && mHardwareAnimating) || (mOuterOpacity > 0 && mOuterRadius > 0);
    
private voidstartPendingHardwareExit(android.view.HardwareCanvas c, android.graphics.Paint p)

        mHasPendingHardwareExit = false;

        final int opacityDuration = mPendingOpacityDuration;
        final int inflectionDuration = mPendingInflectionDuration;
        final int inflectionOpacity = mPendingInflectionOpacity;

        final Paint outerPaint = getTempPaint(p);
        outerPaint.setAlpha((int) (outerPaint.getAlpha() * mOuterOpacity + 0.5f));
        mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
        mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
        mPropOuterX = CanvasProperty.createFloat(mOuterX);
        mPropOuterY = CanvasProperty.createFloat(mOuterY);

        final RenderNodeAnimator outerOpacityAnim;
        if (inflectionDuration > 0) {
            // Outer opacity continues to increase for a bit.
            outerOpacityAnim = new RenderNodeAnimator(mPropOuterPaint,
                    RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
            outerOpacityAnim.setDuration(inflectionDuration);
            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);

            // Chain the outer opacity exit animation.
            final int outerDuration = opacityDuration - inflectionDuration;
            if (outerDuration > 0) {
                final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
                        mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
                outerFadeOutAnim.setDuration(outerDuration);
                outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
                outerFadeOutAnim.setStartDelay(inflectionDuration);
                outerFadeOutAnim.setStartValue(inflectionOpacity);
                outerFadeOutAnim.addListener(mAnimationListener);
                outerFadeOutAnim.setTarget(c);
                outerFadeOutAnim.start();

                mRunningAnimations.add(outerFadeOutAnim);
            } else {
                outerOpacityAnim.addListener(mAnimationListener);
            }
        } else {
            outerOpacityAnim = new RenderNodeAnimator(
                    mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
            outerOpacityAnim.setDuration(opacityDuration);
            outerOpacityAnim.addListener(mAnimationListener);
        }

        outerOpacityAnim.setTarget(c);
        outerOpacityAnim.start();

        mRunningAnimations.add(outerOpacityAnim);

        mHardwareAnimating = true;

        // Set up the software values to match the hardware end values.
        mOuterOpacity = 0;