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

前端 未结 2 1252
终归单人心
终归单人心 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:36

    Finally I got the solution. Below are the steps to implement the same--

    Step-1] Create one "attrs.xml" file in "res/values/" folder and paste below code in that file--

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="DottedSeekBar">
            <attr name="dots_positions" format="reference"/>
            <attr name="dots_drawable" format="reference"/>
        </declare-styleable>
    </resources>
    

    Step-2] Prepare one image icon which you want to use to mark on progress bar and name it "video_mark.png".

    Step-3] Create one custom SeekBar as below--

    public class DottedSeekBar extends AppCompatSeekBar {
    
        /** Int values which corresponds to dots */
        private int[] mDotsPositions = null;
        /** Drawable for dot */
        private Bitmap mDotBitmap = null;
    
        public DottedSeekBar(final Context context) {
            super(context);
            init(null);
        }
    
        public DottedSeekBar(final Context context, final AttributeSet attrs) {
            super(context, attrs);
            init(attrs);
        }
    
        public DottedSeekBar(final Context context, final AttributeSet attrs, final int defStyle) {
            super(context, attrs, defStyle);
            init(attrs);
        }
    
        /**
         * Initializes Seek bar extended attributes from xml
         *
         * @param attributeSet {@link AttributeSet}
         */
        private void init(final AttributeSet attributeSet) {
            final TypedArray attrsArray = getContext().obtainStyledAttributes(attributeSet, R.styleable.DottedSeekBar, 0, 0);
    
            final int dotsArrayResource = attrsArray.getResourceId(R.styleable.DottedSeekBar_dots_positions, 0);
    
            if (0 != dotsArrayResource) {
                mDotsPositions = getResources().getIntArray(dotsArrayResource);
            }
    
            final int dotDrawableId = attrsArray.getResourceId(R.styleable.DottedSeekBar_dots_drawable, 0);
    
            if (0 != dotDrawableId) {
                mDotBitmap = BitmapFactory.decodeResource(getResources(), dotDrawableId);
            }
        }
    
        /**
         * @param dots to be displayed on this SeekBar
         */
        public void setDots(final int[] dots) {
            mDotsPositions = dots;
            invalidate();
        }
    
        /**
         * @param dotsResource resource id to be used for dots drawing
         */
        public void setDotsDrawable(final int dotsResource)
        {
            mDotBitmap = BitmapFactory.decodeResource(getResources(), dotsResource);
            invalidate();
        }
    
        @Override
        protected synchronized void onDraw(final Canvas canvas) {
            super.onDraw(canvas);
    
            final float width=getMeasuredWidth()-getPaddingLeft()-getPaddingRight();
            final float step=width/(float)(getMax());
    
            if (null != mDotsPositions && 0 != mDotsPositions.length && null != mDotBitmap) {
                // draw dots if we have ones
                for (int position : mDotsPositions) {
                    canvas.drawBitmap(mDotBitmap, position * step, 0, null);
                }
            }
        }
    }
    

    Step-4] Use this custom SeekBar in your activity.xml file as below--

    <com.your_package.DottedSeekBar
                            android:id="@+id/videoProgress"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"/>                    
    

    Step-5] Add below code in "onCreate()" method of your "Activity.java" class--

            DottedSeekBar videoProgress = (DottedSeekBar) findViewById(R.id.videoProgress);
            // Disable SeekBar Thumb Drag. (Optional)
                    videoProgress.setOnTouchListener(new View.OnTouchListener()
                    {
                        @Override
                        public boolean onTouch(View view, MotionEvent motionEvent)
                        {
                            return true;
                        }
                    });
    
    
        // Set custom thumb icon color here (Optional)
    
            videoProgress.getThumb().setColorFilter(getResources().getColor(R.color.cerulean_blue), PorterDuff.Mode.SRC_IN);
    
        // Add below line to avoid unnecessary SeekBar padding. (Optional)
    
            videoProgress.setPadding(0, 0, 0, 0);
    
    // Handler to update video progress time--
    
    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);
                }
            };
    
    Use "videoView.setOnPreparedListener()" method to calculate total video time in seconds
    
    
    
         yourVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
                                                  {
                                                      @Override
                                                      public void onPrepared(MediaPlayer mp)
                                                      {
    
    
                                                          String strTotalDuration = msToTimeConverter(vidView.getDuration());
    
                                                          String[] strTimeArr = strTotalDuration.split(":");
    
                                                          int min = Integer.parseInt(strTimeArr[0]);
                                                          int videoLengthInSec = Integer.parseInt(strTimeArr[1]);
                                                          videoLengthInSec = videoLengthInSec + (min*60);
    
                                                          videoProgress.setProgress(0);
                                                          videoProgress.setMax(videoLengthInSec);
    
                                                          // Start the initial runnable task by posting through the handler
                                                          handler.post(runnableCode);
    
                                                          initVideoMarkers();
                                                      }
                                                  }
    
                    );
    

    Step-6] Copy below required methods in your "Activity.java" class--

    // Method to update time progress

     private void updateCurrentTime()
            {
                if (videoProgress.getProgress() >= 100)
                {
                    handler.removeMessages(0);
                }
                String currentPosition = msToTimeConverter(vidView.getCurrentPosition());
    
                String[] strArr = currentPosition.split(":");
    
                int progress = vidView.getCurrentPosition() * videoLengthInSec / vidView.getDuration();
    
                videoProgress.setProgress(progress);
            }
    
    // Milliseconds to Time converter Method
    
         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)));
            }
    

    // Method to set Marker values

    private void initVideoMarkers()
            {
                // Here I'm adding markers on 10, 15 and 20 Second index
                videoProgress.setDots(new int[] {10, 15, 20});
                videoProgress.setDotsDrawable(R.drawable.video_mark);
           }
    
    0 讨论(0)
  • 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<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"
    }
    
    0 讨论(0)
提交回复
热议问题