15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/*
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Copyright (C) 2013 The Android Open Source Project
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Licensed under the Apache License, Version 2.0 (the "License");
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * you may not use this file except in compliance with the License.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * You may obtain a copy of the License at
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *      http://www.apache.org/licenses/LICENSE-2.0
91e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) *
10d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * Unless required by applicable law or agreed to in writing, software
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * distributed under the License is distributed on an "AS IS" BASIS,
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * See the License for the specific language governing permissions and
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * limitations under the License.
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)package android.graphics.drawable;
182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.animation.Animator;
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.animation.AnimatorListenerAdapter;
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.animation.ObjectAnimator;
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.animation.TimeInterpolator;
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.Canvas;
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.CanvasProperty;
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.Paint;
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.graphics.Rect;
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.util.MathUtils;
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.view.HardwareCanvas;
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.view.RenderNodeAnimator;
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.view.animation.LinearInterpolator;
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.util.ArrayList;
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Draws a Material ripple.
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Ripple {
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final TimeInterpolator DECEL_INTERPOLATOR = new LogInterpolator();
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final float GLOBAL_SPEED = 1.0f;
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0f * GLOBAL_SPEED;
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final float WAVE_TOUCH_UP_ACCELERATION = 3400.0f * GLOBAL_SPEED;
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final long RIPPLE_ENTER_DELAY = 80;
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Hardware animators.
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private final ArrayList<RenderNodeAnimator> mRunningAnimations =
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            new ArrayList<RenderNodeAnimator>();
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private final RippleDrawable mOwner;
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Bounds used for computing max radius. */
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private final Rect mBounds;
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    /** Maximum ripple radius. */
58b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    private float mOuterRadius;
59b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /** Screen density used to adjust pixel-based velocities. */
61b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)    private float mDensity;
62c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    private float mStartingX;
64c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    private float mStartingY;
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private float mClampedStartingX;
66eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    private float mClampedStartingY;
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Hardware rendering properties.
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private CanvasProperty<Paint> mPropPaint;
70eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    private CanvasProperty<Float> mPropRadius;
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private CanvasProperty<Float> mPropX;
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private CanvasProperty<Float> mPropY;
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
74eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    // Software animators.
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private ObjectAnimator mAnimRadius;
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private ObjectAnimator mAnimOpacity;
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private ObjectAnimator mAnimX;
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private ObjectAnimator mAnimY;
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Temporary paint used for creating canvas properties.
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private Paint mTempPaint;
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
83a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    // Software rendering properties.
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private float mOpacity = 1;
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private float mOuterX;
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private float mOuterY;
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Values used to tween between the start and end positions.
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private float mTweenRadius = 0;
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private float mTweenX = 0;
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private float mTweenY = 0;
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Whether we should be drawing hardware animations. */
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private boolean mHardwareAnimating;
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Whether we can use hardware acceleration for the exit animation. */
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private boolean mCanUseHardware;
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Whether we have an explicit maximum radius. */
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private boolean mHasMaxRadius;
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Whether we were canceled externally and should avoid self-removal. */
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private boolean mCanceled;
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private boolean mHasPendingHardwareExit;
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private int mPendingRadiusDuration;
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private int mPendingOpacityDuration;
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Creates a new ripple.
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public Ripple(RippleDrawable owner, Rect bounds, float startingX, float startingY) {
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mOwner = owner;
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mBounds = bounds;
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mStartingX = startingX;
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mStartingY = startingY;
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public void setup(int maxRadius, float density) {
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (maxRadius != RippleDrawable.RADIUS_AUTO) {
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            mHasMaxRadius = true;
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            mOuterRadius = maxRadius;
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        } else {
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            final float halfWidth = mBounds.width() / 2.0f;
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            final float halfHeight = mBounds.height() / 2.0f;
127eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mOuterX = 0;
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mOuterY = 0;
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mDensity = density;
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        clampStartingPosition();
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public boolean isHardwareAnimating() {
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return mHardwareAnimating;
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private void clampStartingPosition() {
1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        final float cX = mBounds.exactCenterX();
1432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        final float cY = mBounds.exactCenterY();
1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        final float dX = mStartingX - cX;
1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        final float dY = mStartingY - cY;
1462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        final float r = mOuterRadius;
1472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if (dX * dX + dY * dY > r * r) {
1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            // Point is outside the circle, clamp to the circumference.
1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            final double angle = Math.atan2(dY, dX);
1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            mClampedStartingX = cX + (float) (Math.cos(angle) * r);
1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            mClampedStartingY = cY + (float) (Math.sin(angle) * r);
1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        } else {
1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            mClampedStartingX = mStartingX;
1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            mClampedStartingY = mStartingY;
1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    public void onHotspotBoundsChanged() {
1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if (!mHasMaxRadius) {
1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            final float halfWidth = mBounds.width() / 2.0f;
1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            final float halfHeight = mBounds.height() / 2.0f;
1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            clampStartingPosition();
1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    public void setOpacity(float a) {
1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        mOpacity = a;
1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        invalidateSelf();
1712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    public float getOpacity() {
1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        return mOpacity;
1752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    @SuppressWarnings("unused")
1782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    public void setRadiusGravity(float r) {
1792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        mTweenRadius = r;
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        invalidateSelf();
1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
182c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
1835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    @SuppressWarnings("unused")
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public float getRadiusGravity() {
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return mTweenRadius;
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @SuppressWarnings("unused")
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public void setXGravity(float x) {
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mTweenX = x;
1917dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch        invalidateSelf();
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @SuppressWarnings("unused")
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public float getXGravity() {
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return mTweenX;
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @SuppressWarnings("unused")
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public void setYGravity(float y) {
201eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        mTweenY = y;
202f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        invalidateSelf();
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @SuppressWarnings("unused")
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public float getYGravity() {
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return mTweenY;
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
209f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Draws the ripple centered at (0,0) using the specified paint.
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public boolean draw(Canvas c, Paint p) {
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        final boolean canUseHardware = c.isHardwareAccelerated();
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (mCanUseHardware != canUseHardware && mCanUseHardware) {
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            // We've switched from hardware to non-hardware mode. Panic.
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            cancelHardwareAnimations(true);
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mCanUseHardware = canUseHardware;
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        final boolean hasContent;
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) {
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            hasContent = drawHardware((HardwareCanvas) c, p);
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        } else {
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            hasContent = drawSoftware(c, p);
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
228        return hasContent;
229    }
230
231    private boolean drawHardware(HardwareCanvas c, Paint p) {
232        if (mHasPendingHardwareExit) {
233            cancelHardwareAnimations(false);
234            startPendingHardwareExit(c, p);
235        }
236
237        c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
238
239        return true;
240    }
241
242    private boolean drawSoftware(Canvas c, Paint p) {
243        boolean hasContent = false;
244
245        final int paintAlpha = p.getAlpha();
246        final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
247        final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
248        if (alpha > 0 && radius > 0) {
249            final float x = MathUtils.lerp(
250                    mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
251            final float y = MathUtils.lerp(
252                    mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
253            p.setAlpha(alpha);
254            c.drawCircle(x, y, radius, p);
255            p.setAlpha(paintAlpha);
256            hasContent = true;
257        }
258
259        return hasContent;
260    }
261
262    /**
263     * Returns the maximum bounds of the ripple relative to the ripple center.
264     */
265    public void getBounds(Rect bounds) {
266        final int outerX = (int) mOuterX;
267        final int outerY = (int) mOuterY;
268        final int r = (int) mOuterRadius + 1;
269        bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
270    }
271
272    /**
273     * Specifies the starting position relative to the drawable bounds. No-op if
274     * the ripple has already entered.
275     */
276    public void move(float x, float y) {
277        mStartingX = x;
278        mStartingY = y;
279
280        clampStartingPosition();
281    }
282
283    /**
284     * Starts the enter animation.
285     */
286    public void enter() {
287        cancel();
288
289        final int radiusDuration = (int)
290                (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
291
292        final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
293        radius.setAutoCancel(true);
294        radius.setDuration(radiusDuration);
295        radius.setInterpolator(LINEAR_INTERPOLATOR);
296        radius.setStartDelay(RIPPLE_ENTER_DELAY);
297
298        final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1);
299        cX.setAutoCancel(true);
300        cX.setDuration(radiusDuration);
301        cX.setInterpolator(LINEAR_INTERPOLATOR);
302        cX.setStartDelay(RIPPLE_ENTER_DELAY);
303
304        final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", 1);
305        cY.setAutoCancel(true);
306        cY.setDuration(radiusDuration);
307        cY.setInterpolator(LINEAR_INTERPOLATOR);
308        cY.setStartDelay(RIPPLE_ENTER_DELAY);
309
310        mAnimRadius = radius;
311        mAnimX = cX;
312        mAnimY = cY;
313
314        // Enter animations always run on the UI thread, since it's unlikely
315        // that anything interesting is happening until the user lifts their
316        // finger.
317        radius.start();
318        cX.start();
319        cY.start();
320    }
321
322    /**
323     * Starts the exit animation.
324     */
325    public void exit() {
326        final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
327        final float remaining;
328        if (mAnimRadius != null && mAnimRadius.isRunning()) {
329            remaining = mOuterRadius - radius;
330        } else {
331            remaining = mOuterRadius;
332        }
333
334        cancel();
335
336        final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
337                + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
338        final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
339
340        if (mCanUseHardware) {
341            createPendingHardwareExit(radiusDuration, opacityDuration);
342        } else {
343            exitSoftware(radiusDuration, opacityDuration);
344        }
345    }
346
347    private void createPendingHardwareExit(int radiusDuration, int opacityDuration) {
348        mHasPendingHardwareExit = true;
349        mPendingRadiusDuration = radiusDuration;
350        mPendingOpacityDuration = opacityDuration;
351
352        // The animation will start on the next draw().
353        invalidateSelf();
354    }
355
356    private void startPendingHardwareExit(HardwareCanvas c, Paint p) {
357        mHasPendingHardwareExit = false;
358
359        final int radiusDuration = mPendingRadiusDuration;
360        final int opacityDuration = mPendingOpacityDuration;
361
362        final float startX = MathUtils.lerp(
363                mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
364        final float startY = MathUtils.lerp(
365                mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
366
367        final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
368        final Paint paint = getTempPaint(p);
369        paint.setAlpha((int) (paint.getAlpha() * mOpacity + 0.5f));
370        mPropPaint = CanvasProperty.createPaint(paint);
371        mPropRadius = CanvasProperty.createFloat(startRadius);
372        mPropX = CanvasProperty.createFloat(startX);
373        mPropY = CanvasProperty.createFloat(startY);
374
375        final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius);
376        radiusAnim.setDuration(radiusDuration);
377        radiusAnim.setInterpolator(DECEL_INTERPOLATOR);
378        radiusAnim.setTarget(c);
379        radiusAnim.start();
380
381        final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX);
382        xAnim.setDuration(radiusDuration);
383        xAnim.setInterpolator(DECEL_INTERPOLATOR);
384        xAnim.setTarget(c);
385        xAnim.start();
386
387        final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY);
388        yAnim.setDuration(radiusDuration);
389        yAnim.setInterpolator(DECEL_INTERPOLATOR);
390        yAnim.setTarget(c);
391        yAnim.start();
392
393        final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint,
394                RenderNodeAnimator.PAINT_ALPHA, 0);
395        opacityAnim.setDuration(opacityDuration);
396        opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
397        opacityAnim.addListener(mAnimationListener);
398        opacityAnim.setTarget(c);
399        opacityAnim.start();
400
401        mRunningAnimations.add(radiusAnim);
402        mRunningAnimations.add(opacityAnim);
403        mRunningAnimations.add(xAnim);
404        mRunningAnimations.add(yAnim);
405
406        mHardwareAnimating = true;
407
408        // Set up the software values to match the hardware end values.
409        mOpacity = 0;
410        mTweenX = 1;
411        mTweenY = 1;
412        mTweenRadius = 1;
413    }
414
415    /**
416     * Jump all animations to their end state. The caller is responsible for
417     * removing the ripple from the list of animating ripples.
418     */
419    public void jump() {
420        mCanceled = true;
421        endSoftwareAnimations();
422        cancelHardwareAnimations(true);
423        mCanceled = false;
424    }
425
426    private void endSoftwareAnimations() {
427        if (mAnimRadius != null) {
428            mAnimRadius.end();
429            mAnimRadius = null;
430        }
431
432        if (mAnimOpacity != null) {
433            mAnimOpacity.end();
434            mAnimOpacity = null;
435        }
436
437        if (mAnimX != null) {
438            mAnimX.end();
439            mAnimX = null;
440        }
441
442        if (mAnimY != null) {
443            mAnimY.end();
444            mAnimY = null;
445        }
446    }
447
448    private Paint getTempPaint(Paint original) {
449        if (mTempPaint == null) {
450            mTempPaint = new Paint();
451        }
452        mTempPaint.set(original);
453        return mTempPaint;
454    }
455
456    private void exitSoftware(int radiusDuration, int opacityDuration) {
457        final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
458        radiusAnim.setAutoCancel(true);
459        radiusAnim.setDuration(radiusDuration);
460        radiusAnim.setInterpolator(DECEL_INTERPOLATOR);
461
462        final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1);
463        xAnim.setAutoCancel(true);
464        xAnim.setDuration(radiusDuration);
465        xAnim.setInterpolator(DECEL_INTERPOLATOR);
466
467        final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1);
468        yAnim.setAutoCancel(true);
469        yAnim.setDuration(radiusDuration);
470        yAnim.setInterpolator(DECEL_INTERPOLATOR);
471
472        final ObjectAnimator opacityAnim = ObjectAnimator.ofFloat(this, "opacity", 0);
473        opacityAnim.setAutoCancel(true);
474        opacityAnim.setDuration(opacityDuration);
475        opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
476        opacityAnim.addListener(mAnimationListener);
477
478        mAnimRadius = radiusAnim;
479        mAnimOpacity = opacityAnim;
480        mAnimX = xAnim;
481        mAnimY = yAnim;
482
483        radiusAnim.start();
484        opacityAnim.start();
485        xAnim.start();
486        yAnim.start();
487    }
488
489    /**
490     * Cancels all animations. The caller is responsible for removing
491     * the ripple from the list of animating ripples.
492     */
493    public void cancel() {
494        mCanceled = true;
495        cancelSoftwareAnimations();
496        cancelHardwareAnimations(false);
497        mCanceled = false;
498    }
499
500    private void cancelSoftwareAnimations() {
501        if (mAnimRadius != null) {
502            mAnimRadius.cancel();
503            mAnimRadius = null;
504        }
505
506        if (mAnimOpacity != null) {
507            mAnimOpacity.cancel();
508            mAnimOpacity = null;
509        }
510
511        if (mAnimX != null) {
512            mAnimX.cancel();
513            mAnimX = null;
514        }
515
516        if (mAnimY != null) {
517            mAnimY.cancel();
518            mAnimY = null;
519        }
520    }
521
522    /**
523     * Cancels any running hardware animations.
524     */
525    private void cancelHardwareAnimations(boolean jumpToEnd) {
526        final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
527        final int N = runningAnimations.size();
528        for (int i = 0; i < N; i++) {
529            if (jumpToEnd) {
530                runningAnimations.get(i).end();
531            } else {
532                runningAnimations.get(i).cancel();
533            }
534        }
535        runningAnimations.clear();
536
537        if (mHasPendingHardwareExit) {
538            // If we had a pending hardware exit, jump to the end state.
539            mHasPendingHardwareExit = false;
540
541            if (jumpToEnd) {
542                mOpacity = 0;
543                mTweenX = 1;
544                mTweenY = 1;
545                mTweenRadius = 1;
546            }
547        }
548
549        mHardwareAnimating = false;
550    }
551
552    private void removeSelf() {
553        // The owner will invalidate itself.
554        if (!mCanceled) {
555            mOwner.removeRipple(this);
556        }
557    }
558
559    private void invalidateSelf() {
560        mOwner.invalidateSelf();
561    }
562
563    private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
564        @Override
565        public void onAnimationEnd(Animator animation) {
566            removeSelf();
567        }
568    };
569
570    /**
571    * Interpolator with a smooth log deceleration
572    */
573    private static final class LogInterpolator implements TimeInterpolator {
574        @Override
575        public float getInterpolation(float input) {
576            return 1 - (float) Math.pow(400, -input * 1.4);
577        }
578    }
579}
580