Merge "Squiggle progress smoothly transitions when seek is disabled" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
22c046e137
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user