Improve smoothness of segmented progress bar and respect video duration.

This commit is contained in:
Alex Hart
2022-03-03 13:12:28 -04:00
parent 63412b0153
commit 4e57432dbb
10 changed files with 116 additions and 64 deletions

View File

@@ -29,14 +29,14 @@ package org.thoughtcrime.securesms.components.segmentedprogressbar
*/
class Segment(val animationDurationMillis: Long) {
private var animationProgress: Int = 0
var animationProgressPercentage: Float = 0f
var animationState: AnimationState = AnimationState.IDLE
set(value) {
animationProgress = when (value) {
AnimationState.ANIMATED -> 100
AnimationState.IDLE -> 0
else -> animationProgress
animationProgressPercentage = when (value) {
AnimationState.ANIMATED -> 1f
AnimationState.IDLE -> 0f
else -> animationProgressPercentage
}
field = value
}
@@ -49,9 +49,4 @@ class Segment(val animationDurationMillis: Long) {
ANIMATING,
IDLE
}
val progressPercentage: Float
get() = animationProgress.toFloat() / 100
fun progress() = animationProgress++
}

View File

@@ -0,0 +1,6 @@
package org.thoughtcrime.securesms.components.segmentedprogressbar
data class SegmentState(
val position: Long,
val duration: Long
)

View File

@@ -28,13 +28,12 @@ import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Path
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.viewpager.widget.ViewPager
import org.thoughtcrime.securesms.R
import java.util.concurrent.TimeUnit
/**
* Created by Tiago Ornelas on 18/04/2020.
@@ -42,7 +41,14 @@ import org.thoughtcrime.securesms.R
* @see Segment
* And the progress of each segment is animated based on a set speed
*/
class SegmentedProgressBar : View, Runnable, ViewPager.OnPageChangeListener, View.OnTouchListener {
class SegmentedProgressBar : View, ViewPager.OnPageChangeListener, View.OnTouchListener {
companion object {
/**
* It is common now for devices to run at 60FPS
*/
val MILLIS_PER_FRAME = TimeUnit.MILLISECONDS.toMillis(17)
}
private val path = Path()
private val corners = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
@@ -57,7 +63,10 @@ class SegmentedProgressBar : View, Runnable, ViewPager.OnPageChangeListener, Vie
}
/**
* Mapping of segment index -> duration in millis
* Mapping of segment index -> duration in millis. Negative durations
* ARE valid but they'll result in a call to SegmentedProgressBarListener#onRequestSegmentProgressPercentage
* which should return the current % position for the currently playing item. This helps
* to avoid synchronizing the seek bar to playback.
*/
var segmentDurations: Map<Int, Long> = mapOf()
set(value) {
@@ -93,8 +102,6 @@ class SegmentedProgressBar : View, Runnable, ViewPager.OnPageChangeListener, Vie
private val selectedSegmentIndex: Int
get() = segments.indexOf(this.selectedSegment)
private val animationHandler = Handler(Looper.getMainLooper())
// Drawing
val strokeApplicable: Boolean
get() = segmentStrokeWidth * 4 <= measuredHeight
@@ -121,6 +128,8 @@ class SegmentedProgressBar : View, Runnable, ViewPager.OnPageChangeListener, Vie
*/
var listener: SegmentedProgressBarListener? = null
private var lastFrameTimeMillis: Long = 0L
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
@@ -225,6 +234,8 @@ class SegmentedProgressBar : View, Runnable, ViewPager.OnPageChangeListener, Vie
}
}
}
onFrame(System.currentTimeMillis())
}
/**
@@ -233,17 +244,20 @@ class SegmentedProgressBar : View, Runnable, ViewPager.OnPageChangeListener, Vie
fun start() {
pause()
val segment = selectedSegment
if (segment == null)
if (segment == null) {
next()
else
animationHandler.postDelayed(this, segment.animationDurationMillis / 100)
} else {
isPaused = false
invalidate()
}
}
/**
* Pauses the animation process
*/
fun pause() {
animationHandler.removeCallbacks(this)
isPaused = true
lastFrameTimeMillis = 0L
}
/**
@@ -327,15 +341,24 @@ class SegmentedProgressBar : View, Runnable, ViewPager.OnPageChangeListener, Vie
if (nextSegment != null) {
pause()
nextSegment.animationState = Segment.AnimationState.ANIMATING
animationHandler.postDelayed(this, nextSegment.animationDurationMillis / 100)
isPaused = false
invalidate()
this.listener?.onPage(oldSegmentIndex, this.selectedSegmentIndex)
viewPager?.currentItem = this.selectedSegmentIndex
} else {
animationHandler.removeCallbacks(this)
pause()
this.listener?.onFinished()
}
}
private fun getSegmentProgressPercentage(segment: Segment, timeSinceLastFrameMillis: Long): Float {
return if (segment.animationDurationMillis > 0) {
segment.animationProgressPercentage + timeSinceLastFrameMillis.toFloat() / segment.animationDurationMillis
} else {
listener?.onRequestSegmentProgressPercentage() ?: 0f
}
}
private fun initSegments() {
this.segments.clear()
segments.addAll(
@@ -348,12 +371,30 @@ class SegmentedProgressBar : View, Runnable, ViewPager.OnPageChangeListener, Vie
reset()
}
override fun run() {
if (this.selectedSegment?.progress() ?: 0 >= 100) {
private var isPaused = true
private fun onFrame(frameTimeMillis: Long) {
if (isPaused) {
return
}
val lastFrameTimeMillis = this.lastFrameTimeMillis
this.lastFrameTimeMillis = frameTimeMillis
val selectedSegment = this.selectedSegment
if (selectedSegment == null) {
loadSegment(offset = 1, userAction = false)
} else if (lastFrameTimeMillis > 0L) {
val segmentProgressPercentage = getSegmentProgressPercentage(selectedSegment, frameTimeMillis - lastFrameTimeMillis)
selectedSegment.animationProgressPercentage = segmentProgressPercentage
if (selectedSegment.animationProgressPercentage >= 1f) {
loadSegment(offset = 1, userAction = false)
} else {
this.invalidate()
}
} else {
this.invalidate()
animationHandler.postDelayed(this, this.selectedSegment?.animationDurationMillis?.let { it / 100 } ?: (timePerSegmentMs / 100))
}
}

View File

@@ -37,4 +37,6 @@ interface SegmentedProgressBarListener {
* Notifies when last segment finished animating
*/
fun onFinished()
fun onRequestSegmentProgressPercentage(): Float?
}

View File

@@ -78,7 +78,7 @@ fun SegmentedProgressBar.getDrawingComponents(
RectF(
startBound + stroke,
height - stroke,
startBound + segment.progressPercentage * segmentWidth,
startBound + segment.animationProgressPercentage * segmentWidth,
stroke
)
)