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