1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.graphics.drawable; 18 19 import android.animation.Animator; 20 import android.graphics.Canvas; 21 import android.graphics.Paint; 22 import android.graphics.Rect; 23 import android.view.DisplayListCanvas; 24 import android.view.RenderNodeAnimator; 25 26 import java.util.ArrayList; 27 28 /** 29 * Abstract class that handles hardware/software hand-off and lifecycle for 30 * animated ripple foreground and background components. 31 */ 32 abstract class RippleComponent { 33 private final RippleDrawable mOwner; 34 35 /** Bounds used for computing max radius. May be modified by the owner. */ 36 protected final Rect mBounds; 37 38 /** Whether we can use hardware acceleration for the exit animation. */ 39 private boolean mHasDisplayListCanvas; 40 41 private boolean mHasPendingHardwareAnimator; 42 private RenderNodeAnimatorSet mHardwareAnimator; 43 44 private Animator mSoftwareAnimator; 45 46 /** Whether we have an explicit maximum radius. */ 47 private boolean mHasMaxRadius; 48 49 /** How big this ripple should be when fully entered. */ 50 protected float mTargetRadius; 51 52 /** Screen density used to adjust pixel-based constants. */ 53 protected float mDensity; 54 RippleComponent(RippleDrawable owner, Rect bounds)55 public RippleComponent(RippleDrawable owner, Rect bounds) { 56 mOwner = owner; 57 mBounds = bounds; 58 } 59 onBoundsChange()60 public void onBoundsChange() { 61 if (!mHasMaxRadius) { 62 mTargetRadius = getTargetRadius(mBounds); 63 onTargetRadiusChanged(mTargetRadius); 64 } 65 } 66 setup(float maxRadius, float density)67 public final void setup(float maxRadius, float density) { 68 if (maxRadius >= 0) { 69 mHasMaxRadius = true; 70 mTargetRadius = maxRadius; 71 } else { 72 mTargetRadius = getTargetRadius(mBounds); 73 } 74 75 mDensity = density; 76 77 onTargetRadiusChanged(mTargetRadius); 78 } 79 getTargetRadius(Rect bounds)80 private static float getTargetRadius(Rect bounds) { 81 final float halfWidth = bounds.width() / 2.0f; 82 final float halfHeight = bounds.height() / 2.0f; 83 return (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight); 84 } 85 86 /** 87 * Starts a ripple enter animation. 88 * 89 * @param fast whether the ripple should enter quickly 90 */ enter(boolean fast)91 public final void enter(boolean fast) { 92 cancel(); 93 94 mSoftwareAnimator = createSoftwareEnter(fast); 95 96 if (mSoftwareAnimator != null) { 97 mSoftwareAnimator.start(); 98 } 99 } 100 101 /** 102 * Starts a ripple exit animation. 103 */ exit()104 public final void exit() { 105 cancel(); 106 107 if (mHasDisplayListCanvas) { 108 // We don't have access to a canvas here, but we expect one on the 109 // next frame. We'll start the render thread animation then. 110 mHasPendingHardwareAnimator = true; 111 112 // Request another frame. 113 invalidateSelf(); 114 } else { 115 mSoftwareAnimator = createSoftwareExit(); 116 mSoftwareAnimator.start(); 117 } 118 } 119 120 /** 121 * Cancels all animations. Software animation values are left in the 122 * current state, while hardware animation values jump to the end state. 123 */ cancel()124 public void cancel() { 125 cancelSoftwareAnimations(); 126 endHardwareAnimations(); 127 } 128 129 /** 130 * Ends all animations, jumping values to the end state. 131 */ end()132 public void end() { 133 endSoftwareAnimations(); 134 endHardwareAnimations(); 135 } 136 137 /** 138 * Draws the ripple to the canvas, inheriting the paint's color and alpha 139 * properties. 140 * 141 * @param c the canvas to which the ripple should be drawn 142 * @param p the paint used to draw the ripple 143 * @return {@code true} if something was drawn, {@code false} otherwise 144 */ draw(Canvas c, Paint p)145 public boolean draw(Canvas c, Paint p) { 146 final boolean hasDisplayListCanvas = c.isHardwareAccelerated() 147 && c instanceof DisplayListCanvas; 148 if (mHasDisplayListCanvas != hasDisplayListCanvas) { 149 mHasDisplayListCanvas = hasDisplayListCanvas; 150 151 if (!hasDisplayListCanvas) { 152 // We've switched from hardware to non-hardware mode. Panic. 153 endHardwareAnimations(); 154 } 155 } 156 157 if (hasDisplayListCanvas) { 158 final DisplayListCanvas hw = (DisplayListCanvas) c; 159 startPendingAnimation(hw, p); 160 161 if (mHardwareAnimator != null) { 162 return drawHardware(hw); 163 } 164 } 165 166 return drawSoftware(c, p); 167 } 168 169 /** 170 * Populates {@code bounds} with the maximum drawing bounds of the ripple 171 * relative to its center. The resulting bounds should be translated into 172 * parent drawable coordinates before use. 173 * 174 * @param bounds the rect to populate with drawing bounds 175 */ getBounds(Rect bounds)176 public void getBounds(Rect bounds) { 177 final int r = (int) Math.ceil(mTargetRadius); 178 bounds.set(-r, -r, r, r); 179 } 180 181 /** 182 * Starts the pending hardware animation, if available. 183 * 184 * @param hw hardware canvas on which the animation should draw 185 * @param p paint whose properties the hardware canvas should use 186 */ startPendingAnimation(DisplayListCanvas hw, Paint p)187 private void startPendingAnimation(DisplayListCanvas hw, Paint p) { 188 if (mHasPendingHardwareAnimator) { 189 mHasPendingHardwareAnimator = false; 190 191 mHardwareAnimator = createHardwareExit(new Paint(p)); 192 mHardwareAnimator.start(hw); 193 194 // Preemptively jump the software values to the end state now that 195 // the hardware exit has read whatever values it needs. 196 jumpValuesToExit(); 197 } 198 } 199 200 /** 201 * Cancels any current software animations, leaving the values in their 202 * current state. 203 */ cancelSoftwareAnimations()204 private void cancelSoftwareAnimations() { 205 if (mSoftwareAnimator != null) { 206 mSoftwareAnimator.cancel(); 207 mSoftwareAnimator = null; 208 } 209 } 210 211 /** 212 * Ends any current software animations, jumping the values to their end 213 * state. 214 */ endSoftwareAnimations()215 private void endSoftwareAnimations() { 216 if (mSoftwareAnimator != null) { 217 mSoftwareAnimator.end(); 218 mSoftwareAnimator = null; 219 } 220 } 221 222 /** 223 * Ends any pending or current hardware animations. 224 * <p> 225 * Hardware animations can't synchronize values back to the software 226 * thread, so there is no "cancel" equivalent. 227 */ endHardwareAnimations()228 private void endHardwareAnimations() { 229 if (mHardwareAnimator != null) { 230 mHardwareAnimator.end(); 231 mHardwareAnimator = null; 232 } 233 234 if (mHasPendingHardwareAnimator) { 235 mHasPendingHardwareAnimator = false; 236 237 // Manually jump values to their exited state. Normally we'd do that 238 // later when starting the hardware exit, but we're aborting early. 239 jumpValuesToExit(); 240 } 241 } 242 invalidateSelf()243 protected final void invalidateSelf() { 244 mOwner.invalidateSelf(false); 245 } 246 isHardwareAnimating()247 protected final boolean isHardwareAnimating() { 248 return mHardwareAnimator != null && mHardwareAnimator.isRunning() 249 || mHasPendingHardwareAnimator; 250 } 251 onHotspotBoundsChanged()252 protected final void onHotspotBoundsChanged() { 253 if (!mHasMaxRadius) { 254 final float halfWidth = mBounds.width() / 2.0f; 255 final float halfHeight = mBounds.height() / 2.0f; 256 final float targetRadius = (float) Math.sqrt(halfWidth * halfWidth 257 + halfHeight * halfHeight); 258 259 onTargetRadiusChanged(targetRadius); 260 } 261 } 262 263 /** 264 * Called when the target radius changes. 265 * 266 * @param targetRadius the new target radius 267 */ onTargetRadiusChanged(float targetRadius)268 protected void onTargetRadiusChanged(float targetRadius) { 269 // Stub. 270 } 271 createSoftwareEnter(boolean fast)272 protected abstract Animator createSoftwareEnter(boolean fast); 273 createSoftwareExit()274 protected abstract Animator createSoftwareExit(); 275 createHardwareExit(Paint p)276 protected abstract RenderNodeAnimatorSet createHardwareExit(Paint p); 277 drawHardware(DisplayListCanvas c)278 protected abstract boolean drawHardware(DisplayListCanvas c); 279 drawSoftware(Canvas c, Paint p)280 protected abstract boolean drawSoftware(Canvas c, Paint p); 281 282 /** 283 * Called when the hardware exit is cancelled. Jumps software values to end 284 * state to ensure that software and hardware values are synchronized. 285 */ jumpValuesToExit()286 protected abstract void jumpValuesToExit(); 287 288 public static class RenderNodeAnimatorSet { 289 private final ArrayList<RenderNodeAnimator> mAnimators = new ArrayList<>(); 290 add(RenderNodeAnimator anim)291 public void add(RenderNodeAnimator anim) { 292 mAnimators.add(anim); 293 } 294 clear()295 public void clear() { 296 mAnimators.clear(); 297 } 298 start(DisplayListCanvas target)299 public void start(DisplayListCanvas target) { 300 if (target == null) { 301 throw new IllegalArgumentException("Hardware canvas must be non-null"); 302 } 303 304 final ArrayList<RenderNodeAnimator> animators = mAnimators; 305 final int N = animators.size(); 306 for (int i = 0; i < N; i++) { 307 final RenderNodeAnimator anim = animators.get(i); 308 anim.setTarget(target); 309 anim.start(); 310 } 311 } 312 cancel()313 public void cancel() { 314 final ArrayList<RenderNodeAnimator> animators = mAnimators; 315 final int N = animators.size(); 316 for (int i = 0; i < N; i++) { 317 final RenderNodeAnimator anim = animators.get(i); 318 anim.cancel(); 319 } 320 } 321 end()322 public void end() { 323 final ArrayList<RenderNodeAnimator> animators = mAnimators; 324 final int N = animators.size(); 325 for (int i = 0; i < N; i++) { 326 final RenderNodeAnimator anim = animators.get(i); 327 anim.end(); 328 } 329 } 330 isRunning()331 public boolean isRunning() { 332 final ArrayList<RenderNodeAnimator> animators = mAnimators; 333 final int N = animators.size(); 334 for (int i = 0; i < N; i++) { 335 final RenderNodeAnimator anim = animators.get(i); 336 if (anim.isRunning()) { 337 return true; 338 } 339 } 340 return false; 341 } 342 } 343 } 344