Waveform change animation.

This commit is contained in:
Anton Chekulaev 2020-10-08 19:31:20 +11:00
parent 8cbb34f174
commit c7d89985a1
4 changed files with 129 additions and 80 deletions

View File

@ -79,12 +79,13 @@
android:layout_gravity="center_vertical"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
app:wave_gravity="center"
app:wave_width="4dp"
app:wave_corner_radius="2dp"
app:wave_gap="1dp"
tools:wave_background_color="#bbb"
tools:wave_progress_color="?colorPrimary"/>
app:bar_gravity="center"
app:bar_width="4dp"
app:bar_corner_radius="2dp"
app:bar_gap="1dp"
tools:progress="0.5"
tools:bar_background_color="#bbb"
tools:bar_progress_color="?colorPrimary"/>
<TextView android:id="@+id/total_duration"
android:layout_width="wrap_content"

View File

@ -289,15 +289,15 @@
</declare-styleable>
<declare-styleable name="WaveformSeekBar">
<attr name="wave_progress" format="float"/>
<attr name="wave_width" format="dimension"/>
<attr name="wave_gap" format="dimension"/>
<attr name="wave_min_height" format="dimension"/>
<attr name="wave_corner_radius" format="dimension"/>
<attr name="wave_background_color" format="color"/>
<attr name="wave_progress_color" format="color"/>
<attr name="progress" format="float"/>
<attr name="bar_width" format="dimension"/>
<attr name="bar_gap" format="dimension"/>
<attr name="bar_min_height" format="dimension"/>
<attr name="bar_corner_radius" format="dimension"/>
<attr name="bar_background_color" format="color"/>
<attr name="bar_progress_color" format="color"/>
<!-- Corresponds to WaveformSeekBar.WaveGravity enum. -->
<attr name="wave_gravity" format="enum">
<attr name="bar_gravity" format="enum">
<enum name="top" value="1" />
<enum name="center" value="2" />
<enum name="bottom" value="3" />

View File

