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