In my current app there is requirement to create custom video player and the special requirement is, to display or mark video progress-bar with different color at some given tim
One possibility is to create a custom view. By doing so you can draw exactly what you need on a canvas, and for a custom progress-bar view this is rather easy. However, it is not as quick as using built-in Views, but the advantage is you can customize it exactly as you want it. Do note this code is just a draft showing it is possible.
I created attributes so it is easy to customize the color of the progress-bar's components, and you can modify the height. The gif below shows the progress bar created at the bottom:
class IndicatorProgressBar(context: Context, attrs: AttributeSet) : View(context, attrs) {
private val TAG = "IndicatorProgressBar"
private var barColor = Color.GRAY
private var barHeight = 25F
private var indicatorColor = Color.CYAN
private var progressColor = Color.GREEN
private val paint = Paint()
lateinit var indicatorPositions: List
var progress = 0F // From float from 0 to 1
set(state) {
field = state
invalidate()
}
init {
paint.isAntiAlias = true
setupAttributes(attrs)
}
private fun setupAttributes(attrs: AttributeSet?) {
context.theme.obtainStyledAttributes(
attrs, R.styleable.IndicatorProgressBar,
0, 0
).apply {
barColor = getColor(R.styleable.IndicatorProgressBar_barColor, barColor)
barHeight = getFloat(R.styleable.IndicatorProgressBar_barHeight, barHeight)
progress = getFloat(R.styleable.IndicatorProgressBar_progress, progress)
progressColor = getColor(R.styleable.IndicatorProgressBar_progressColor, progressColor)
indicatorColor =
getColor(R.styleable.IndicatorProgressBar_indicatorColor, indicatorColor)
recycle()
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.style = Paint.Style.FILL // We will only use FILL for the progress bar's components.
drawProgressBar(canvas)
drawProgress(canvas)
drawIndicators(canvas)
}
/**
* Used to get the measuredWidth from the view as a float to be used in the draw methods.
*/
private fun width(): Float {
return measuredWidth.toFloat()
}
private fun drawProgressBar(canvas: Canvas) {
paint.color = barColor
drawCenteredBar(canvas, 0F, width())
}
private fun drawProgress(canvas: Canvas) {
paint.color = progressColor
val barWidth = (progress) * width()
drawCenteredBar(canvas, 0F, barWidth)
}
private fun drawIndicators(canvas: Canvas) {
paint.color = indicatorColor
indicatorPositions.forEach {
val barPositionCenter = it * width()
val barPositionLeft = barPositionCenter - 3F
val barPositionRight = barPositionCenter + 3F
drawCenteredBar(canvas, barPositionLeft, barPositionRight)
}
}
private fun drawCenteredBar(canvas: Canvas, left: Float, right: Float) {
val barTop = (measuredHeight - barHeight) / 2
val barBottom = (measuredHeight + barHeight) / 2
val barRect = RectF(left, barTop, right, barBottom)
canvas.drawRoundRect(barRect, 50F, 50F, paint)
}
override fun onSaveInstanceState(): Parcelable {
val bundle = Bundle()
bundle.putFloat("progress", progress)
bundle.putParcelable("superState", super.onSaveInstanceState())
return bundle
}
override fun onRestoreInstanceState(state: Parcelable) {
var viewState = state
if (viewState is Bundle) {
progress = viewState.getFloat("progress", progress)
viewState = viewState.getParcelable("superState")!!
}
super.onRestoreInstanceState(viewState)
}
override fun performClick(): Boolean {
super.performClick()
return true
}
override fun onTouchEvent(event: MotionEvent): Boolean {
super.onTouchEvent(event)
Log.d(TAG, "x=${event.x} / ${width()} (${event.x / measuredWidth}%), y=${event.y}")
when (event.action) {
MotionEvent.ACTION_DOWN -> {
return updateProgress(event)
}
MotionEvent.ACTION_MOVE -> {
return updateProgress(event)
}
MotionEvent.ACTION_UP -> {
performClick()
return true
}
}
return false
}
private fun updateProgress(event: MotionEvent): Boolean {
// percent may be outside the range (0..1)
val percent = event.x / width()
val boundedPercent = min(max(percent, 0F), 1F) // not above 1
progress = boundedPercent
invalidate() // Make the view redraw itself
return true
}
}
The attributes for custom views are defined in res/values/attrs.xml
You use the custom view in layouts like this:
Main activity:
class MainActivity : AppCompatActivity() {
private lateinit var indicatorProgressBar: IndicatorProgressBar
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined)
private val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
indicatorProgressBar = findViewById(R.id.indicatorProgressBar)
indicatorProgressBar.indicatorPositions = listOf(0.13F, 0.34F, 0.57F, 0.85F, 0.92F)
updateCurrentTime()
indicatorProgressBar.setOnClickListener {
if(indicatorProgressBar.progress >= 1F){
updateCurrentTime()
}
}
}
private fun updateCurrentTime() {
scope.launch {
while (indicatorProgressBar.progress <= 1F){
Log.d(TAG, "In while loop")
delay(33)
runOnUiThread{
indicatorProgressBar.progress += 0.003F
Log.d(TAG, "Progress is now: ${indicatorProgressBar.progress}")
}
}
}
}
Add Kotlin Coroutines to your dependencies in build.gradle (app) if you want to run the updateCurrentTime method in the MainActivity:
dependencies {
...
def coroutines_version = "1.3.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
}