1 /* 2 * Copyright (C) 2014 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 com.android.deskclock; 18 19 import android.animation.Animator; 20 import android.animation.ArgbEvaluator; 21 import android.animation.ObjectAnimator; 22 import android.animation.PropertyValuesHolder; 23 import android.animation.TypeEvaluator; 24 import android.animation.ValueAnimator; 25 import android.graphics.Rect; 26 import android.graphics.drawable.Animatable; 27 import android.graphics.drawable.Drawable; 28 import android.graphics.drawable.LayerDrawable; 29 import androidx.core.graphics.drawable.DrawableCompat; 30 import androidx.interpolator.view.animation.FastOutSlowInInterpolator; 31 import android.util.Property; 32 import android.view.View; 33 import android.view.animation.Interpolator; 34 import android.widget.ImageView; 35 36 import java.lang.reflect.InvocationTargetException; 37 import java.lang.reflect.Method; 38 39 public class AnimatorUtils { 40 41 public static final Interpolator DECELERATE_ACCELERATE_INTERPOLATOR = new Interpolator() { 42 @Override 43 public float getInterpolation(float x) { 44 return 0.5f + 4.0f * (x - 0.5f) * (x - 0.5f) * (x - 0.5f); 45 } 46 }; 47 48 public static final Interpolator INTERPOLATOR_FAST_OUT_SLOW_IN = 49 new FastOutSlowInInterpolator(); 50 51 public static final Property<View, Integer> BACKGROUND_ALPHA = 52 new Property<View, Integer>(Integer.class, "background.alpha") { 53 @Override 54 public Integer get(View view) { 55 Drawable background = view.getBackground(); 56 if (background instanceof LayerDrawable 57 && ((LayerDrawable) background).getNumberOfLayers() > 0) { 58 background = ((LayerDrawable) background).getDrawable(0); 59 } 60 return background.getAlpha(); 61 } 62 63 @Override 64 public void set(View view, Integer value) { 65 setBackgroundAlpha(view, value); 66 } 67 }; 68 69 /** 70 * Sets the alpha of the top layer's drawable (of the background) only, if the background is a 71 * layer drawable, to ensure that the other layers (i.e., the selectable item background, and 72 * therefore the touch feedback RippleDrawable) are not affected. 73 * 74 * @param view the affected view 75 * @param value the alpha value (0-255) 76 */ setBackgroundAlpha(View view, Integer value)77 public static void setBackgroundAlpha(View view, Integer value) { 78 Drawable background = view.getBackground(); 79 if (background instanceof LayerDrawable 80 && ((LayerDrawable) background).getNumberOfLayers() > 0) { 81 background = ((LayerDrawable) background).getDrawable(0); 82 } 83 background.setAlpha(value); 84 } 85 86 public static final Property<ImageView, Integer> DRAWABLE_ALPHA = 87 new Property<ImageView, Integer>(Integer.class, "drawable.alpha") { 88 @Override 89 public Integer get(ImageView view) { 90 return view.getDrawable().getAlpha(); 91 } 92 93 @Override 94 public void set(ImageView view, Integer value) { 95 view.getDrawable().setAlpha(value); 96 } 97 }; 98 99 public static final Property<ImageView, Integer> DRAWABLE_TINT = 100 new Property<ImageView, Integer>(Integer.class, "drawable.tint") { 101 @Override 102 public Integer get(ImageView view) { 103 return null; 104 } 105 106 @Override 107 public void set(ImageView view, Integer value) { 108 // Ensure the drawable is wrapped using DrawableCompat. 109 final Drawable drawable = view.getDrawable(); 110 final Drawable wrappedDrawable = DrawableCompat.wrap(drawable); 111 if (wrappedDrawable != drawable) { 112 view.setImageDrawable(wrappedDrawable); 113 } 114 // Set the new tint value via DrawableCompat. 115 DrawableCompat.setTint(wrappedDrawable, value); 116 } 117 }; 118 119 @SuppressWarnings("unchecked") 120 public static final TypeEvaluator<Integer> ARGB_EVALUATOR = new ArgbEvaluator(); 121 122 private static Method sAnimateValue; 123 private static boolean sTryAnimateValue = true; 124 setAnimatedFraction(ValueAnimator animator, float fraction)125 public static void setAnimatedFraction(ValueAnimator animator, float fraction) { 126 if (Utils.isLMR1OrLater()) { 127 animator.setCurrentFraction(fraction); 128 return; 129 } 130 131 if (sTryAnimateValue) { 132 // try to set the animated fraction directly so that it isn't affected by the 133 // internal animator scale or time (b/17938711) 134 try { 135 if (sAnimateValue == null) { 136 sAnimateValue = ValueAnimator.class 137 .getDeclaredMethod("animateValue", float.class); 138 sAnimateValue.setAccessible(true); 139 } 140 141 sAnimateValue.invoke(animator, fraction); 142 return; 143 } catch (NoSuchMethodException | InvocationTargetException 144 | IllegalAccessException e) { 145 // something went wrong, don't try that again 146 LogUtils.e("Unable to use animateValue directly", e); 147 sTryAnimateValue = false; 148 } 149 } 150 151 // if that doesn't work then just fall back to setting the current play time 152 animator.setCurrentPlayTime(Math.round(fraction * animator.getDuration())); 153 } 154 reverse(ValueAnimator... animators)155 public static void reverse(ValueAnimator... animators) { 156 for (ValueAnimator animator : animators) { 157 final float fraction = animator.getAnimatedFraction(); 158 if (fraction > 0.0f) { 159 animator.reverse(); 160 setAnimatedFraction(animator, 1.0f - fraction); 161 } 162 } 163 } 164 cancel(ValueAnimator... animators)165 public static void cancel(ValueAnimator... animators) { 166 for (ValueAnimator animator : animators) { 167 animator.cancel(); 168 } 169 } 170 getScaleAnimator(View view, float... values)171 public static ValueAnimator getScaleAnimator(View view, float... values) { 172 return ObjectAnimator.ofPropertyValuesHolder(view, 173 PropertyValuesHolder.ofFloat(View.SCALE_X, values), 174 PropertyValuesHolder.ofFloat(View.SCALE_Y, values)); 175 } 176 getAlphaAnimator(View view, float... values)177 public static ValueAnimator getAlphaAnimator(View view, float... values) { 178 return ObjectAnimator.ofFloat(view, View.ALPHA, values); 179 } 180 181 public static final Property<View, Integer> VIEW_LEFT = 182 new Property<View, Integer>(Integer.class, "left") { 183 @Override 184 public Integer get(View view) { 185 return view.getLeft(); 186 } 187 188 @Override 189 public void set(View view, Integer left) { 190 view.setLeft(left); 191 } 192 }; 193 194 public static final Property<View, Integer> VIEW_TOP = 195 new Property<View, Integer>(Integer.class, "top") { 196 @Override 197 public Integer get(View view) { 198 return view.getTop(); 199 } 200 201 @Override 202 public void set(View view, Integer top) { 203 view.setTop(top); 204 } 205 }; 206 207 public static final Property<View, Integer> VIEW_BOTTOM = 208 new Property<View, Integer>(Integer.class, "bottom") { 209 @Override 210 public Integer get(View view) { 211 return view.getBottom(); 212 } 213 214 @Override 215 public void set(View view, Integer bottom) { 216 view.setBottom(bottom); 217 } 218 }; 219 220 public static final Property<View, Integer> VIEW_RIGHT = 221 new Property<View, Integer>(Integer.class, "right") { 222 @Override 223 public Integer get(View view) { 224 return view.getRight(); 225 } 226 227 @Override 228 public void set(View view, Integer right) { 229 view.setRight(right); 230 } 231 }; 232 233 /** 234 * @param target the view to be morphed 235 * @param from the bounds of the {@code target} before animating 236 * @param to the bounds of the {@code target} after animating 237 * @return an animator that morphs the {@code target} between the {@code from} bounds and the 238 * {@code to} bounds. Note that it is the *content* bounds that matter here, so padding 239 * insets contributed by the background are subtracted from the views when computing the 240 * {@code target} bounds. 241 */ getBoundsAnimator(View target, View from, View to)242 public static Animator getBoundsAnimator(View target, View from, View to) { 243 // Fetch the content insets for the views. Content bounds are what matter, not total bounds. 244 final Rect targetInsets = new Rect(); 245 target.getBackground().getPadding(targetInsets); 246 final Rect fromInsets = new Rect(); 247 from.getBackground().getPadding(fromInsets); 248 final Rect toInsets = new Rect(); 249 to.getBackground().getPadding(toInsets); 250 251 // Before animating, the content bounds of target must match the content bounds of from. 252 final int startLeft = from.getLeft() - fromInsets.left + targetInsets.left; 253 final int startTop = from.getTop() - fromInsets.top + targetInsets.top; 254 final int startRight = from.getRight() - fromInsets.right + targetInsets.right; 255 final int startBottom = from.getBottom() - fromInsets.bottom + targetInsets.bottom; 256 257 // After animating, the content bounds of target must match the content bounds of to. 258 final int endLeft = to.getLeft() - toInsets.left + targetInsets.left; 259 final int endTop = to.getTop() - toInsets.top + targetInsets.top; 260 final int endRight = to.getRight() - toInsets.right + targetInsets.right; 261 final int endBottom = to.getBottom() - toInsets.bottom + targetInsets.bottom; 262 263 return getBoundsAnimator(target, startLeft, startTop, startRight, startBottom, endLeft, 264 endTop, endRight, endBottom); 265 } 266 267 /** 268 * Returns an animator that animates the bounds of a single view. 269 */ getBoundsAnimator(View view, int fromLeft, int fromTop, int fromRight, int fromBottom, int toLeft, int toTop, int toRight, int toBottom)270 public static Animator getBoundsAnimator(View view, int fromLeft, int fromTop, int fromRight, 271 int fromBottom, int toLeft, int toTop, int toRight, int toBottom) { 272 view.setLeft(fromLeft); 273 view.setTop(fromTop); 274 view.setRight(fromRight); 275 view.setBottom(fromBottom); 276 277 return ObjectAnimator.ofPropertyValuesHolder(view, 278 PropertyValuesHolder.ofInt(VIEW_LEFT, toLeft), 279 PropertyValuesHolder.ofInt(VIEW_TOP, toTop), 280 PropertyValuesHolder.ofInt(VIEW_RIGHT, toRight), 281 PropertyValuesHolder.ofInt(VIEW_BOTTOM, toBottom)); 282 } 283 startDrawableAnimation(ImageView view)284 public static void startDrawableAnimation(ImageView view) { 285 final Drawable d = view.getDrawable(); 286 if (d instanceof Animatable) { 287 ((Animatable) d).start(); 288 } 289 } 290 }