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