@ -192,8 +192,8 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
// val colorFilter = createBlendModeColorFilterCompat(foregroundTint, BlendModeCompat.SRC_IN)
// seekBar.progressDrawable.colorFilter = colorFilter
// seekBar.thumb.colorFilter = colorFilter
seekBar.waveProgressColor = foregroundTint
seekBar.waveBackgroundColor = ColorUtils.blendARGB(foregroundTint, backgroundTint, 0.75f)
seekBar.barProgressColor = foregroundTint
seekBar.barBackgroundColor = ColorUtils.blendARGB(foregroundTint, backgroundTint, 0.75f)
}
override fun onPlayerStart(player: AudioSlidePlayer) {
@ -313,7 +313,7 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
android.util.Log.d(TAG, "RMS: ${rmsValues.joinToString()}")
post {
seekBar.sample = rmsValues
seekBar.sampleData = rmsValues
if (totalDurationMs > 0) {
totalDuration.visibility = View.VISIBLE

View File

@ -1,17 +1,23 @@
package org.thoughtcrime.securesms.loki.views
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.animation.DecelerateInterpolator
import network.loki.messenger.R
import java.lang.IllegalArgumentException
import java.lang.Math.abs
import kotlin.math.max
class WaveformSeekBar : View {
@ -19,17 +25,20 @@ class WaveformSeekBar : View {
@JvmStatic
inline fun dp(context: Context, dp: Float): Float {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
context.resources.displayMetrics
TypedValue.COMPLEX_UNIT_DIP,
dp,
context.resources.displayMetrics
)
}
}
var sample: FloatArray = floatArrayOf(0f)
private val sampleDataHolder = SampleDataHolder(::invalidate)
var sampleData: FloatArray?
get() {
return sampleDataHolder.getSamples()
}
set(value) {
if (value.isEmpty()) throw IllegalArgumentException("Sample array cannot be empty")
field = value
sampleDataHolder.setSamples(value)
invalidate()
}
@ -51,44 +60,43 @@ class WaveformSeekBar : View {
return _progress
}
var waveBackgroundColor: Int = Color.LTGRAY
var barBackgroundColor: Int = Color.LTGRAY
set(value) {
field = value
invalidate()
}
var waveProgressColor: Int = Color.WHITE
var barProgressColor: Int = Color.WHITE
set(value) {
field = value
invalidate()
}
var waveGap: Float = dp(context, 2f)
var barGap: Float = dp(context, 2f)
set(value) {
field = value
invalidate()
}
var waveWidth: Float = dp(context, 5f)
var barWidth: Float = dp(context, 5f)
set(value) {
field = value
invalidate()
}
var waveMinHeight: Float = waveWidth
var barMinHeight: Float = barWidth
set(value) {
field = value
invalidate()
}
var waveCornerRadius: Float = dp(context, 2.5f)
var barCornerRadius: Float = dp(context, 2.5f)
set(value) {
field = value
invalidate()
}
var waveGravity: WaveGravity =
WaveGravity.CENTER
var barGravity: WaveGravity = WaveGravity.CENTER
set(value) {
field = value
invalidate()
@ -101,8 +109,8 @@ class WaveformSeekBar : View {
progressChangeListener?.onProgressChanged(this, progress, true)
}
private val wavePaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val waveRect = RectF()
private val barPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val barRect = RectF()
private val progressCanvas = Canvas()
private var canvasWidth = 0
@ -117,28 +125,25 @@ class WaveformSeekBar : View {
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
: super(context, attrs, defStyleAttr) {
val typedAttrs = context.obtainStyledAttributes(attrs,
R.styleable.WaveformSeekBar
val typedAttrs = context.obtainStyledAttributes(attrs, R.styleable.WaveformSeekBar)
barWidth = typedAttrs.getDimension(R.styleable.WaveformSeekBar_bar_width, barWidth)
barGap = typedAttrs.getDimension(R.styleable.WaveformSeekBar_bar_gap, barGap)
barCornerRadius = typedAttrs.getDimension(
R.styleable.WaveformSeekBar_bar_corner_radius,
barCornerRadius
)
waveWidth = typedAttrs.getDimension(R.styleable.WaveformSeekBar_wave_width, waveWidth)
waveGap = typedAttrs.getDimension(R.styleable.WaveformSeekBar_wave_gap, waveGap)
waveCornerRadius = typedAttrs.getDimension(
R.styleable.WaveformSeekBar_wave_corner_radius,
waveCornerRadius
barMinHeight =
typedAttrs.getDimension(R.styleable.WaveformSeekBar_bar_min_height, barMinHeight)
barBackgroundColor = typedAttrs.getColor(
R.styleable.WaveformSeekBar_bar_background_color,
barBackgroundColor
)
waveMinHeight =
typedAttrs.getDimension(R.styleable.WaveformSeekBar_wave_min_height, waveMinHeight)
waveBackgroundColor = typedAttrs.getColor(
R.styleable.WaveformSeekBar_wave_background_color,
waveBackgroundColor
)
waveProgressColor =
typedAttrs.getColor(R.styleable.WaveformSeekBar_wave_progress_color, waveProgressColor)
progress = typedAttrs.getFloat(R.styleable.WaveformSeekBar_wave_progress, progress)
waveGravity =
barProgressColor =
typedAttrs.getColor(R.styleable.WaveformSeekBar_bar_progress_color, barProgressColor)
progress = typedAttrs.getFloat(R.styleable.WaveformSeekBar_progress, progress)
barGravity =
WaveGravity.fromString(
typedAttrs.getString(R.styleable.WaveformSeekBar_wave_gravity)
typedAttrs.getString(R.styleable.WaveformSeekBar_bar_gravity)
)
typedAttrs.recycle()
@ -146,47 +151,39 @@ class WaveformSeekBar : View {
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
canvasWidth = w
canvasHeight = h
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val totalWidth = getAvailableWith()
val totalWidth = getAvailableWidth()
val barAmount = (totalWidth / (barWidth + barGap)).toInt()
val step = (totalWidth / (waveGap + waveWidth)) / sample.size
var lastBarRight = paddingLeft.toFloat()
var lastWaveRight = paddingLeft.toFloat()
(0 until barAmount).forEach { barIdx ->
val barValue = sampleDataHolder.computeBarValue(barIdx, barAmount)
var i = 0f
while (i < sample.size) {
val barHeight = max(barMinHeight, getAvailableHeight() * barValue)
var waveHeight = getAvailableHeight() * sample[i.toInt()]
if (waveHeight < waveMinHeight) {
waveHeight = waveMinHeight
}
val top: Float = when (waveGravity) {
val top: Float = when (barGravity) {
WaveGravity.TOP -> paddingTop.toFloat()
WaveGravity.CENTER -> paddingTop + getAvailableHeight() / 2f - waveHeight / 2f
WaveGravity.BOTTOM -> canvasHeight - paddingBottom - waveHeight
WaveGravity.CENTER -> paddingTop + getAvailableHeight() * 0.5f - barHeight * 0.5f
WaveGravity.BOTTOM -> canvasHeight - paddingBottom - barHeight
}
waveRect.set(lastWaveRight, top, lastWaveRight + waveWidth, top + waveHeight)
barRect.set(lastBarRight, top, lastBarRight + barWidth, top + barHeight)
wavePaint.color = if (waveRect.right <= totalWidth * progress)
waveProgressColor else waveBackgroundColor
barPaint.color = if (barRect.right <= totalWidth * progress)
barProgressColor else barBackgroundColor
canvas.drawRoundRect(waveRect, waveCornerRadius, waveCornerRadius, wavePaint)
canvas.drawRoundRect(barRect, barCornerRadius, barCornerRadius, barPaint)
lastWaveRight = waveRect.right + waveGap
if (lastWaveRight + waveWidth > totalWidth + paddingLeft)
break
i += 1f / step
lastBarRight = barRect.right + barGap
}
}
@ -240,7 +237,7 @@ class WaveformSeekBar : View {
}
private fun updateProgress(event: MotionEvent, notify: Boolean) {
updateProgress(event.x / getAvailableWith(), notify)
updateProgress(event.x / getAvailableWidth(), notify)
}
private fun updateProgress(progress: Float, notify: Boolean) {
@ -270,9 +267,60 @@ class WaveformSeekBar : View {
return true
}
private fun getAvailableWith() = canvasWidth - paddingLeft - paddingRight
private fun getAvailableWidth() = canvasWidth - paddingLeft - paddingRight
private fun getAvailableHeight() = canvasHeight - paddingTop - paddingBottom
private class SampleDataHolder(private val invalidateDelegate: () -> Any) {
private var sampleDataFrom: FloatArray? = null
private var sampleDataTo: FloatArray? = null
private var progress = 1f // Mix between from and to values.
private var animation: ValueAnimator? = null
fun computeBarValue(barIdx: Int, barAmount: Int): Float {
fun getSampleValue(sampleData: FloatArray?): Float {
if (sampleData == null || sampleData.isEmpty())
return 0f
else {
val sampleIdx = (barIdx * (sampleData.size / barAmount.toFloat())).toInt()
return sampleData[sampleIdx]
}
}
if (progress == 1f) {
return getSampleValue(sampleDataTo)
}
val fromValue = getSampleValue(sampleDataFrom)
val toValue = getSampleValue(sampleDataTo)
return fromValue * (1f - progress) + toValue * progress
}
fun setSamples(sampleData: FloatArray?) {
//TODO Animate from the current value.
sampleDataFrom = sampleDataTo
sampleDataTo = sampleData
animation?.cancel()
animation = ValueAnimator.ofFloat(0f, 1f).apply {
addUpdateListener { animation ->
progress = animation.animatedValue as Float
Log.d("MTPHR", "Progress: $progress")
invalidateDelegate()
}
interpolator = DecelerateInterpolator(3f)
duration = 500
start()
}
}
fun getSamples(): FloatArray? {
return sampleDataTo
}
}
enum class WaveGravity {
TOP,
CENTER,