1 /* <lambda>null2 * Copyright (C) 2021 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.statusbar.charging 18 19 import android.animation.Animator 20 import android.animation.AnimatorListenerAdapter 21 import android.animation.ValueAnimator 22 import android.content.Context 23 import android.content.res.Configuration 24 import android.graphics.Canvas 25 import android.graphics.Paint 26 import android.graphics.PointF 27 import android.util.AttributeSet 28 import android.view.View 29 30 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f 31 32 /** 33 * Expanding ripple effect that shows when charging begins. 34 */ 35 class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { 36 private val rippleShader = RippleShader() 37 private val defaultColor: Int = 0xffffffff.toInt() 38 private val ripplePaint = Paint() 39 40 var rippleInProgress: Boolean = false 41 var radius: Float = 0.0f 42 set(value) { 43 rippleShader.radius = value 44 field = value 45 } 46 var origin: PointF = PointF() 47 set(value) { 48 rippleShader.origin = value 49 field = value 50 } 51 var duration: Long = 1750 52 53 init { 54 rippleShader.color = defaultColor 55 rippleShader.progress = 0f 56 rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH 57 ripplePaint.shader = rippleShader 58 visibility = View.GONE 59 } 60 61 override fun onConfigurationChanged(newConfig: Configuration?) { 62 rippleShader.pixelDensity = resources.displayMetrics.density 63 super.onConfigurationChanged(newConfig) 64 } 65 66 override fun onAttachedToWindow() { 67 rippleShader.pixelDensity = resources.displayMetrics.density 68 super.onAttachedToWindow() 69 } 70 71 @JvmOverloads 72 fun startRipple(onAnimationEnd: Runnable? = null) { 73 if (rippleInProgress) { 74 return // Ignore if ripple effect is already playing 75 } 76 val animator = ValueAnimator.ofFloat(0f, 1f) 77 animator.duration = duration 78 animator.addUpdateListener { animator -> 79 val now = animator.currentPlayTime 80 val progress = animator.animatedValue as Float 81 rippleShader.progress = progress 82 rippleShader.distortionStrength = 1 - progress 83 rippleShader.time = now.toFloat() 84 invalidate() 85 } 86 animator.addListener(object : AnimatorListenerAdapter() { 87 override fun onAnimationEnd(animation: Animator?) { 88 rippleInProgress = false 89 visibility = View.GONE 90 onAnimationEnd?.run() 91 } 92 }) 93 animator.start() 94 visibility = View.VISIBLE 95 rippleInProgress = true 96 } 97 98 fun setColor(color: Int) { 99 rippleShader.color = color 100 } 101 102 override fun onDraw(canvas: Canvas?) { 103 // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover 104 // the active effect area. Values here should be kept in sync with the 105 // animation implementation in the ripple shader. 106 val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * 107 (1 - rippleShader.progress)) * radius * 1.5f 108 canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint) 109 } 110 } 111