How to mark horizontal ProgressBar with different color at some index just like Youtube video yellow color ad marker in Android

前端 未结 2 1304
终归单人心
终归单人心 2021-02-08 08:07

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

2条回答
  •  耶瑟儿~
    2021-02-08 08:55

    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"
    }
    

提交回复
热议问题