blob: 3669493f4e41ee0cec93fdd6a81b194b3dc89b21 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.media.controls.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.res.ColorStateList
import android.content.res.Resources
import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Outline
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.Xfermode
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.util.MathUtils
import android.view.View
import androidx.annotation.Keep
import com.android.internal.graphics.ColorUtils
import com.android.internal.graphics.ColorUtils.blendARGB
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import org.xmlpull.v1.XmlPullParser
private const val BACKGROUND_ANIM_DURATION = 370L
/** Drawable that can draw an animated gradient when tapped. */
@Keep
class IlluminationDrawable : Drawable() {
private var themeAttrs: IntArray? = null
private var cornerRadiusOverride = -1f
var cornerRadius = 0f
get() {
return if (cornerRadiusOverride >= 0) {
cornerRadiusOverride
} else {
field
}
}
private var highlightColor = Color.TRANSPARENT
private var tmpHsl = floatArrayOf(0f, 0f, 0f)
private var paint = Paint()
private var highlight = 0f
private val lightSources = arrayListOf<LightSourceDrawable>()
private var backgroundColor = Color.TRANSPARENT
set(value) {
if (value == field) {
return
}
field = value
animateBackground()
}
private var backgroundAnimation: ValueAnimator? = null
/** Draw background and gradient. */
override fun draw(canvas: Canvas) {
canvas.drawRoundRect(
0f,
0f,
bounds.width().toFloat(),
bounds.height().toFloat(),
cornerRadius,
cornerRadius,
paint
)
}
override fun getOutline(outline: Outline) {
outline.setRoundRect(bounds, cornerRadius)
}
override fun getOpacity(): Int {
return PixelFormat.TRANSPARENT
}
override fun inflate(
r: Resources,
parser: XmlPullParser,
attrs: AttributeSet,
theme: Resources.Theme?
) {
val a = obtainAttributes(r, theme, attrs, R.styleable.IlluminationDrawable)
themeAttrs = a.extractThemeAttrs()
updateStateFromTypedArray(a)
a.recycle()
}
private fun updateStateFromTypedArray(a: TypedArray) {
if (a.hasValue(R.styleable.IlluminationDrawable_cornerRadius)) {
cornerRadius =
a.getDimension(R.styleable.IlluminationDrawable_cornerRadius, cornerRadius)
}
if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) {
highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f
}
}
override fun canApplyTheme(): Boolean {
return themeAttrs != null && themeAttrs!!.size > 0 || super.canApplyTheme()
}
override fun applyTheme(t: Resources.Theme) {
super.applyTheme(t)
themeAttrs?.let {
val a = t.resolveAttributes(it, R.styleable.IlluminationDrawable)
updateStateFromTypedArray(a)
a.recycle()
}
}
override fun setColorFilter(p0: ColorFilter?) {
throw UnsupportedOperationException("Color filters are not supported")
}
override fun setAlpha(alpha: Int) {
if (alpha == paint.alpha) {
return
}
paint.alpha = alpha
invalidateSelf()
lightSources.forEach { it.alpha = alpha }
}
override fun getAlpha(): Int {
return paint.alpha
}
override fun setXfermode(mode: Xfermode?) {
if (mode == paint.xfermode) {
return
}
paint.xfermode = mode
invalidateSelf()
}
/**
* Cross fade background.
*
* @see setTintList
* @see backgroundColor
*/
private fun animateBackground() {
ColorUtils.colorToHSL(backgroundColor, tmpHsl)
val L = tmpHsl[2]
tmpHsl[2] =
MathUtils.constrain(
if (L < 1f - highlight) {
L + highlight
} else {
L - highlight
},
0f,
1f
)
val initialBackground = paint.color
val initialHighlight = highlightColor
val finalHighlight = ColorUtils.HSLToColor(tmpHsl)
backgroundAnimation?.cancel()
backgroundAnimation =
ValueAnimator.ofFloat(0f, 1f).apply {
duration = BACKGROUND_ANIM_DURATION
interpolator = Interpolators.FAST_OUT_LINEAR_IN
addUpdateListener {
val progress = it.animatedValue as Float
paint.color = blendARGB(initialBackground, backgroundColor, progress)
highlightColor = blendARGB(initialHighlight, finalHighlight, progress)
lightSources.forEach { it.highlightColor = highlightColor }
invalidateSelf()
}
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
backgroundAnimation = null
}
}
)
start()
}
}
override fun setTintList(tint: ColorStateList?) {
super.setTintList(tint)
backgroundColor = tint!!.defaultColor
}
fun registerLightSource(lightSource: View) {
if (lightSource.background is LightSourceDrawable) {
registerLightSource(lightSource.background as LightSourceDrawable)
} else if (lightSource.foreground is LightSourceDrawable) {
registerLightSource(lightSource.foreground as LightSourceDrawable)
}
}
private fun registerLightSource(lightSource: LightSourceDrawable) {
lightSource.alpha = paint.alpha
lightSources.add(lightSource)
}
/** Set or remove the corner radius override. This is typically set during animations. */
fun setCornerRadiusOverride(cornerRadius: Float?) {
cornerRadiusOverride = cornerRadius ?: -1f
}
}