1 /* 2 * 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.systemui.media.controls.ui.drawable 18 19 import android.animation.Animator 20 import android.animation.AnimatorListenerAdapter 21 import android.animation.ValueAnimator 22 import android.content.res.ColorStateList 23 import android.content.res.Resources 24 import android.content.res.TypedArray 25 import android.graphics.Canvas 26 import android.graphics.Color 27 import android.graphics.ColorFilter 28 import android.graphics.Outline 29 import android.graphics.Paint 30 import android.graphics.PixelFormat 31 import android.graphics.Xfermode 32 import android.graphics.drawable.Drawable 33 import android.util.AttributeSet 34 import android.util.MathUtils 35 import android.view.View 36 import androidx.annotation.Keep 37 import com.android.app.animation.Interpolators 38 import com.android.internal.graphics.ColorUtils 39 import com.android.internal.graphics.ColorUtils.blendARGB 40 import com.android.systemui.res.R 41 import org.xmlpull.v1.XmlPullParser 42 43 private const val BACKGROUND_ANIM_DURATION = 370L 44 45 /** Drawable that can draw an animated gradient when tapped. */ 46 @Keep 47 class IlluminationDrawable : Drawable() { 48 49 private var themeAttrs: IntArray? = null 50 private var cornerRadiusOverride = -1f 51 var cornerRadius = 0f 52 get() { 53 return if (cornerRadiusOverride >= 0) { 54 cornerRadiusOverride 55 } else { 56 field 57 } 58 } 59 private var highlightColor = Color.TRANSPARENT 60 private var tmpHsl = floatArrayOf(0f, 0f, 0f) 61 private var paint = Paint() 62 private var highlight = 0f 63 private val lightSources = arrayListOf<LightSourceDrawable>() 64 65 private var backgroundColor = Color.TRANSPARENT 66 set(value) { 67 if (value == field) { 68 return 69 } 70 field = value 71 animateBackground() 72 } 73 74 private var backgroundAnimation: ValueAnimator? = null 75 76 /** Draw background and gradient. */ drawnull77 override fun draw(canvas: Canvas) { 78 canvas.drawRoundRect( 79 0f, 80 0f, 81 bounds.width().toFloat(), 82 bounds.height().toFloat(), 83 cornerRadius, 84 cornerRadius, 85 paint 86 ) 87 } 88 getOutlinenull89 override fun getOutline(outline: Outline) { 90 outline.setRoundRect(bounds, cornerRadius) 91 } 92 getOpacitynull93 override fun getOpacity(): Int { 94 return PixelFormat.TRANSPARENT 95 } 96 inflatenull97 override fun inflate( 98 r: Resources, 99 parser: XmlPullParser, 100 attrs: AttributeSet, 101 theme: Resources.Theme? 102 ) { 103 val a = obtainAttributes(r, theme, attrs, R.styleable.IlluminationDrawable) 104 themeAttrs = a.extractThemeAttrs() 105 updateStateFromTypedArray(a) 106 a.recycle() 107 } 108 updateStateFromTypedArraynull109 private fun updateStateFromTypedArray(a: TypedArray) { 110 if (a.hasValue(R.styleable.IlluminationDrawable_cornerRadius)) { 111 cornerRadius = 112 a.getDimension(R.styleable.IlluminationDrawable_cornerRadius, cornerRadius) 113 } 114 if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) { 115 highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f 116 } 117 } 118 canApplyThemenull119 override fun canApplyTheme(): Boolean { 120 return themeAttrs != null && themeAttrs!!.size > 0 || super.canApplyTheme() 121 } 122 applyThemenull123 override fun applyTheme(t: Resources.Theme) { 124 super.applyTheme(t) 125 themeAttrs?.let { 126 val a = t.resolveAttributes(it, R.styleable.IlluminationDrawable) 127 updateStateFromTypedArray(a) 128 a.recycle() 129 } 130 } 131 setColorFilternull132 override fun setColorFilter(p0: ColorFilter?) { 133 throw UnsupportedOperationException("Color filters are not supported") 134 } 135 setAlphanull136 override fun setAlpha(alpha: Int) { 137 if (alpha == paint.alpha) { 138 return 139 } 140 141 paint.alpha = alpha 142 invalidateSelf() 143 144 lightSources.forEach { it.alpha = alpha } 145 } 146 getAlphanull147 override fun getAlpha(): Int { 148 return paint.alpha 149 } 150 setXfermodenull151 override fun setXfermode(mode: Xfermode?) { 152 if (mode == paint.xfermode) { 153 return 154 } 155 156 paint.xfermode = mode 157 invalidateSelf() 158 } 159 160 /** 161 * Cross fade background. 162 * 163 * @see setTintList 164 * @see backgroundColor 165 */ animateBackgroundnull166 private fun animateBackground() { 167 ColorUtils.colorToHSL(backgroundColor, tmpHsl) 168 val L = tmpHsl[2] 169 tmpHsl[2] = 170 MathUtils.constrain( 171 if (L < 1f - highlight) { 172 L + highlight 173 } else { 174 L - highlight 175 }, 176 0f, 177 1f 178 ) 179 180 val initialBackground = paint.color 181 val initialHighlight = highlightColor 182 val finalHighlight = ColorUtils.HSLToColor(tmpHsl) 183 184 backgroundAnimation?.cancel() 185 backgroundAnimation = 186 ValueAnimator.ofFloat(0f, 1f).apply { 187 duration = BACKGROUND_ANIM_DURATION 188 interpolator = Interpolators.FAST_OUT_LINEAR_IN 189 addUpdateListener { 190 val progress = it.animatedValue as Float 191 paint.color = blendARGB(initialBackground, backgroundColor, progress) 192 highlightColor = blendARGB(initialHighlight, finalHighlight, progress) 193 lightSources.forEach { it.highlightColor = highlightColor } 194 invalidateSelf() 195 } 196 addListener( 197 object : AnimatorListenerAdapter() { 198 override fun onAnimationEnd(animation: Animator) { 199 backgroundAnimation = null 200 } 201 } 202 ) 203 start() 204 } 205 } 206 setTintListnull207 override fun setTintList(tint: ColorStateList?) { 208 super.setTintList(tint) 209 backgroundColor = tint!!.defaultColor 210 } 211 registerLightSourcenull212 fun registerLightSource(lightSource: View) { 213 if (lightSource.background is LightSourceDrawable) { 214 registerLightSource(lightSource.background as LightSourceDrawable) 215 } else if (lightSource.foreground is LightSourceDrawable) { 216 registerLightSource(lightSource.foreground as LightSourceDrawable) 217 } 218 } 219 registerLightSourcenull220 private fun registerLightSource(lightSource: LightSourceDrawable) { 221 lightSource.alpha = paint.alpha 222 lightSources.add(lightSource) 223 } 224 225 /** Set or remove the corner radius override. This is typically set during animations. */ setCornerRadiusOverridenull226 fun setCornerRadiusOverride(cornerRadius: Float?) { 227 cornerRadiusOverride = cornerRadius ?: -1f 228 } 229 } 230