Merge "Squiggle progress smoothly transitions when seek is disabled" into tm-dev am: 22c046e137

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17747676

Change-Id: I0bd89cdf86c1b685f3892ccbaa6ec5a2cf5bbf04
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
TreeHugger Robot
2022-04-15 23:31:01 +00:00
committed by Automerger Merge Worker
3 changed files with 101 additions and 59 deletions

View File

@@ -89,6 +89,7 @@ open class SeekBarObserver(
holder.seekBar.thumb.alpha = if (data.seekAvailable) 255 else 0 holder.seekBar.thumb.alpha = if (data.seekAvailable) 255 else 0
holder.seekBar.isEnabled = data.seekAvailable holder.seekBar.isEnabled = data.seekAvailable
progressDrawable?.animate = data.playing && !data.scrubbing progressDrawable?.animate = data.playing && !data.scrubbing
progressDrawable?.transitionEnabled = !data.seekAvailable
if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) { if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) {
holder.seekBar.maxHeight = seekBarEnabledMaxHeight holder.seekBar.maxHeight = seekBarEnabledMaxHeight

View File

@@ -5,13 +5,15 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter import android.graphics.ColorFilter
import android.graphics.Paint import android.graphics.Paint
import android.graphics.Path import android.graphics.Path
import android.graphics.PixelFormat import android.graphics.PixelFormat
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.SystemClock import android.os.SystemClock
import android.util.MathUtils.lerp
import android.util.MathUtils.lerpInv
import android.util.MathUtils.lerpInvSat
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import com.android.internal.graphics.ColorUtils import com.android.internal.graphics.ColorUtils
import com.android.systemui.animation.Interpolators import com.android.systemui.animation.Interpolators
@@ -34,6 +36,13 @@ class SquigglyProgress : Drawable() {
private var phaseOffset = 0f private var phaseOffset = 0f
private var lastFrameTime = -1L private var lastFrameTime = -1L
/* distance over which amplitude drops to zero, measured in wavelengths */
private val transitionPeriods = 1.5f
/* wave endpoint as percentage of bar when play position is zero */
private val minWaveEndpoint = 0.2f
/* wave endpoint as percentage of bar when play position matches wave endpoint */
private val matchedWaveEndpoint = 0.6f
// Horizontal length of the sine wave // Horizontal length of the sine wave
var waveLength = 0f var waveLength = 0f
// Height of each peak of the sine wave // Height of each peak of the sine wave
@@ -51,6 +60,12 @@ class SquigglyProgress : Drawable() {
linePaint.strokeWidth = value linePaint.strokeWidth = value
} }
var transitionEnabled = true
set(value) {
field = value
invalidateSelf()
}
init { init {
wavePaint.strokeCap = Paint.Cap.ROUND wavePaint.strokeCap = Paint.Cap.ROUND
linePaint.strokeCap = Paint.Cap.ROUND linePaint.strokeCap = Paint.Cap.ROUND
@@ -95,57 +110,90 @@ class SquigglyProgress : Drawable() {
if (animate) { if (animate) {
invalidateSelf() invalidateSelf()
val now = SystemClock.uptimeMillis() val now = SystemClock.uptimeMillis()
phaseOffset -= (now - lastFrameTime) / 1000f * phaseSpeed phaseOffset += (now - lastFrameTime) / 1000f * phaseSpeed
phaseOffset %= waveLength phaseOffset %= waveLength
lastFrameTime = now lastFrameTime = now
} }
val totalProgressPx = (bounds.width() * (level / 10_000f)) val progress = level / 10_000f
val totalProgressPx = bounds.width() * progress
val waveProgressPx = bounds.width() * (
if (!transitionEnabled || progress > matchedWaveEndpoint) progress else
lerp(minWaveEndpoint, matchedWaveEndpoint, lerpInv(0f, matchedWaveEndpoint, progress)))
// Build Wiggly Path
val waveStart = -phaseOffset
val waveEnd = waveProgressPx
val transitionLength = if (transitionEnabled) transitionPeriods * waveLength else 0.01f
// helper function, computes amplitude for wave segment
val computeAmplitude: (Float, Float) -> Float = { x, sign ->
sign * heightFraction * lineAmplitude *
lerpInvSat(waveEnd, waveEnd - transitionLength, x)
}
var currentX = waveEnd
var waveSign = if (phaseOffset < waveLength / 2) 1f else -1f
path.rewind()
// Draw flat line from end to wave endpoint
path.moveTo(bounds.width().toFloat(), 0f)
path.lineTo(waveEnd, 0f)
// First wave has shortened wavelength
// approx quarter wave gets us to first wave peak
// shouldn't be big enough to notice it's not a sin wave
currentX -= phaseOffset % (waveLength / 2)
val controlRatio = 0.25f
var currentAmp = computeAmplitude(currentX, waveSign)
path.cubicTo(
waveEnd, currentAmp * controlRatio,
lerp(currentX, waveEnd, controlRatio), currentAmp,
currentX, currentAmp)
// Other waves have full wavelength
val dist = -1 * waveLength / 2f
while (currentX > waveStart) {
waveSign = -waveSign
val nextX = currentX + dist
val midX = currentX + dist / 2
val nextAmp = computeAmplitude(nextX, waveSign)
path.cubicTo(
midX, currentAmp,
midX, nextAmp,
nextX, nextAmp)
currentAmp = nextAmp
currentX = nextX
}
// Draw path; clip to progress position
canvas.save() canvas.save()
canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat()) canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat())
// Clip drawing, so we stop at the thumb
canvas.clipRect( canvas.clipRect(
0f, 0f,
-lineAmplitude - strokeWidth, -lineAmplitude - strokeWidth,
totalProgressPx, totalProgressPx,
lineAmplitude + strokeWidth) lineAmplitude + strokeWidth)
// The squiggly line
val start = phaseOffset
var currentX = start
var waveSign = 1f
path.rewind()
path.moveTo(start, lineAmplitude * heightFraction)
while (currentX < totalProgressPx) {
val nextX = currentX + waveLength / 2f
val nextWaveSign = waveSign * -1
path.cubicTo(
currentX + waveLength / 4f, lineAmplitude * waveSign * heightFraction,
nextX - waveLength / 4f, lineAmplitude * nextWaveSign * heightFraction,
nextX, lineAmplitude * nextWaveSign * heightFraction)
currentX = nextX
waveSign = nextWaveSign
}
wavePaint.style = Paint.Style.STROKE
canvas.drawPath(path, wavePaint) canvas.drawPath(path, wavePaint)
canvas.restore() canvas.restore()
// Draw path; clip between progression position & far edge
canvas.save()
canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat())
canvas.clipRect(
totalProgressPx,
-lineAmplitude - strokeWidth,
bounds.width().toFloat(),
lineAmplitude + strokeWidth)
canvas.drawPath(path, linePaint)
canvas.restore()
// Draw round line cap at the beginning of the wave // Draw round line cap at the beginning of the wave
val startAmp = cos(abs(phaseOffset) / waveLength * TWO_PI) val startAmp = cos(abs(waveEnd - phaseOffset) / waveLength * TWO_PI)
val p = Paint()
p.color = Color.WHITE
canvas.drawPoint( canvas.drawPoint(
bounds.left.toFloat(), bounds.left.toFloat(),
bounds.centerY() + startAmp * lineAmplitude * heightFraction, bounds.centerY() + startAmp * lineAmplitude * heightFraction,
wavePaint) wavePaint)
// Draw continuous line, to the right of the thumb
canvas.drawLine(
bounds.left.toFloat() + totalProgressPx,
bounds.centerY().toFloat(),
bounds.width().toFloat(),
bounds.centerY().toFloat(),
linePaint)
} }
override fun getOpacity(): Int { override fun getOpacity(): Int {

View File

@@ -19,7 +19,7 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor import org.mockito.ArgumentCaptor
import org.mockito.Captor import org.mockito.Captor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.anyFloat import org.mockito.Mockito.times
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoJUnit
@@ -35,8 +35,7 @@ class SquigglyProgressTest : SysuiTestCase() {
lateinit var squigglyProgress: SquigglyProgress lateinit var squigglyProgress: SquigglyProgress
@Mock lateinit var canvas: Canvas @Mock lateinit var canvas: Canvas
@Captor lateinit var wavePaintCaptor: ArgumentCaptor<Paint> @Captor lateinit var paintCaptor: ArgumentCaptor<Paint>
@Captor lateinit var linePaintCaptor: ArgumentCaptor<Paint>
@JvmField @Rule val mockitoRule = MockitoJUnit.rule() @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
@Before @Before
@@ -53,9 +52,7 @@ class SquigglyProgressTest : SysuiTestCase() {
fun testDrawPathAndLine() { fun testDrawPathAndLine() {
squigglyProgress.draw(canvas) squigglyProgress.draw(canvas)
verify(canvas).drawPath(any(), wavePaintCaptor.capture()) verify(canvas, times(2)).drawPath(any(), paintCaptor.capture())
verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(),
linePaintCaptor.capture())
} }
@Test @Test
@@ -69,12 +66,11 @@ class SquigglyProgressTest : SysuiTestCase() {
fun testStrokeWidth() { fun testStrokeWidth() {
squigglyProgress.draw(canvas) squigglyProgress.draw(canvas)
verify(canvas).drawPath(any(), wavePaintCaptor.capture()) verify(canvas, times(2)).drawPath(any(), paintCaptor.capture())
verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), val (wavePaint, linePaint) = paintCaptor.getAllValues()
linePaintCaptor.capture())
assertThat(wavePaintCaptor.value.strokeWidth).isEqualTo(strokeWidth) assertThat(wavePaint.strokeWidth).isEqualTo(strokeWidth)
assertThat(linePaintCaptor.value.strokeWidth).isEqualTo(strokeWidth) assertThat(linePaint.strokeWidth).isEqualTo(strokeWidth)
} }
@Test @Test
@@ -82,13 +78,12 @@ class SquigglyProgressTest : SysuiTestCase() {
squigglyProgress.alpha = alpha squigglyProgress.alpha = alpha
squigglyProgress.draw(canvas) squigglyProgress.draw(canvas)
verify(canvas).drawPath(any(), wavePaintCaptor.capture()) verify(canvas, times(2)).drawPath(any(), paintCaptor.capture())
verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), val (wavePaint, linePaint) = paintCaptor.getAllValues()
linePaintCaptor.capture())
assertThat(squigglyProgress.alpha).isEqualTo(alpha) assertThat(squigglyProgress.alpha).isEqualTo(alpha)
assertThat(wavePaintCaptor.value.alpha).isEqualTo(alpha) assertThat(wavePaint.alpha).isEqualTo(alpha)
assertThat(linePaintCaptor.value.alpha).isEqualTo((alpha / 255f * DISABLED_ALPHA).toInt()) assertThat(linePaint.alpha).isEqualTo((alpha / 255f * DISABLED_ALPHA).toInt())
} }
@Test @Test
@@ -96,12 +91,11 @@ class SquigglyProgressTest : SysuiTestCase() {
squigglyProgress.colorFilter = colorFilter squigglyProgress.colorFilter = colorFilter
squigglyProgress.draw(canvas) squigglyProgress.draw(canvas)
verify(canvas).drawPath(any(), wavePaintCaptor.capture()) verify(canvas, times(2)).drawPath(any(), paintCaptor.capture())
verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), val (wavePaint, linePaint) = paintCaptor.getAllValues()
linePaintCaptor.capture())
assertThat(wavePaintCaptor.value.colorFilter).isEqualTo(colorFilter) assertThat(wavePaint.colorFilter).isEqualTo(colorFilter)
assertThat(linePaintCaptor.value.colorFilter).isEqualTo(colorFilter) assertThat(linePaint.colorFilter).isEqualTo(colorFilter)
} }
@Test @Test
@@ -109,12 +103,11 @@ class SquigglyProgressTest : SysuiTestCase() {
squigglyProgress.setTint(tint) squigglyProgress.setTint(tint)
squigglyProgress.draw(canvas) squigglyProgress.draw(canvas)
verify(canvas).drawPath(any(), wavePaintCaptor.capture()) verify(canvas, times(2)).drawPath(any(), paintCaptor.capture())
verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), val (wavePaint, linePaint) = paintCaptor.getAllValues()
linePaintCaptor.capture())
assertThat(wavePaintCaptor.value.color).isEqualTo(tint) assertThat(wavePaint.color).isEqualTo(tint)
assertThat(linePaintCaptor.value.color).isEqualTo( assertThat(linePaint.color).isEqualTo(
ColorUtils.setAlphaComponent(tint, DISABLED_ALPHA)) ColorUtils.setAlphaComponent(tint, DISABLED_ALPHA))
} }
} }