Working Vertical SeekBar for Android

20 Apr

More than likely you’ve come here wanting source code for a fully working Vertical SeekBar. I’ve made this quick tutorial due to frustration over the lack of working solutions found on Google and StackOverflow – They mostly worked, but there was always something wrong with each implementation; the progress didn’t update correctly, the bar didn’t draw correctly, the thumb didn’t unhighlight, or maybe it just didn’t look right. This solution seems to solve all of this.
Vertical SeekBar

Here is the important parts of the VerticalSeekBar class. I merged a few solutions I found to make this. The goal was to make VerticalSeekBar behave exactly like a normal horizontal SeekBar.

public class VerticalSeekBar extends SeekBar {

	public VerticalSeekBar(Context context) {
		super(context);
	}

	public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	public VerticalSeekBar(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(h, w, oldh, oldw);
	}

	@Override
	protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(heightMeasureSpec, widthMeasureSpec);
		setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
	}

	protected void onDraw(Canvas c) {
		c.rotate(-90);
		c.translate(-getHeight(), 0);

		super.onDraw(c);
	}

	private OnSeekBarChangeListener onChangeListener;
	@Override
	public void setOnSeekBarChangeListener(OnSeekBarChangeListener onChangeListener){
		this.onChangeListener = onChangeListener;
	}

	private int lastProgress = 0;
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (!isEnabled()) {
			return false;
		}

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			onChangeListener.onStartTrackingTouch(this);
			setPressed(true);
			setSelected(true);
			break;
		case MotionEvent.ACTION_MOVE:
			super.onTouchEvent(event);
			int progress = getMax() - (int) (getMax() * event.getY() / getHeight());
			
			// Ensure progress stays within boundaries
			if(progress < 0) {progress = 0;}
			if(progress > getMax()) {progress = getMax();}
			setProgress(progress);	// Draw progress
			if(progress != lastProgress) {
				// Only enact listener if the progress has actually changed
				lastProgress = progress;
				onChangeListener.onProgressChanged(this, progress, true);
			}
			
			onSizeChanged(getWidth(), getHeight() , 0, 0);
			setPressed(true);
			setSelected(true);
			break;
		case MotionEvent.ACTION_UP:
			onChangeListener.onStopTrackingTouch(this);
			setPressed(false);
			setSelected(false);
			break;
		case MotionEvent.ACTION_CANCEL:
			super.onTouchEvent(event);
			setPressed(false);
			setSelected(false);
			break;
		}
		return true;
	}

	public synchronized void setProgressAndThumb(int progress) {
		setProgress(progress);
		onSizeChanged(getWidth(), getHeight() , 0, 0);
		if(progress != lastProgress) {
			// Only enact listener if the progress has actually changed
			lastProgress = progress;
			onChangeListener.onProgressChanged(this, progress, true);
		}
	}

	public synchronized void setMaximum(int maximum) {
		setMax(maximum);
	}

	public synchronized int getMaximum() {
		return getMax();
	}
}

I then inserted the VerticalSeekBar inside a LinearLayout like so:

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <com.safetyculture.jsadroidtablet.VerticalSeekBar
        android:id="@+id/calculatorVerticalSeekBar"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_gravity="bottom"
        android:max="4"
        android:progress="2" />

</LinearLayout>

NOTE: In this implementation you must set an OnSeekBarChangeListener for the VerticalSeekBar before using it in the app, otherwise interacting with the VerticalSeekBar will produce NullPointerException.

NOTE2: If you wish to change the progress manually/programmatically rather than through the UI Motion Events, you will need to call setProgressAndThumb(progress), otherwise the thumb doesn’t update correctly.

About these ads

25 Responses to “Working Vertical SeekBar for Android”

  1. Nancy April 28, 2012 at 1:14 AM #

    Where do you put the java code?Everyone is providing a solution but not really saying where to place the code or even how to used it.

    • techn1x April 28, 2012 at 1:24 AM #

      The “VerticalSeekBar” class code, just throw that into it’s own class file, like I have here: http://d.pr/i/drMq

      Using it in XML is done by using the XML code I provided, except change “com.safetyculture.jsadroidtablet.VerticalSeekBar” to whatever your package name is where the class is located (eg “com.your.package.here.VerticalSeekBar”)

      If you then wanted to access the layout element in the code, it can be done the same way normal layout elements are accessed:

      VerticalSeekBar variableName = (VerticalSeekBar)layoutName.findViewById(R.id.verticalSeekBarId);

      Replace variableName with a name for it.
      Replace layoutName with the layout you’re using.
      Replace vericalSeekBarId with the id you gave it in the XML.

      I hope that answers your question :)

  2. Ferlle May 2, 2012 at 4:13 PM #

    Hi, i’ve implemented your vertical seekbar and works great, but i have experienced an issue when using buttons to change the progress value, i have two buttons:

    top button sets current progress += 1
    bottom button set to current progress value -= 1

    and when are pressed the thumb indicator gets always at bottom of the layout contanier of the seekbar, i think is cause i must adjust the thumb “y” position but i cant find how do it, thanks for your help.

    • techn1x May 2, 2012 at 5:51 PM #

      I see what you’re trying to do.

      I think the best way will be to create a simulated touch Event and pass it to the dispatchTouchEvent(MotionEvent event) method of the VerticalSeekBar, when you want to change the progress manually. ( This way you just send a MotionEvent that is of the type MotionEvent.ACTION_MOVE )

      So, inside your top button’s OnClick listener code, make it call verticalSeekBar.dispatchTouchEvent(event) with the following event:

      MotionEvent event = MotionEvent.obtain( SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE, x, y, 0);
      

      I’m not quite sure what you’ll need to set ‘x’ and ‘y’ to, hopefully you’ll get it with some trial and error. Also, perhaps the methods verticalSeekBar.getScrollX() and verticalSeekBar.getScrollY() will be of use.

      Hope I helped.

      • Ferlle May 3, 2012 at 6:15 AM #

        Thanks, i solved just calling the setProgressAndThumb(progress) method inside your VerticalSeekBar class, if someone else gets in the same situation here is how i solved it:

        // Creating the vertical Seekbar
        vs = (VerticalSeekBar) findViewById(R.id.vs);

        // Snippet to increase progress value manually from a button
        ….
        if ( !(vs.getProgress() + 1 > vs.getMax()) ){
        int newProgresValue = vs.getProgress() + 1;
        vs.setProgressAndThumb( newProgresValue );
        }
        ….

        Thanks again :D !!!

      • techn1x May 3, 2012 at 1:11 PM #

        Ah! I completely forgot about the setProgressAndThumb method. You may also want to set the lastProgress class variable inside the method (I’ve edited the code to include it)

  3. Terrence Streeter June 11, 2012 at 11:20 AM #

    Works great! Thank you.

  4. lol July 4, 2012 at 8:49 PM #

    It does not correctly handle padding.
    You have to set paddingLeft to actually get paddingBottom.
    Besides that, the padding isn’t being taken into account when calculating the position of the slider (i.e. the slider position does not correspond to the touch position)

    • techn1x July 5, 2012 at 12:39 PM #

      How would you suggest going about this?
      The slider still seems to work for the most/important part, but I think I know what you mean.

      • lol July 6, 2012 at 12:02 AM #

        I’m afraid you’d have to reimplement vital parts of ProgressBar, AbsSeekBar and SeekBar …

  5. KingLawnGnome October 14, 2012 at 11:26 AM #

    Hey! I’ve implemented this VerticalSeekBar, but I think I’ve uncovered a bug! I have three of these VerticalSeekBars in a horizontal LinearLayout, each with a layout_weight of 1. When the VerticalSeekBars are drawn, for some reason the thumb appears way off to the left of the bar it is associated with. Here is a screenshot showing the error: http://imgur.com/ecRFi.

    I think this has something to do with a difference of width between when the view is measured versus when it is drawn, but I’m way out of my league on this. By any chance can you see where the problem comes from?

  6. Julius February 12, 2013 at 4:37 AM #

    Hi!
    I am newie to Android and I want to use your seekbar. But I am having problems when I am trying to use the onStartTrackingTouch and onStopTrackingTouch methids. It seems that nothing happens when I click and I release the seebar.
    Thanks!

  7. Julius February 12, 2013 at 6:44 AM #

    Hi again, forget it! I solve it!
    Thanks! :)

    • Richard May 8, 2013 at 3:04 AM #

      Hi Julius,
      How did you manage to solve this problem.. I’m having exactly the same thing.

      Thanks!

  8. TNg March 20, 2013 at 11:13 AM #

    hi,
    when I use your code in Android, I got the correct vertical seek bar which starts from bottom (progress 0 ) to top (progress max). I actually need an inversely vertical seek bar which starts from top (progress 0) to bottom. I tried to use
    c.rotate(90);
    c.translate(0,-getHeight());

    but I got the thumb and the progess bar separately, not nicely together like with your code.
    Could you show me what I did wrong here? Thanks!

    • Shai March 31, 2013 at 3:03 AM #

      Try:

      c.rotate(90);
      c.translate(0,-getWidth());

  9. Alan May 7, 2013 at 2:07 AM #

    Thanks a lot for the code, it was very helpful.
    But i was having a problem when using more than one SeekBar at a time and using the method setProgressAndThumb. One SeekBar was always OK, but the others the thumb position wouldn’t match the progress position. So i put everything of the setProgressAndThumb method inside a
    this.post(new Runnable() {
    public void run() {
    }
    });

    After that it worked fine. I think that happened because i was using this method of the SeekBars at the same time. Just trying to contribute with your work and make it better to everyone

  10. SoundsaFoolMusic June 29, 2013 at 5:20 AM #

    Great post and I have made some simple changes. Might be useful for others. Maybe not…..

    For me I like to click on a slider and it takes me straight to that point as well as using movement as per original post while still invoking the handler to pick up changes (MIDI for me..).

    private int lastProgress = 0;
    private int progress = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    if (!isEnabled()) {
    return false;
    }

    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
    onChangeListener.onStartTrackingTouch(this);

    case MotionEvent.ACTION_MOVE:
    super.onTouchEvent(event);
    progress = getMax() – (int) (getMax() * event.getY() / getHeight());

    // Ensure progress stays within boundaries
    if(progress getMax()) {progress = getMax();}
    setProgress(progress); // Draw progress
    if(progress != lastProgress) {
    // Only enact listener if the progress has actually changed
    lastProgress = progress;
    onChangeListener.onProgressChanged(this, progress, true);
    }

    onSizeChanged(getWidth(), getHeight() , 0, 0);
    setPressed(true);
    setSelected(true);
    break;
    case MotionEvent.ACTION_UP:
    onChangeListener.onStopTrackingTouch(this);
    setPressed(false);
    setSelected(false);
    break;
    case MotionEvent.ACTION_CANCEL:
    super.onTouchEvent(event);
    setPressed(false);
    setSelected(false);
    break;
    }
    return true;
    }

    Second change:

    in class i have some attributes: that i use to draw lines on the slider: The offset is for the starting position of the first gridline and gLines is the number of lines I want with distance between each based on the size of the widget.

    private int vHeight;
    private int vWidth;
    private final int gLines = 8;
    private final int gOffset = 10;
    private Paint paint = null;;

    Update the height etc from onMeasure()

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(heightMeasureSpec, widthMeasureSpec);
    setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
    vHeight= getMeasuredHeight();
    vWidth= getMeasuredWidth();
    }

    then onDraw() I can draw gridlines::

    protected void onDraw(Canvas c) {
    // This takes the standard seekbar widget and rotates it anti clockwise by 90 degrees
    // Making it look like a hardware slider
    c.rotate(-90);
    c.translate(-getHeight(), 0);

    // trying to keep this efficient by reusing paint
    if (paint == null)
    {
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(Color.BLACK);
    paint.setStrokeWidth(1);
    }
    // needs to produce the grid before rendering the rest via calling the super.onDraw

    // Remember this was on its side and now through 90%
    // xy xy paint in reality x=y and y=x
    if (gLines > 0 ){
    Integer vPos = gOffset;
    Integer vInterval = (vHeight – vPos) / (gLines + 1);
    vPos = vPos + vInterval;
    for (int i = 0; i 0 */

    super.onDraw(c);
    }

  11. SoundsaFoolMusic June 29, 2013 at 5:26 AM #

    The ondraw method has not rendered properly in this website..

    protected void onDraw(Canvas c) {
    // This takes the standard seekbar widget and rotates it anti clockwise by 90 degrees
    // Making it look like a hardware slider
    c.rotate(-90);
    c.translate(-getHeight(), 0);

    // trying to keep this efficient by reusing paint
    if (paint == null)
    {
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(Color.BLACK);
    paint.setStrokeWidth(1);
    }
    // needs to produce the grid before rendering the rest via calling the super.onDraw

    // Remember this was on its side and now through 90%
    // xy xy paint in reality x=y and y=x
    if (gLines > 0 ){
    Integer vPos = gOffset;
    Integer vInterval = (vHeight – vPos) / (gLines + 1);
    vPos = vPos + vInterval;
    for (int i = 0; i 0 */

    super.onDraw(c);
    }

  12. Creepwood July 8, 2013 at 7:32 AM #

    Can someone give me an example for the OnSeekBarChangeListener?

    • sryan April 17, 2014 at 1:18 AM #

      I put mine in the onCreateView method of the View where my VerticalSeekBar was living.

      VerticalSeekBar vs = (VerticalSeekBar)v.findViewById(R.id.speedVerticalSeekBar);
      vs.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
      @Override
      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

      }

      @Override
      public void onStartTrackingTouch(SeekBar seekBar) {

      }

      @Override
      public void onStopTrackingTouch(SeekBar seekBar) {

      }
      });

  13. sryan April 17, 2014 at 1:22 AM #

    I’m using your VerticalSeekBar and it works great! No problems. However I tried something a little different and now I do have an issue that I hope you might have some advice for. I have two radiobuttons and each one will change the background of the verticalseekbar. I do this using the setBackgroundResource (each radiobutton sets a different style). My problem is that whenever I switch styles the thumb ends up at the bottom of the verticalseekbar. The progress is still correct (in the middle), but the thumb no longer matches it. Any ideas?

    • techn1x April 25, 2014 at 2:15 PM #

      Hi there, I haven’t worked with vertical seek bars in over 2 years now and my current project doesn’t need them either so I’m not really able to help you, sorry :)

      If you find a solution feel free to post it here for others to see!

      • sryan April 26, 2014 at 2:39 AM #

        Thanks. I finally got it figured out after about a week of trial and error. Turns out I just needed to put the code in a different spot.

  14. Phan van binh July 18, 2014 at 7:40 PM #

    it work pefect but you should check onChangeListener isn’t equal null.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: