Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
SeekBar showing its progress as text on thumb

TextThumbSeekBar

Define a custom class extending SeekBar to show the progress text on thumb of SeekBar.

public class TextThumbSeekBar extends SeekBar {

    private int mThumbSize;
    private TextPaint mTextPaint;

    public TextThumbSeekBar(Context context) {
        this(context, null);
    }

    public TextThumbSeekBar(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.seekBarStyle);
    }

    public TextThumbSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        
        mThumbSize = getResources().getDimensionPixelSize(R.dimen.thumb_size);
        
        mTextPaint = new TextPaint();
        mTextPaint.setColor(Color.WHITE);
        mTextPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.thumb_text_size));
        mTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        String progressText = String.valueOf(getProgress());
        Rect bounds = new Rect();
        mTextPaint.getTextBounds(progressText, 0, progressText.length(), bounds);

        int leftPadding = getPaddingLeft() - getThumbOffset();
        int rightPadding = getPaddingRight() - getThumbOffset();
        int width = getWidth() - leftPadding - rightPadding;
        float progressRatio = (float) getProgress() / getMax();
        float thumbOffset = mThumbSize * (.5f - progressRatio);
        float thumbX = progressRatio * width + leftPadding + thumbOffset;
        float thumbY = getHeight() / 2f + bounds.height() / 2f;
        canvas.drawText(progressText, thumbX, thumbY, mTextPaint);
    }
}

smaple.xml

Use TextThumbSeekBar in your layout xml as following.

<com.yqritc.sample.TextThumbSeekBar
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:max="10"
  android:progress="5"
  android:thumb="@drawable/shape_seek_bar_text_thumb"
  android:thumbOffset="4dp"/>

shape_seek_bar_text_thumb.xml

Define custom thumb drawable as you need like the following.

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/primary_color"/>
    <size
        android:width="@dimen/thumb_size"
        android:height="@dimen/thumb_size"/>
</shape>

Note

If you use image drawable as the thumb instead of xml shape drawable, set the following attribute if the background of your drawable is transparent.

android:splitTrack="false"

DONE.

@hadifar

This comment has been minimized.

Copy link

@hadifar hadifar commented Jul 8, 2016

Avoid object allocation in onDrawer/Layout warning...

@sench93

This comment has been minimized.

Copy link

@sench93 sench93 commented Nov 28, 2016

how much is thumb size?

@UsmanGhauri

This comment has been minimized.

Copy link

@UsmanGhauri UsmanGhauri commented Jan 17, 2017

i want to use rounded drawable for thumb and im not being able to do it even through xml property or from code setThumb .. any help? it shows transparent background with text on it.
screenshot_1

@Sonu999

This comment has been minimized.

Copy link

@Sonu999 Sonu999 commented Jun 16, 2017

What should be thumb_size and thumb_text_size??

@kraunak

This comment has been minimized.

Copy link

@kraunak kraunak commented Aug 10, 2018

how to set progress value as a float?

@mpierucci

This comment has been minimized.

Copy link

@mpierucci mpierucci commented Dec 14, 2018

Why not use thum bounds to calculare x position to draw text?

@oxied

This comment has been minimized.

Copy link

@oxied oxied commented Sep 10, 2019

With 2 TextPaints:

package com.app.android.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.TextPaint;
import android.util.AttributeSet;

import androidx.appcompat.widget.AppCompatSeekBar;
import androidx.core.content.res.ResourcesCompat;

import com.app.android.R;

public class TextThumbSeekBar extends AppCompatSeekBar {

    private int mThumbSize;
    private TextPaint mTextPaintDollar;
    private TextPaint mTextPaintNumber;

    public TextThumbSeekBar(Context context) {
        this(context, null);
    }

    public TextThumbSeekBar(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.seekBarStyle);
    }

    public TextThumbSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mThumbSize = getResources().getDimensionPixelSize(R.dimen._76udp);

        mTextPaintDollar = new TextPaint();
        mTextPaintDollar.setColor(Color.WHITE);
        mTextPaintDollar.setTextSize(getResources().getDimensionPixelSize(R.dimen._24usp));
        mTextPaintDollar.setTypeface(ResourcesCompat.getFont(context, R.font.proxima_nova_regular));
        mTextPaintDollar.setTextAlign(Paint.Align.CENTER);

        mTextPaintNumber = new TextPaint();
        mTextPaintNumber.setColor(Color.WHITE);
        mTextPaintNumber.setTextSize(getResources().getDimensionPixelSize(R.dimen._24usp));
        mTextPaintNumber.setTypeface(ResourcesCompat.getFont(context, R.font.proxima_nova_bold));
        mTextPaintNumber.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        String dollarText = "$";
        Rect boundsDollar = new Rect();
        mTextPaintDollar.getTextBounds(dollarText, 0, dollarText.length(), boundsDollar);

        String progressText = String.valueOf(getProgress());
        Rect boundsNumber = new Rect();
        mTextPaintNumber.getTextBounds(progressText, 0, progressText.length(), boundsNumber);

        int leftPadding = getPaddingLeft() - getThumbOffset();
        int rightPadding = getPaddingRight() - getThumbOffset();
        int width = getWidth() - leftPadding - rightPadding;
        float progressRatio = (float) getProgress() / getMax();
        float thumbOffset = mThumbSize * (.5f - progressRatio);
        float thumbX = progressRatio * width + leftPadding + thumbOffset;
        float thumbXDollar = thumbX - boundsNumber.width() / 2f;
        float thumbXNumber = thumbX + boundsDollar.width() / 2f;
        float thumbY = getHeight() / 2f + boundsNumber.height() / 2f;

        canvas.drawText(dollarText, thumbXDollar, thumbY, mTextPaintDollar);
        canvas.drawText(progressText, thumbXNumber, thumbY, mTextPaintNumber);
    }

}

The result is regular and bold font types (for $ and for 585):
Screenshot 2019-09-10 20 38 05

Also used: https://github.com/oxied/android-universal-dp-size

@senpl

This comment has been minimized.

Copy link

@senpl senpl commented Feb 14, 2020

Also to res\values U need to add dimens.xml if not present:
<resources> <dimen name="thumb_size">16dp</dimen> </resources>

@ZakAnun

This comment has been minimized.

Copy link

@ZakAnun ZakAnun commented Apr 29, 2020

i want to use rounded drawable for thumb and im not being able to do it even through xml property or from code setThumb .. any help? it shows transparent background with text on it.
screenshot_1

Have you make it? I wonder how to draw background under text

@ZakAnun

This comment has been minimized.

Copy link

@ZakAnun ZakAnun commented Apr 30, 2020

I made it with a TextView in onProgressChanged Callback, Haha.. Like this

audioSeekBarIndicate.text = string(R.string.audio_time_result).format(
                    ParseUtils.progressToMSS(progress),
                    ParseUtils.progressToMSS(seekBar.max))
val indicateWidth = audioSeekBarIndicate.width
val thumb = seekBar.thumb
val bounds = thumb.bounds
val seekBarWidth = seekBar.width
val indicatorX = thumb.intrinsicWidth / 2 + bounds.left + seekBar.x
audioSeekBarIndicate.x = when {
        indicatorX < seekBarWidth -> {
            indicatorX
        }
        indicatorX < 0 -> {
            0f
        }
        else -> {
            seekBarWidth.toFloat()
        }
}
if (audioSeekBarIndicate.x > 0 &&
        audioSeekBarIndicate.visibility == View.GONE) {
    audioSeekBarIndicate.visibility = View.VISIBLE
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment