1 /*
2  * Copyright (C) 2024 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.systemui.screenshot.ui
18 
19 import android.animation.ValueAnimator
20 import android.content.res.ColorStateList
21 import android.graphics.Canvas
22 import android.graphics.ColorFilter
23 import android.graphics.drawable.Drawable
24 import androidx.core.animation.doOnEnd
25 import java.util.Objects
26 
27 /**  */
28 class TransitioningIconDrawable : Drawable() {
29     // The drawable for the current icon of this view. During icon transitions, this is the one
30     // being animated out.
31     private var drawable: Drawable? = null
32 
33     // The incoming new icon. Only populated during transition animations (when drawable is also
34     // non-null).
35     private var enteringDrawable: Drawable? = null
36     private var colorFilter: ColorFilter? = null
37     private var tint: ColorStateList? = null
38     private var alpha = 255
39 
40     private var transitionAnimator =
<lambda>null41         ValueAnimator.ofFloat(0f, 1f).also { it.doOnEnd { onTransitionComplete() } }
42 
43     /**
44      * Set the drawable to be displayed, potentially animating the transition from one icon to the
45      * next.
46      */
setIconnull47     fun setIcon(incomingDrawable: Drawable?) {
48         if (Objects.equals(drawable, incomingDrawable) && !transitionAnimator.isRunning) {
49             return
50         }
51 
52         incomingDrawable?.colorFilter = colorFilter
53         incomingDrawable?.setTintList(tint)
54 
55         if (drawable == null) {
56             // No existing icon drawn, just show the new one without a transition
57             drawable = incomingDrawable
58             invalidateSelf()
59             return
60         }
61 
62         if (enteringDrawable != null) {
63             // There's already an entrance animation happening, just update the entering icon, not
64             // maintaining a queue or anything.
65             enteringDrawable = incomingDrawable
66             return
67         }
68 
69         // There was already an icon, need to animate between icons.
70         enteringDrawable = incomingDrawable
71         transitionAnimator.setCurrentFraction(0f)
72         transitionAnimator.start()
73         invalidateSelf()
74     }
75 
drawnull76     override fun draw(canvas: Canvas) {
77         // Scale the old one down, scale the new one up.
78         drawable?.let {
79             val scale =
80                 if (transitionAnimator.isRunning) {
81                     1f - transitionAnimator.animatedFraction
82                 } else {
83                     1f
84                 }
85             drawScaledDrawable(it, canvas, scale)
86         }
87         enteringDrawable?.let {
88             val scale = transitionAnimator.animatedFraction
89             drawScaledDrawable(it, canvas, scale)
90         }
91 
92         if (transitionAnimator.isRunning) {
93             invalidateSelf()
94         }
95     }
96 
drawScaledDrawablenull97     private fun drawScaledDrawable(drawable: Drawable, canvas: Canvas, scale: Float) {
98         drawable.bounds = getBounds()
99         canvas.save()
100         canvas.scale(
101             scale,
102             scale,
103             (drawable.intrinsicWidth / 2).toFloat(),
104             (drawable.intrinsicHeight / 2).toFloat()
105         )
106         drawable.draw(canvas)
107         canvas.restore()
108     }
109 
onTransitionCompletenull110     private fun onTransitionComplete() {
111         drawable = enteringDrawable
112         enteringDrawable = null
113         invalidateSelf()
114     }
115 
setTintListnull116     override fun setTintList(tint: ColorStateList?) {
117         super.setTintList(tint)
118         drawable?.setTintList(tint)
119         enteringDrawable?.setTintList(tint)
120         this.tint = tint
121     }
122 
setAlphanull123     override fun setAlpha(alpha: Int) {
124         this.alpha = alpha
125     }
126 
setColorFilternull127     override fun setColorFilter(colorFilter: ColorFilter?) {
128         this.colorFilter = colorFilter
129         drawable?.colorFilter = colorFilter
130         enteringDrawable?.colorFilter = colorFilter
131     }
132 
getOpacitynull133     override fun getOpacity(): Int = alpha
134 }
135