• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }