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.
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.
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.
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 :)
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.
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:
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.
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 !!!
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)
Works great! Thank you.
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)
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.
I’m afraid you’d have to reimplement vital parts of ProgressBar, AbsSeekBar and SeekBar …
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?
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!
Hi again, forget it! I solve it!
Thanks! :)
Hi Julius,
How did you manage to solve this problem.. I’m having exactly the same thing.
Thanks!
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!
Try:
c.rotate(90);
c.translate(0,-getWidth());
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
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);
}
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);
}
Can someone give me an example for the OnSeekBarChangeListener?
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) {
}
});
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?
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!
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.
it work pefect but you should check onChangeListener isn’t equal null.
Thnak you very much for this :)