Merge changes from topic "polish-docking-anim" into tm-qpr-dev am: 657aa09392
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21452247 Change-Id: I8c4a47060cb9009f70e5560e15762b51567f6a1e Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -68,7 +68,7 @@ class RippleAnimation(private val config: RippleAnimationConfig) {
|
|||||||
private fun applyConfigToShader() {
|
private fun applyConfigToShader() {
|
||||||
with(rippleShader) {
|
with(rippleShader) {
|
||||||
setCenter(config.centerX, config.centerY)
|
setCenter(config.centerX, config.centerY)
|
||||||
setMaxSize(config.maxWidth, config.maxHeight)
|
rippleSize.setMaxSize(config.maxWidth, config.maxHeight)
|
||||||
pixelDensity = config.pixelDensity
|
pixelDensity = config.pixelDensity
|
||||||
color = ColorUtils.setAlphaComponent(config.color, config.opacity)
|
color = ColorUtils.setAlphaComponent(config.color, config.opacity)
|
||||||
sparkleStrength = config.sparkleStrength
|
sparkleStrength = config.sparkleStrength
|
||||||
|
|||||||
@@ -15,9 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.systemui.surfaceeffects.ripple
|
package com.android.systemui.surfaceeffects.ripple
|
||||||
|
|
||||||
import android.graphics.PointF
|
|
||||||
import android.graphics.RuntimeShader
|
import android.graphics.RuntimeShader
|
||||||
|
import android.util.Log
|
||||||
import android.util.MathUtils
|
import android.util.MathUtils
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import com.android.systemui.animation.Interpolators
|
import com.android.systemui.animation.Interpolators
|
||||||
import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
|
import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
|
||||||
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
|
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
|
||||||
@@ -44,6 +45,8 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
|
|||||||
}
|
}
|
||||||
// language=AGSL
|
// language=AGSL
|
||||||
companion object {
|
companion object {
|
||||||
|
private val TAG = RippleShader::class.simpleName
|
||||||
|
|
||||||
// Default fade in/ out values. The value range is [0,1].
|
// Default fade in/ out values. The value range is [0,1].
|
||||||
const val DEFAULT_FADE_IN_START = 0f
|
const val DEFAULT_FADE_IN_START = 0f
|
||||||
const val DEFAULT_FADE_OUT_END = 1f
|
const val DEFAULT_FADE_OUT_END = 1f
|
||||||
@@ -99,7 +102,7 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
|
|||||||
vec4 main(vec2 p) {
|
vec4 main(vec2 p) {
|
||||||
float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
|
float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
|
||||||
in_thickness), in_blur);
|
in_thickness), in_blur);
|
||||||
float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
|
float inside = soften(sdRoundedBox(p-in_center, in_size * 1.25, in_cornerRadius),
|
||||||
in_blur);
|
in_blur);
|
||||||
float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
|
float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
|
||||||
* (1.-sparkleRing) * in_fadeSparkle;
|
* (1.-sparkleRing) * in_fadeSparkle;
|
||||||
@@ -184,12 +187,17 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
|
|||||||
setFloatUniform("in_center", x, y)
|
setFloatUniform("in_center", x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Max width of the ripple. */
|
/**
|
||||||
private var maxSize: PointF = PointF()
|
* Blur multipliers for the ripple.
|
||||||
fun setMaxSize(width: Float, height: Float) {
|
*
|
||||||
maxSize.x = width
|
* <p>It interpolates from [blurStart] to [blurEnd] based on the [progress]. Increase number to
|
||||||
maxSize.y = height
|
* add more blur.
|
||||||
}
|
*/
|
||||||
|
var blurStart: Float = 1.25f
|
||||||
|
var blurEnd: Float = 0.5f
|
||||||
|
|
||||||
|
/** Size of the ripple. */
|
||||||
|
val rippleSize = RippleSize()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Linear progress of the ripple. Float value between [0, 1].
|
* Linear progress of the ripple. Float value between [0, 1].
|
||||||
@@ -209,15 +217,19 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
|
|||||||
/** Progress with Standard easing curve applied. */
|
/** Progress with Standard easing curve applied. */
|
||||||
private var progress: Float = 0.0f
|
private var progress: Float = 0.0f
|
||||||
set(value) {
|
set(value) {
|
||||||
currentWidth = maxSize.x * value
|
field = value
|
||||||
currentHeight = maxSize.y * value
|
|
||||||
setFloatUniform("in_size", currentWidth, currentHeight)
|
|
||||||
|
|
||||||
setFloatUniform("in_thickness", maxSize.y * value * 0.5f)
|
rippleSize.update(value)
|
||||||
// radius should not exceed width and height values.
|
|
||||||
setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * value)
|
|
||||||
|
|
||||||
setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
|
setFloatUniform("in_size", rippleSize.currentWidth, rippleSize.currentHeight)
|
||||||
|
setFloatUniform("in_thickness", rippleSize.currentHeight * 0.5f)
|
||||||
|
// Corner radius is always max of the min between the current width and height.
|
||||||
|
setFloatUniform(
|
||||||
|
"in_cornerRadius",
|
||||||
|
Math.min(rippleSize.currentWidth, rippleSize.currentHeight)
|
||||||
|
)
|
||||||
|
|
||||||
|
setFloatUniform("in_blur", MathUtils.lerp(blurStart, blurEnd, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Play time since the start of the effect. */
|
/** Play time since the start of the effect. */
|
||||||
@@ -264,12 +276,6 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
|
|||||||
setFloatUniform("in_pixelDensity", value)
|
setFloatUniform("in_pixelDensity", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentWidth: Float = 0f
|
|
||||||
private set
|
|
||||||
|
|
||||||
var currentHeight: Float = 0f
|
|
||||||
private set
|
|
||||||
|
|
||||||
/** Parameters that are used to fade in/ out of the sparkle ring. */
|
/** Parameters that are used to fade in/ out of the sparkle ring. */
|
||||||
val sparkleRingFadeParams =
|
val sparkleRingFadeParams =
|
||||||
FadeParams(
|
FadeParams(
|
||||||
@@ -342,4 +348,101 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
|
|||||||
/** The endpoint of the fade out, given that the animation goes from 0 to 1. */
|
/** The endpoint of the fade out, given that the animation goes from 0 to 1. */
|
||||||
var fadeOutEnd: Float = DEFAULT_FADE_OUT_END,
|
var fadeOutEnd: Float = DEFAULT_FADE_OUT_END,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Desired size of the ripple at a point t in [progress].
|
||||||
|
*
|
||||||
|
* <p>Note that [progress] is curved and normalized. Below is an example usage:
|
||||||
|
* SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 0.2f, width= 500f, height=
|
||||||
|
* 700f), SizeAtProgress(t= 1f, width= 100f, height= 300f)
|
||||||
|
*
|
||||||
|
* <p>For simple ripple effects, you will want to use [setMaxSize] as it is translated into:
|
||||||
|
* SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 1f, width= maxWidth, height=
|
||||||
|
* maxHeight)
|
||||||
|
*/
|
||||||
|
data class SizeAtProgress(
|
||||||
|
/** Time t in [0,1] progress range. */
|
||||||
|
var t: Float,
|
||||||
|
/** Target width size of the ripple at time [t]. */
|
||||||
|
var width: Float,
|
||||||
|
/** Target height size of the ripple at time [t]. */
|
||||||
|
var height: Float
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Updates and stores the ripple size. */
|
||||||
|
inner class RippleSize {
|
||||||
|
@VisibleForTesting var sizes = mutableListOf<SizeAtProgress>()
|
||||||
|
@VisibleForTesting var currentSizeIndex = 0
|
||||||
|
@VisibleForTesting val initialSize = SizeAtProgress(0f, 0f, 0f)
|
||||||
|
|
||||||
|
var currentWidth: Float = 0f
|
||||||
|
private set
|
||||||
|
var currentHeight: Float = 0f
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the max size of the ripple.
|
||||||
|
*
|
||||||
|
* <p>Use this if the ripple shape simply changes linearly.
|
||||||
|
*/
|
||||||
|
fun setMaxSize(width: Float, height: Float) {
|
||||||
|
setSizeAtProgresses(initialSize, SizeAtProgress(1f, width, height))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the list of [sizes].
|
||||||
|
*
|
||||||
|
* <p>Note that setting this clears the existing sizes.
|
||||||
|
*/
|
||||||
|
fun setSizeAtProgresses(vararg sizes: SizeAtProgress) {
|
||||||
|
// Reset everything.
|
||||||
|
this.sizes.clear()
|
||||||
|
currentSizeIndex = 0
|
||||||
|
|
||||||
|
this.sizes.addAll(sizes)
|
||||||
|
this.sizes.sortBy { it.t }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current ripple size based on the progress.
|
||||||
|
*
|
||||||
|
* <p>Should be called when progress updates.
|
||||||
|
*/
|
||||||
|
fun update(progress: Float) {
|
||||||
|
val targetIndex = updateTargetIndex(progress)
|
||||||
|
val prevIndex = Math.max(targetIndex - 1, 0)
|
||||||
|
|
||||||
|
val targetSize = sizes[targetIndex]
|
||||||
|
val prevSize = sizes[prevIndex]
|
||||||
|
|
||||||
|
val subProgress = subProgress(prevSize.t, targetSize.t, progress)
|
||||||
|
|
||||||
|
currentWidth = targetSize.width * subProgress + prevSize.width
|
||||||
|
currentHeight = targetSize.height * subProgress + prevSize.height
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTargetIndex(progress: Float): Int {
|
||||||
|
if (sizes.isEmpty()) {
|
||||||
|
// It could be empty on init.
|
||||||
|
if (progress > 0f) {
|
||||||
|
Log.e(
|
||||||
|
TAG,
|
||||||
|
"Did you forget to set the ripple size? Use [setMaxSize] or " +
|
||||||
|
"[setSizeAtProgresses] before playing the animation."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// If there's no size is set, we set everything to 0.
|
||||||
|
setSizeAtProgresses(initialSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidate = sizes[currentSizeIndex]
|
||||||
|
|
||||||
|
while (progress > candidate.t) {
|
||||||
|
currentSizeIndex = Math.min(currentSizeIndex + 1, sizes.size - 1)
|
||||||
|
candidate = sizes[currentSizeIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentSizeIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,12 +45,8 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
|
|||||||
|
|
||||||
var duration: Long = 1750
|
var duration: Long = 1750
|
||||||
|
|
||||||
private var maxWidth: Float = 0.0f
|
|
||||||
private var maxHeight: Float = 0.0f
|
|
||||||
fun setMaxSize(maxWidth: Float, maxHeight: Float) {
|
fun setMaxSize(maxWidth: Float, maxHeight: Float) {
|
||||||
this.maxWidth = maxWidth
|
rippleShader.rippleSize.setMaxSize(maxWidth, maxHeight)
|
||||||
this.maxHeight = maxHeight
|
|
||||||
rippleShader.setMaxSize(maxWidth, maxHeight)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var centerX: Float = 0.0f
|
private var centerX: Float = 0.0f
|
||||||
@@ -84,6 +80,106 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
|
|||||||
ripplePaint.shader = rippleShader
|
ripplePaint.shader = rippleShader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the fade parameters for the base ring.
|
||||||
|
*
|
||||||
|
* <p>Base ring indicates a blurred ring below the sparkle ring. See
|
||||||
|
* [RippleShader.baseRingFadeParams].
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun setBaseRingFadeParams(
|
||||||
|
fadeInStart: Float = rippleShader.baseRingFadeParams.fadeInStart,
|
||||||
|
fadeInEnd: Float = rippleShader.baseRingFadeParams.fadeInEnd,
|
||||||
|
fadeOutStart: Float = rippleShader.baseRingFadeParams.fadeOutStart,
|
||||||
|
fadeOutEnd: Float = rippleShader.baseRingFadeParams.fadeOutEnd
|
||||||
|
) {
|
||||||
|
setFadeParams(
|
||||||
|
rippleShader.baseRingFadeParams,
|
||||||
|
fadeInStart,
|
||||||
|
fadeInEnd,
|
||||||
|
fadeOutStart,
|
||||||
|
fadeOutEnd
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the fade parameters for the sparkle ring.
|
||||||
|
*
|
||||||
|
* <p>Sparkle ring refers to the ring that's drawn on top of the base ring. See
|
||||||
|
* [RippleShader.sparkleRingFadeParams].
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun setSparkleRingFadeParams(
|
||||||
|
fadeInStart: Float = rippleShader.sparkleRingFadeParams.fadeInStart,
|
||||||
|
fadeInEnd: Float = rippleShader.sparkleRingFadeParams.fadeInEnd,
|
||||||
|
fadeOutStart: Float = rippleShader.sparkleRingFadeParams.fadeOutStart,
|
||||||
|
fadeOutEnd: Float = rippleShader.sparkleRingFadeParams.fadeOutEnd
|
||||||
|
) {
|
||||||
|
setFadeParams(
|
||||||
|
rippleShader.sparkleRingFadeParams,
|
||||||
|
fadeInStart,
|
||||||
|
fadeInEnd,
|
||||||
|
fadeOutStart,
|
||||||
|
fadeOutEnd
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the fade parameters for the center fill.
|
||||||
|
*
|
||||||
|
* <p>One common use case is set all the params to 1, which completely removes the center fill.
|
||||||
|
* See [RippleShader.centerFillFadeParams].
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun setCenterFillFadeParams(
|
||||||
|
fadeInStart: Float = rippleShader.centerFillFadeParams.fadeInStart,
|
||||||
|
fadeInEnd: Float = rippleShader.centerFillFadeParams.fadeInEnd,
|
||||||
|
fadeOutStart: Float = rippleShader.centerFillFadeParams.fadeOutStart,
|
||||||
|
fadeOutEnd: Float = rippleShader.centerFillFadeParams.fadeOutEnd
|
||||||
|
) {
|
||||||
|
setFadeParams(
|
||||||
|
rippleShader.centerFillFadeParams,
|
||||||
|
fadeInStart,
|
||||||
|
fadeInEnd,
|
||||||
|
fadeOutStart,
|
||||||
|
fadeOutEnd
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setFadeParams(
|
||||||
|
fadeParams: RippleShader.FadeParams,
|
||||||
|
fadeInStart: Float,
|
||||||
|
fadeInEnd: Float,
|
||||||
|
fadeOutStart: Float,
|
||||||
|
fadeOutEnd: Float
|
||||||
|
) {
|
||||||
|
with(fadeParams) {
|
||||||
|
this.fadeInStart = fadeInStart
|
||||||
|
this.fadeInEnd = fadeInEnd
|
||||||
|
this.fadeOutStart = fadeOutStart
|
||||||
|
this.fadeOutEnd = fadeOutEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets blur multiplier at start and end of the progress.
|
||||||
|
*
|
||||||
|
* <p>It interpolates between [start] and [end]. No need to set it if using default blur.
|
||||||
|
*/
|
||||||
|
fun setBlur(start: Float, end: Float) {
|
||||||
|
rippleShader.blurStart = start
|
||||||
|
rippleShader.blurEnd = end
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the list of [RippleShader.SizeAtProgress].
|
||||||
|
*
|
||||||
|
* <p>Note that this clears the list before it sets with the new data.
|
||||||
|
*/
|
||||||
|
fun setSizeAtProgresses(vararg targetSizes: RippleShader.SizeAtProgress) {
|
||||||
|
rippleShader.rippleSize.setSizeAtProgresses(*targetSizes)
|
||||||
|
}
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun startRipple(onAnimationEnd: Runnable? = null) {
|
fun startRipple(onAnimationEnd: Runnable? = null) {
|
||||||
if (animator.isRunning) {
|
if (animator.isRunning) {
|
||||||
@@ -133,13 +229,13 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
|
|||||||
}
|
}
|
||||||
// To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the
|
// To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the
|
||||||
// active effect area. Values here should be kept in sync with the animation implementation
|
// active effect area. Values here should be kept in sync with the animation implementation
|
||||||
// in the ripple shader. (Twice bigger)
|
// in the ripple shader.
|
||||||
if (rippleShape == RippleShape.CIRCLE) {
|
if (rippleShape == RippleShape.CIRCLE) {
|
||||||
val maskRadius = rippleShader.currentWidth
|
val maskRadius = rippleShader.rippleSize.currentWidth
|
||||||
canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
|
canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
|
||||||
} else {
|
} else if (rippleShape == RippleShape.ELLIPSE) {
|
||||||
val maskWidth = rippleShader.currentWidth * 2
|
val maskWidth = rippleShader.rippleSize.currentWidth * 2
|
||||||
val maskHeight = rippleShader.currentHeight * 2
|
val maskHeight = rippleShader.rippleSize.currentHeight * 2
|
||||||
canvas.drawRect(
|
canvas.drawRect(
|
||||||
/* left= */ centerX - maskWidth,
|
/* left= */ centerX - maskWidth,
|
||||||
/* top= */ centerY - maskHeight,
|
/* top= */ centerY - maskHeight,
|
||||||
@@ -147,6 +243,10 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
|
|||||||
/* bottom= */ centerY + maskHeight,
|
/* bottom= */ centerY + maskHeight,
|
||||||
ripplePaint
|
ripplePaint
|
||||||
)
|
)
|
||||||
|
} else { // RippleShape.RoundedBox
|
||||||
|
// No masking for the rounded box, as it has more blur which requires larger bounds.
|
||||||
|
// Masking creates sharp bounds even when the masking is 4 times bigger.
|
||||||
|
canvas.drawPaint(ripplePaint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ class SdfShaderLibrary {
|
|||||||
|
|
||||||
float roundedBoxRing(vec2 p, vec2 size, float cornerRadius,
|
float roundedBoxRing(vec2 p, vec2 size, float cornerRadius,
|
||||||
float borderThickness) {
|
float borderThickness) {
|
||||||
float outerRoundBox = sdRoundedBox(p, size, cornerRadius);
|
float outerRoundBox = sdRoundedBox(p, size + vec2(borderThickness),
|
||||||
float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness),
|
cornerRadius + borderThickness);
|
||||||
cornerRadius - borderThickness);
|
float innerRoundBox = sdRoundedBox(p, size, cornerRadius);
|
||||||
return subtract(outerRoundBox, innerRoundBox);
|
return subtract(outerRoundBox, innerRoundBox);
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@@ -69,10 +69,13 @@ class SdfShaderLibrary {
|
|||||||
|
|
||||||
vec2 u = wh*p, v = wh*wh;
|
vec2 u = wh*p, v = wh*wh;
|
||||||
|
|
||||||
float U1 = u.y/2.0; float U5 = 4.0*U1;
|
float U1 = u.y/2.0;
|
||||||
float U2 = v.y-v.x; float U6 = 6.0*U1;
|
float U2 = v.y-v.x;
|
||||||
float U3 = u.x-U2; float U7 = 3.0*U3;
|
float U3 = u.x-U2;
|
||||||
float U4 = u.x+U2;
|
float U4 = u.x+U2;
|
||||||
|
float U5 = 4.0*U1;
|
||||||
|
float U6 = 6.0*U1;
|
||||||
|
float U7 = 3.0*U3;
|
||||||
|
|
||||||
float t = 0.5;
|
float t = 0.5;
|
||||||
for (int i = 0; i < 3; i ++) {
|
for (int i = 0; i < 3; i ++) {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
|
|||||||
}
|
}
|
||||||
private var radius: Float = 0f
|
private var radius: Float = 0f
|
||||||
set(value) {
|
set(value) {
|
||||||
rippleShader.setMaxSize(value * 2f, value * 2f)
|
rippleShader.rippleSize.setMaxSize(value * 2f, value * 2f)
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
private var origin: Point = Point()
|
private var origin: Point = Point()
|
||||||
@@ -364,7 +364,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
|
|||||||
|
|
||||||
if (drawRipple) {
|
if (drawRipple) {
|
||||||
canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(),
|
canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(),
|
||||||
rippleShader.currentWidth, ripplePaint)
|
rippleShader.rippleSize.currentWidth, ripplePaint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ import android.widget.TextView;
|
|||||||
import com.android.settingslib.Utils;
|
import com.android.settingslib.Utils;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.animation.Interpolators;
|
import com.android.systemui.animation.Interpolators;
|
||||||
|
import com.android.systemui.shared.recents.utilities.Utilities;
|
||||||
import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
|
import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
|
||||||
|
import com.android.systemui.surfaceeffects.ripple.RippleShader;
|
||||||
import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
|
import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
|
||||||
import com.android.systemui.surfaceeffects.ripple.RippleView;
|
import com.android.systemui.surfaceeffects.ripple.RippleView;
|
||||||
|
|
||||||
@@ -44,10 +46,12 @@ import java.text.NumberFormat;
|
|||||||
*/
|
*/
|
||||||
final class WirelessChargingLayout extends FrameLayout {
|
final class WirelessChargingLayout extends FrameLayout {
|
||||||
private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500;
|
private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500;
|
||||||
private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 1750;
|
private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 3000;
|
||||||
private static final int SCRIM_COLOR = 0x4C000000;
|
private static final int SCRIM_COLOR = 0x4C000000;
|
||||||
private static final int SCRIM_FADE_DURATION = 300;
|
private static final int SCRIM_FADE_DURATION = 300;
|
||||||
private RippleView mRippleView;
|
private RippleView mRippleView;
|
||||||
|
// This is only relevant to the rounded box ripple.
|
||||||
|
private RippleShader.SizeAtProgress[] mSizeAtProgressArray;
|
||||||
|
|
||||||
WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel,
|
WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel,
|
||||||
boolean isDozing, RippleShape rippleShape) {
|
boolean isDozing, RippleShape rippleShape) {
|
||||||
@@ -125,20 +129,23 @@ final class WirelessChargingLayout extends FrameLayout {
|
|||||||
AnimatorSet animatorSet = new AnimatorSet();
|
AnimatorSet animatorSet = new AnimatorSet();
|
||||||
animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator);
|
animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator);
|
||||||
|
|
||||||
ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this,
|
// For tablet docking animation, we don't play the background scrim.
|
||||||
"backgroundColor", Color.TRANSPARENT, SCRIM_COLOR);
|
if (!Utilities.isTablet(context)) {
|
||||||
scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION);
|
ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this,
|
||||||
scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR);
|
"backgroundColor", Color.TRANSPARENT, SCRIM_COLOR);
|
||||||
ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this,
|
scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION);
|
||||||
"backgroundColor", SCRIM_COLOR, Color.TRANSPARENT);
|
scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR);
|
||||||
scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION);
|
ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this,
|
||||||
scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR);
|
"backgroundColor", SCRIM_COLOR, Color.TRANSPARENT);
|
||||||
scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE
|
scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION);
|
||||||
? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION)
|
scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR);
|
||||||
- SCRIM_FADE_DURATION);
|
scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE
|
||||||
AnimatorSet animatorSetScrim = new AnimatorSet();
|
? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION)
|
||||||
animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator);
|
- SCRIM_FADE_DURATION);
|
||||||
animatorSetScrim.start();
|
AnimatorSet animatorSetScrim = new AnimatorSet();
|
||||||
|
animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator);
|
||||||
|
animatorSetScrim.start();
|
||||||
|
}
|
||||||
|
|
||||||
mRippleView = findViewById(R.id.wireless_charging_ripple);
|
mRippleView = findViewById(R.id.wireless_charging_ripple);
|
||||||
mRippleView.setupShader(rippleShape);
|
mRippleView.setupShader(rippleShape);
|
||||||
@@ -147,7 +154,26 @@ final class WirelessChargingLayout extends FrameLayout {
|
|||||||
if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
|
if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
|
||||||
mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION);
|
mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION);
|
||||||
mRippleView.setSparkleStrength(0.22f);
|
mRippleView.setSparkleStrength(0.22f);
|
||||||
mRippleView.setColor(color, 28);
|
mRippleView.setColor(color, 102); // 40% of opacity.
|
||||||
|
mRippleView.setBaseRingFadeParams(
|
||||||
|
/* fadeInStart = */ 0f,
|
||||||
|
/* fadeInEnd = */ 0f,
|
||||||
|
/* fadeOutStart = */ 0.2f,
|
||||||
|
/* fadeOutEnd= */ 0.47f
|
||||||
|
);
|
||||||
|
mRippleView.setSparkleRingFadeParams(
|
||||||
|
/* fadeInStart = */ 0f,
|
||||||
|
/* fadeInEnd = */ 0f,
|
||||||
|
/* fadeOutStart = */ 0.2f,
|
||||||
|
/* fadeOutEnd= */ 1f
|
||||||
|
);
|
||||||
|
mRippleView.setCenterFillFadeParams(
|
||||||
|
/* fadeInStart = */ 0f,
|
||||||
|
/* fadeInEnd = */ 0f,
|
||||||
|
/* fadeOutStart = */ 0f,
|
||||||
|
/* fadeOutEnd= */ 0.2f
|
||||||
|
);
|
||||||
|
mRippleView.setBlur(6.5f, 2.5f);
|
||||||
} else {
|
} else {
|
||||||
mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION);
|
mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION);
|
||||||
mRippleView.setColor(color, RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA);
|
mRippleView.setColor(color, RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA);
|
||||||
@@ -246,9 +272,7 @@ final class WirelessChargingLayout extends FrameLayout {
|
|||||||
int height = getMeasuredHeight();
|
int height = getMeasuredHeight();
|
||||||
mRippleView.setCenter(width * 0.5f, height * 0.5f);
|
mRippleView.setCenter(width * 0.5f, height * 0.5f);
|
||||||
if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
|
if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
|
||||||
// Those magic numbers are introduced for visual polish. This aspect ratio maps with
|
updateRippleSizeAtProgressList(width, height);
|
||||||
// the tablet's docking station.
|
|
||||||
mRippleView.setMaxSize(width * 1.36f, height * 1.46f);
|
|
||||||
} else {
|
} else {
|
||||||
float maxSize = Math.max(width, height);
|
float maxSize = Math.max(width, height);
|
||||||
mRippleView.setMaxSize(maxSize, maxSize);
|
mRippleView.setMaxSize(maxSize, maxSize);
|
||||||
@@ -257,4 +281,36 @@ final class WirelessChargingLayout extends FrameLayout {
|
|||||||
|
|
||||||
super.onLayout(changed, left, top, right, bottom);
|
super.onLayout(changed, left, top, right, bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateRippleSizeAtProgressList(float width, float height) {
|
||||||
|
if (mSizeAtProgressArray == null) {
|
||||||
|
float maxSize = Math.max(width, height);
|
||||||
|
mSizeAtProgressArray = new RippleShader.SizeAtProgress[] {
|
||||||
|
// Those magic numbers are introduced for visual polish. It starts from a pill
|
||||||
|
// shape and expand to a full circle.
|
||||||
|
new RippleShader.SizeAtProgress(0f, 0f, 0f),
|
||||||
|
new RippleShader.SizeAtProgress(0.3f, width * 0.4f, height * 0.4f),
|
||||||
|
new RippleShader.SizeAtProgress(1f, maxSize, maxSize)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Same multipliers, just need to recompute with the new width and height.
|
||||||
|
RippleShader.SizeAtProgress first = mSizeAtProgressArray[0];
|
||||||
|
first.setT(0f);
|
||||||
|
first.setWidth(0f);
|
||||||
|
first.setHeight(0f);
|
||||||
|
|
||||||
|
RippleShader.SizeAtProgress second = mSizeAtProgressArray[1];
|
||||||
|
second.setT(0.3f);
|
||||||
|
second.setWidth(width * 0.4f);
|
||||||
|
second.setHeight(height * 0.4f);
|
||||||
|
|
||||||
|
float maxSize = Math.max(width, height);
|
||||||
|
RippleShader.SizeAtProgress last = mSizeAtProgressArray[2];
|
||||||
|
last.setT(1f);
|
||||||
|
last.setWidth(maxSize);
|
||||||
|
last.setHeight(maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
mRippleView.setSizeAtProgresses(mSizeAtProgressArray);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleVi
|
|||||||
// Calculates the actual starting percentage according to ripple shader progress set method.
|
// Calculates the actual starting percentage according to ripple shader progress set method.
|
||||||
// Check calculations in [RippleShader.progress]
|
// Check calculations in [RippleShader.progress]
|
||||||
fun calculateStartingPercentage(newHeight: Float): Float {
|
fun calculateStartingPercentage(newHeight: Float): Float {
|
||||||
val ratio = rippleShader.currentHeight / newHeight
|
val ratio = rippleShader.rippleSize.currentHeight / newHeight
|
||||||
val remainingPercentage = (1 - ratio).toDouble().pow(1 / 3.toDouble()).toFloat()
|
val remainingPercentage = (1 - ratio).toDouble().pow(1 / 3.toDouble()).toFloat()
|
||||||
return 1 - remainingPercentage
|
return 1 - remainingPercentage
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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.surfaceeffects.ripple
|
||||||
|
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
class RippleShaderTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private lateinit var rippleShader: RippleShader
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
rippleShader = RippleShader()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setMaxSize_hasCorrectSizes() {
|
||||||
|
val expectedMaxWidth = 300f
|
||||||
|
val expectedMaxHeight = 500f
|
||||||
|
|
||||||
|
rippleShader.rippleSize.setMaxSize(expectedMaxWidth, expectedMaxHeight)
|
||||||
|
|
||||||
|
assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(2)
|
||||||
|
assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(rippleShader.rippleSize.initialSize)
|
||||||
|
val maxSize = rippleShader.rippleSize.sizes[1]
|
||||||
|
assertThat(maxSize.t).isEqualTo(1f)
|
||||||
|
assertThat(maxSize.width).isEqualTo(expectedMaxWidth)
|
||||||
|
assertThat(maxSize.height).isEqualTo(expectedMaxHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setSizeAtProgresses_hasCorrectSizes() {
|
||||||
|
val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f)
|
||||||
|
val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f)
|
||||||
|
val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f)
|
||||||
|
|
||||||
|
rippleShader.rippleSize.setSizeAtProgresses(expectedSize0, expectedSize1, expectedSize2)
|
||||||
|
|
||||||
|
assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(3)
|
||||||
|
assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(expectedSize0)
|
||||||
|
assertThat(rippleShader.rippleSize.sizes[1]).isEqualTo(expectedSize1)
|
||||||
|
assertThat(rippleShader.rippleSize.sizes[2]).isEqualTo(expectedSize2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setSizeAtProgresses_sizeListIsSortedByT() {
|
||||||
|
val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f)
|
||||||
|
val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f)
|
||||||
|
val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f)
|
||||||
|
val expectedSize3 = RippleShader.SizeAtProgress(t = 0.8f, width = 300f, height = 900f)
|
||||||
|
val expectedSize4 = RippleShader.SizeAtProgress(t = 1f, width = 500f, height = 300f)
|
||||||
|
|
||||||
|
// Add them in unsorted order
|
||||||
|
rippleShader.rippleSize.setSizeAtProgresses(
|
||||||
|
expectedSize0,
|
||||||
|
expectedSize3,
|
||||||
|
expectedSize2,
|
||||||
|
expectedSize4,
|
||||||
|
expectedSize1
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(5)
|
||||||
|
assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(expectedSize0)
|
||||||
|
assertThat(rippleShader.rippleSize.sizes[1]).isEqualTo(expectedSize1)
|
||||||
|
assertThat(rippleShader.rippleSize.sizes[2]).isEqualTo(expectedSize2)
|
||||||
|
assertThat(rippleShader.rippleSize.sizes[3]).isEqualTo(expectedSize3)
|
||||||
|
assertThat(rippleShader.rippleSize.sizes[4]).isEqualTo(expectedSize4)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun update_getsCorrectNextTargetSize() {
|
||||||
|
val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f)
|
||||||
|
val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f)
|
||||||
|
val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f)
|
||||||
|
val expectedSize3 = RippleShader.SizeAtProgress(t = 0.8f, width = 300f, height = 900f)
|
||||||
|
val expectedSize4 = RippleShader.SizeAtProgress(t = 1f, width = 500f, height = 300f)
|
||||||
|
|
||||||
|
rippleShader.rippleSize.setSizeAtProgresses(
|
||||||
|
expectedSize0,
|
||||||
|
expectedSize1,
|
||||||
|
expectedSize2,
|
||||||
|
expectedSize3,
|
||||||
|
expectedSize4
|
||||||
|
)
|
||||||
|
|
||||||
|
rippleShader.rippleSize.update(0.5f)
|
||||||
|
// Progress is between 0.4 and 0.8 (expectedSize3 and 4), so the index should be 3.
|
||||||
|
assertThat(rippleShader.rippleSize.currentSizeIndex).isEqualTo(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun update_sizeListIsEmpty_setsInitialSize() {
|
||||||
|
assertThat(rippleShader.rippleSize.sizes).isEmpty()
|
||||||
|
|
||||||
|
rippleShader.rippleSize.update(0.3f)
|
||||||
|
|
||||||
|
assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(1)
|
||||||
|
assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(rippleShader.rippleSize.initialSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user