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