mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-30 21:45:20 +00:00
Waveform change animation.
This commit is contained in:
parent
8cbb34f174
commit
c7d89985a1
@ -79,12 +79,13 @@
|
|||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
app:wave_gravity="center"
|
app:bar_gravity="center"
|
||||||
app:wave_width="4dp"
|
app:bar_width="4dp"
|
||||||
app:wave_corner_radius="2dp"
|
app:bar_corner_radius="2dp"
|
||||||
app:wave_gap="1dp"
|
app:bar_gap="1dp"
|
||||||
tools:wave_background_color="#bbb"
|
tools:progress="0.5"
|
||||||
tools:wave_progress_color="?colorPrimary"/>
|
tools:bar_background_color="#bbb"
|
||||||
|
tools:bar_progress_color="?colorPrimary"/>
|
||||||
|
|
||||||
<TextView android:id="@+id/total_duration"
|
<TextView android:id="@+id/total_duration"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -289,15 +289,15 @@
|
|||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="WaveformSeekBar">
|
<declare-styleable name="WaveformSeekBar">
|
||||||
<attr name="wave_progress" format="float"/>
|
<attr name="progress" format="float"/>
|
||||||
<attr name="wave_width" format="dimension"/>
|
<attr name="bar_width" format="dimension"/>
|
||||||
<attr name="wave_gap" format="dimension"/>
|
<attr name="bar_gap" format="dimension"/>
|
||||||
<attr name="wave_min_height" format="dimension"/>
|
<attr name="bar_min_height" format="dimension"/>
|
||||||
<attr name="wave_corner_radius" format="dimension"/>
|
<attr name="bar_corner_radius" format="dimension"/>
|
||||||
<attr name="wave_background_color" format="color"/>
|
<attr name="bar_background_color" format="color"/>
|
||||||
<attr name="wave_progress_color" format="color"/>
|
<attr name="bar_progress_color" format="color"/>
|
||||||
<!-- Corresponds to WaveformSeekBar.WaveGravity enum. -->
|
<!-- Corresponds to WaveformSeekBar.WaveGravity enum. -->
|
||||||
<attr name="wave_gravity" format="enum">
|
<attr name="bar_gravity" format="enum">
|
||||||
<enum name="top" value="1" />
|
<enum name="top" value="1" />
|
||||||
<enum name="center" value="2" />
|
<enum name="center" value="2" />
|
||||||
<enum name="bottom" value="3" />
|
<enum name="bottom" value="3" />
|
||||||
|
@ -192,8 +192,8 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
|
|||||||
// val colorFilter = createBlendModeColorFilterCompat(foregroundTint, BlendModeCompat.SRC_IN)
|
// val colorFilter = createBlendModeColorFilterCompat(foregroundTint, BlendModeCompat.SRC_IN)
|
||||||
// seekBar.progressDrawable.colorFilter = colorFilter
|
// seekBar.progressDrawable.colorFilter = colorFilter
|
||||||
// seekBar.thumb.colorFilter = colorFilter
|
// seekBar.thumb.colorFilter = colorFilter
|
||||||
seekBar.waveProgressColor = foregroundTint
|
seekBar.barProgressColor = foregroundTint
|
||||||
seekBar.waveBackgroundColor = ColorUtils.blendARGB(foregroundTint, backgroundTint, 0.75f)
|
seekBar.barBackgroundColor = ColorUtils.blendARGB(foregroundTint, backgroundTint, 0.75f)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlayerStart(player: AudioSlidePlayer) {
|
override fun onPlayerStart(player: AudioSlidePlayer) {
|
||||||
@ -313,7 +313,7 @@ class MessageAudioView: FrameLayout, AudioSlidePlayer.Listener {
|
|||||||
android.util.Log.d(TAG, "RMS: ${rmsValues.joinToString()}")
|
android.util.Log.d(TAG, "RMS: ${rmsValues.joinToString()}")
|
||||||
|
|
||||||
post {
|
post {
|
||||||
seekBar.sample = rmsValues
|
seekBar.sampleData = rmsValues
|
||||||
|
|
||||||
if (totalDurationMs > 0) {
|
if (totalDurationMs > 0) {
|
||||||
totalDuration.visibility = View.VISIBLE
|
totalDuration.visibility = View.VISIBLE
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
package org.thoughtcrime.securesms.loki.views
|
package org.thoughtcrime.securesms.loki.views
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
import android.content.Context
|
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.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewConfiguration
|
import android.view.ViewConfiguration
|
||||||
|
import android.view.animation.DecelerateInterpolator
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import java.lang.IllegalArgumentException
|
|
||||||
import java.lang.Math.abs
|
import java.lang.Math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
class WaveformSeekBar : View {
|
class WaveformSeekBar : View {
|
||||||
|
|
||||||
@ -26,10 +32,13 @@ class WaveformSeekBar : View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var sample: FloatArray = floatArrayOf(0f)
|
private val sampleDataHolder = SampleDataHolder(::invalidate)
|
||||||
|
var sampleData: FloatArray?
|
||||||
|
get() {
|
||||||
|
return sampleDataHolder.getSamples()
|
||||||
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value.isEmpty()) throw IllegalArgumentException("Sample array cannot be empty")
|
sampleDataHolder.setSamples(value)
|
||||||
field = value
|
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,44 +60,43 @@ class WaveformSeekBar : View {
|
|||||||
return _progress
|
return _progress
|
||||||
}
|
}
|
||||||
|
|
||||||
var waveBackgroundColor: Int = Color.LTGRAY
|
var barBackgroundColor: Int = Color.LTGRAY
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
var waveProgressColor: Int = Color.WHITE
|
var barProgressColor: Int = Color.WHITE
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
var waveGap: Float = dp(context, 2f)
|
var barGap: Float = dp(context, 2f)
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
var waveWidth: Float = dp(context, 5f)
|
var barWidth: Float = dp(context, 5f)
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
var waveMinHeight: Float = waveWidth
|
var barMinHeight: Float = barWidth
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
var waveCornerRadius: Float = dp(context, 2.5f)
|
var barCornerRadius: Float = dp(context, 2.5f)
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
var waveGravity: WaveGravity =
|
var barGravity: WaveGravity = WaveGravity.CENTER
|
||||||
WaveGravity.CENTER
|
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
invalidate()
|
invalidate()
|
||||||
@ -101,8 +109,8 @@ class WaveformSeekBar : View {
|
|||||||
progressChangeListener?.onProgressChanged(this, progress, true)
|
progressChangeListener?.onProgressChanged(this, progress, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val wavePaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
private val barPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||||
private val waveRect = RectF()
|
private val barRect = RectF()
|
||||||
private val progressCanvas = Canvas()
|
private val progressCanvas = Canvas()
|
||||||
|
|
||||||
private var canvasWidth = 0
|
private var canvasWidth = 0
|
||||||
@ -117,28 +125,25 @@ class WaveformSeekBar : View {
|
|||||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
|
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
|
||||||
: super(context, attrs, defStyleAttr) {
|
: super(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
val typedAttrs = context.obtainStyledAttributes(attrs,
|
val typedAttrs = context.obtainStyledAttributes(attrs, R.styleable.WaveformSeekBar)
|
||||||
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
|
||||||
)
|
)
|
||||||
|
barMinHeight =
|
||||||
waveWidth = typedAttrs.getDimension(R.styleable.WaveformSeekBar_wave_width, waveWidth)
|
typedAttrs.getDimension(R.styleable.WaveformSeekBar_bar_min_height, barMinHeight)
|
||||||
waveGap = typedAttrs.getDimension(R.styleable.WaveformSeekBar_wave_gap, waveGap)
|
barBackgroundColor = typedAttrs.getColor(
|
||||||
waveCornerRadius = typedAttrs.getDimension(
|
R.styleable.WaveformSeekBar_bar_background_color,
|
||||||
R.styleable.WaveformSeekBar_wave_corner_radius,
|
barBackgroundColor
|
||||||
waveCornerRadius
|
|
||||||
)
|
)
|
||||||
waveMinHeight =
|
barProgressColor =
|
||||||
typedAttrs.getDimension(R.styleable.WaveformSeekBar_wave_min_height, waveMinHeight)
|
typedAttrs.getColor(R.styleable.WaveformSeekBar_bar_progress_color, barProgressColor)
|
||||||
waveBackgroundColor = typedAttrs.getColor(
|
progress = typedAttrs.getFloat(R.styleable.WaveformSeekBar_progress, progress)
|
||||||
R.styleable.WaveformSeekBar_wave_background_color,
|
barGravity =
|
||||||
waveBackgroundColor
|
|
||||||
)
|
|
||||||
waveProgressColor =
|
|
||||||
typedAttrs.getColor(R.styleable.WaveformSeekBar_wave_progress_color, waveProgressColor)
|
|
||||||
progress = typedAttrs.getFloat(R.styleable.WaveformSeekBar_wave_progress, progress)
|
|
||||||
waveGravity =
|
|
||||||
WaveGravity.fromString(
|
WaveGravity.fromString(
|
||||||
typedAttrs.getString(R.styleable.WaveformSeekBar_wave_gravity)
|
typedAttrs.getString(R.styleable.WaveformSeekBar_bar_gravity)
|
||||||
)
|
)
|
||||||
|
|
||||||
typedAttrs.recycle()
|
typedAttrs.recycle()
|
||||||
@ -146,47 +151,39 @@ class WaveformSeekBar : View {
|
|||||||
|
|
||||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||||
super.onSizeChanged(w, h, oldw, oldh)
|
super.onSizeChanged(w, h, oldw, oldh)
|
||||||
|
|
||||||
canvasWidth = w
|
canvasWidth = w
|
||||||
canvasHeight = h
|
canvasHeight = h
|
||||||
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
super.onDraw(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
|
val barHeight = max(barMinHeight, getAvailableHeight() * barValue)
|
||||||
while (i < sample.size) {
|
|
||||||
|
|
||||||
var waveHeight = getAvailableHeight() * sample[i.toInt()]
|
val top: Float = when (barGravity) {
|
||||||
|
|
||||||
if (waveHeight < waveMinHeight) {
|
|
||||||
waveHeight = waveMinHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
val top: Float = when (waveGravity) {
|
|
||||||
WaveGravity.TOP -> paddingTop.toFloat()
|
WaveGravity.TOP -> paddingTop.toFloat()
|
||||||
WaveGravity.CENTER -> paddingTop + getAvailableHeight() / 2f - waveHeight / 2f
|
WaveGravity.CENTER -> paddingTop + getAvailableHeight() * 0.5f - barHeight * 0.5f
|
||||||
WaveGravity.BOTTOM -> canvasHeight - paddingBottom - waveHeight
|
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)
|
barPaint.color = if (barRect.right <= totalWidth * progress)
|
||||||
waveProgressColor else waveBackgroundColor
|
barProgressColor else barBackgroundColor
|
||||||
|
|
||||||
canvas.drawRoundRect(waveRect, waveCornerRadius, waveCornerRadius, wavePaint)
|
canvas.drawRoundRect(barRect, barCornerRadius, barCornerRadius, barPaint)
|
||||||
|
|
||||||
lastWaveRight = waveRect.right + waveGap
|
lastBarRight = barRect.right + barGap
|
||||||
|
|
||||||
if (lastWaveRight + waveWidth > totalWidth + paddingLeft)
|
|
||||||
break
|
|
||||||
|
|
||||||
i += 1f / step
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +237,7 @@ class WaveformSeekBar : View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateProgress(event: MotionEvent, notify: Boolean) {
|
private fun updateProgress(event: MotionEvent, notify: Boolean) {
|
||||||
updateProgress(event.x / getAvailableWith(), notify)
|
updateProgress(event.x / getAvailableWidth(), notify)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateProgress(progress: Float, notify: Boolean) {
|
private fun updateProgress(progress: Float, notify: Boolean) {
|
||||||
@ -270,9 +267,60 @@ class WaveformSeekBar : View {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAvailableWith() = canvasWidth - paddingLeft - paddingRight
|
private fun getAvailableWidth() = canvasWidth - paddingLeft - paddingRight
|
||||||
private fun getAvailableHeight() = canvasHeight - paddingTop - paddingBottom
|
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 {
|
enum class WaveGravity {
|
||||||
TOP,
|
TOP,
|
||||||
CENTER,
|
CENTER,
|
||||||
|
Loading…
Reference in New Issue
Block a user