Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

android

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 time-index just like Youtube marks its video with yellow color to indicate ads on its video progress bar.

For more clarity please check below screen-shot:

enter image description here

At this moment I'm done with Video Player functionality using VideoView and for now, I'm using Horizontal ProgressBar to show video progress. I did lot of research, but unfortunately no result so far. There is no compulsion to use only Horizontal ProgressBar, we can use SeekBar or inbuilt MediaController too, but the thing is we should be able to mark it with different color at some given position. Below is my code I have written so far:

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"
    android:paddingLeft="5dp"
    android:paddingTop="5dp"
    android:paddingRight="5dp"
    android:paddingBottom="5dp"
    tools:context=".MainActivity">

    <VideoView
        android:id="@+id/myVideo"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_centerInParent="true" />

    <RelativeLayout
        android:id="@+id/rlVidProgress"
        android:layout_width="match_parent"
        android:layout_alignParentBottom="true"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tvCurrentTime"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="20dp"
            android:text="00:00"
            android:textColor="@android:color/white"
            android:textSize="13dp"
            android:textStyle="bold" />

        <ProgressBar
            android:id="@+id/videoProgress"
            style="@android:style/Widget.ProgressBar.Horizontal"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@+id/tvTotalTime"
            android:layout_width="fill_parent"
            android:layout_toRightOf="@+id/tvCurrentTime"
            android:layout_height="10dp"
            android:layout_marginBottom="10dp" />

        <TextView
            android:id="@+id/tvTotalTime"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_alignParentRight="true"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="5dp"
            android:text="00:00"
            android:textColor="@android:color/white"
            android:textSize="13dp"
            android:textStyle="bold" />

    </RelativeLayout>

    <TextView
        android:id="@+id/tvAutoSave"
        android:layout_width="wrap_content"
        android:textColor="@android:color/white"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="10dp"
        android:text="@string/click_to_auto_save_offer"
        android:background="@drawable/white_border_bg"
        android:textSize="13dp"
        android:layout_above="@+id/rlVidProgress"
        android:visibility="gone"
        android:paddingBottom="3dp"
        android:paddingTop="3dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textStyle="bold"
        android:layout_height="wrap_content" />

</RelativeLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
    ProgressBar videoProgress;
    TextView tvCurrentTime;
    Handler handler;
    VideoView vidView;
    String strTotalDuration;
    TextView tvAutoSave;
    Animation animFadeIn, animFadeOut;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        animFadeIn = new AlphaAnimation(0, 1);
        animFadeIn.setInterpolator(new DecelerateInterpolator()); //add this
        animFadeIn.setDuration(1000);

        animFadeOut = new AlphaAnimation(1, 0);
        animFadeOut.setInterpolator(new AccelerateInterpolator()); //and this
        //animFadeOut.setStartOffset(500);
        animFadeOut.setDuration(500);


        tvCurrentTime = (TextView) findViewById(R.id.tvCurrentTime);
        final TextView tvTotalTime = (TextView) findViewById(R.id.tvTotalTime);
        tvAutoSave = (TextView) findViewById(R.id.tvAutoSave);
        tvAutoSave.setOnClickListener(this);

        vidView = (VideoView)findViewById(R.id.myVideo);

        videoProgress = (ProgressBar) findViewById(R.id.videoProgress);
        videoProgress.setProgress(0);
        videoProgress.setMax(100);

        final String videoSource = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
        vidView.setKeepScreenOn(true);
        vidView.setVideoURI(Uri.parse(videoSource));
        vidView.setMediaController(null);

        handler = new Handler();
        // Define the code block to be executed
        final Runnable runnableCode = new Runnable() {
            @Override
            public void run()
            {
                updateCurrentTime();
                // Repeat this the same runnable code block again another 1 seconds
                // 'this' is referencing the Runnable object
                handler.postDelayed(this, 1000);
            }
        };

        vidView.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
        {
            @Override
            public void onCompletion(MediaPlayer mp)
            {
                if(tvAutoSave.getVisibility() == View.VISIBLE)
                {
                    tvAutoSave.setVisibility(View.GONE);
                    tvAutoSave.startAnimation(animFadeOut);
                }
            }
        });

        vidView.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
                                      {
                                          @Override
                                          public void onPrepared(MediaPlayer mp)
                                          {
                                              vidView.start();
                                              strTotalDuration = msToTimeConverter(vidView.getDuration());
                                              tvTotalTime.setText(""+strTotalDuration);
                                              // Start the initial runnable task by posting through the handler
                                              handler.post(runnableCode);
                                              //startHandler();
                                          }
                                      }


        );
    }


    private void updateCurrentTime()
    {
        if (videoProgress.getProgress() >= 100)
        {
            handler.removeMessages(0);
        }
        String currentPosition = msToTimeConverter(vidView.getCurrentPosition());

        String[] strArr = currentPosition.split(":");

        if(strArr.length==2 && (strArr[1].equals("06") || strArr[1].equals("6")))
        {
            Toast.makeText(MainActivity.this, "Trigger success at 6 sec position.......", Toast.LENGTH_SHORT).show();
            tvAutoSave.setVisibility(View.VISIBLE);
            tvAutoSave.startAnimation(animFadeIn);
        }

        int progress = vidView.getCurrentPosition() * 100 / vidView.getDuration();
        videoProgress.setProgress(progress);

        tvCurrentTime.setText(""+currentPosition);
    }

    String msToTimeConverter(int millis)
    {
        return String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)),
                TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
    }

    @Override
    public void onClick(View v)
    {
        switch (v.getId())
        {
            case R.id.tvAutoSave:
                tvAutoSave.setVisibility(View.GONE);
                tvAutoSave.startAnimation(animFadeOut);
                Toast.makeText(MainActivity.this, "Offer(s) saved in wallet successfully", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}
like image 904
Dnyanesh M Avatar asked Jun 12 '19 10:06

Dnyanesh M


1 Answers

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:

Progress-bar in action

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<Float>
    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

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="IndicatorProgressBar">
        <!-- The color of the progressbar (not the progress)-->
        <attr name="barColor" format="color" />
        <!-- The color of the indicators on the progress bar (such as ads on YouTube)-->
        <attr name="indicatorColor" format="color" />
        <!-- The color of the progressed time/work of the progressbar-->
        <attr name="progressColor" format="color" />
        <!-- The initial progress value, a value from 0 to 1 -->
        <attr name="progress" format="float"/>
        <!-- The height of the progress bar, note that layout_height should be set to a larger
        number so the onTouchEvent listener is more easy to trigger-->
        <attr name="barHeight" format="float"/>

    </declare-styleable>
</resources>

You use the custom view in layouts like this:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"
    android:gravity="bottom"
    android:paddingLeft="5dp"
    android:paddingTop="5dp"
    android:paddingRight="5dp"
    android:paddingBottom="5dp"
    tools:context=".MainActivity">

    <com.yourpackagename.progressbarindicator.IndicatorProgressBar
        android:id="@+id/indicatorProgressBar"
        android:layout_width="wrap_content"
        android:layout_height="25dp"
        android:layout_centerVertical="true"
        android:foregroundGravity="center"
        app:barColor="@color/colorAccent"
        app:barHeight="12"
        app:indicatorColor="#ffffff"
        app:progress="0"
        app:progressColor="#11c011" />

</RelativeLayout>

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"
}
like image 153
Tor-Martin Holen Avatar answered Sep 28 '22 01:09

Tor-Martin Holen