53

I've implemented the commonly-pointed-to VerticalSeekBar post here. As it is, the SeekBar operates a little quirky. Here is my slightly adapted onTouchEvent() from that example:

public boolean onTouchEvent(MotionEvent event)
    {
            xPos = event.getX();
            yPos = event.getY();
            oOffset = this.getThumbOffset();
            oProgress = this.getProgress();

            //Code from example - Not working
            //this.setThumbOffset( progress * (this.getBottom()-this.getTop()) );

            this.setProgress((int)(29*yPos/this.getBottom()));
            return true;
    }

I've managed to implement one VerticalSeekBar in which the progress updates as expected and is fully-functional, but the thumb does not follow suit. This is only a graphical glitch, so I'm overlooking it for now. But, it would be nice to have that working. This SeekBar has max = 20.

However, I tried implementing another VerticalSeekBar with max = 1000. Obviously, it uses the same code, so you'd assume the same behavior. I'm only able to achieve a progress of 0~35, even as my finger slides beyond the SeekBar and eventually off the screen. If I just tap near the end of the progress bar (which should be progress ~ 900) it returns a progress of about 35 and the yellow progress bar reflects that value by staying near the top.

My question is: Does anyone have a link to a working vertical SeekBar, or know how to adapt this particular example?

Community
  • 1
  • 1
Snailer
  • 3,603
  • 3
  • 32
  • 45
  • 1
    follow the link : [vertical seek bar](http://code.google.com/p/adamrocker/source/browse/#svn/trunk/VerticalSlidebarExample%253Fstate%253Dclosed) – Vicky Kapadia Mar 30 '11 at 05:56

9 Answers9

150

Here is a working VerticalSeekBar implementation:

package android.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;

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);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled()) {
            return false;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                setProgress(getMax() - (int) (getMax() * event.getY() / getHeight()));
                onSizeChanged(getWidth(), getHeight(), 0, 0);
                break;

            case MotionEvent.ACTION_CANCEL:
                break;
        }
        return true;
    }
}

To implement it, create a new class in your project, choosing the right package:

There, paste the code and save it. Now use it in your XML layout:

<android.widget.VerticalSeekBar
  android:id="@+id/seekBar1"
  android:layout_width="wrap_content"
  android:layout_height="200dp"
  />
slhck
  • 30,965
  • 24
  • 125
  • 174
Paul Tsupikoff
  • 1,579
  • 1
  • 13
  • 15
  • 1
    This is a fantastically simple solution - did you write it? – David Snabel-Caunt Sep 08 '11 at 14:51
  • 1
    This works absolutely correct. The only thing that should be mentioned is that you must be aware of the layout choice, if you choose lets say LinearLayout orientation of that layout really matters of how you will see the bar, thus the place of getMeasuredHeight() and width, will change `setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());` If someone needs the example I said I can provide full sample code. – Nikola Despotoski Sep 14 '11 at 15:44
  • This is great! Does anyone know why this is not in android to start with? – snapfractalpop Apr 09 '12 at 18:32
  • What I should have asked is: does this need to be instantiated in code, or can it be somehow declared in xml? (If the latter, could someone give an example?) – snapfractalpop Apr 09 '12 at 19:24
  • To answer my own comment: it turns out, you can just use it directly in xml via the right click "change widget type.." menu within the xml editor (in eclipse). – snapfractalpop Apr 09 '12 at 22:06
  • 15
    Override `setProgress` to call it from outside the class.`@Override public synchronized void setProgress(int progress){ super.setProgress(progress); onSizeChanged(getWidth(), getHeight(), 0, 0); }` – Xavi Gil Jul 24 '12 at 08:22
  • Please add code wrote by Xavi to answer avoid unxpected behaviour on "setProgress" method. Thank you all of you for that! – Michal Nov 01 '13 at 07:27
  • 1
    If you are using this with paddings you need to change onTouchEVent, otherwise setprogess would behave unexpactedly. Here is the fixed version `switch (event.getAction()) {case MotionEvent.ACTION_DOWN:case MotionEvent.ACTION_MOVE:case MotionEvent.ACTION_UP:setProgress(getMax() - (int) (getMax() *(event.getY() - getPaddingRight()) / (getHeight() - getPaddingRight() - getPaddingLeft()))); onSizeChanged(getWidth(), getHeight(), 0, 0); break;} ` – user65721 Nov 02 '13 at 14:21
  • in the case you use an other package need to add the line `import android.widget.SeekBar;` at head imports – Universe Apr 29 '14 at 10:47
  • to set padding Left you should set paddingTop – AndroidGeek Feb 08 '15 at 18:07
  • This is really good! I was wondering, how do you make it so it starts on the top side of the seekbar? – Galarzaa90 Aug 25 '15 at 19:32
  • 2
    @Galarzaa90 I don't have enough time to test it right now, but I think it would be enough to c.rotate(90); instead of -90, remove c.translate(...), and remove "getMax() - " in onTouchEvent. – Paul Tsupikoff Aug 26 '15 at 20:04
  • 1
    Why do I get the Thumb different from the horizontal SeekBar? – jaiserpe Jan 08 '16 at 13:53
  • @Galarzaa90 `canvas.rotate(90); canvas.translate(0, -getWidth());` – Attacktive Sep 12 '16 at 01:59
  • Thank you, worked like a charm. But note this: case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_UP: int progress = (int) (getMax() * event.getY() / getHeight()); if (progress > getMax()) { progress = getMax(); } else if (progress < 0) { progress = 0; } setProgress(getMax() - progress); onSizeChanged(getWidth(), getHeight(), 0, 0); break; – Hermandroid Jan 31 '17 at 23:45
  • 1
    WHERE IS THUMB? it doesn't appear with you solution – user25 Apr 08 '17 at 20:22
30

The code given in the accepted answer didn't intercept the onStartTrackingTouch and the onStopTrackingTouch events, so I've modified it to have more control over this two events.

Here is my code:

public class VerticalSeekBar extends SeekBar {

private OnSeekBarChangeListener myListener;
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());
}

@Override
public void setOnSeekBarChangeListener(OnSeekBarChangeListener mListener){
    this.myListener = mListener;
}

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

    super.onDraw(c);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (!isEnabled()) {
        return false;
    }

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            if(myListener!=null)
                myListener.onStartTrackingTouch(this);
            break;
        case MotionEvent.ACTION_MOVE:
            setProgress(getMax() - (int) (getMax() * event.getY() / getHeight()));
            onSizeChanged(getWidth(), getHeight(), 0, 0);
            myListener.onProgressChanged(this, getMax() - (int) (getMax() * event.getY() / getHeight()), true);
            break;
        case MotionEvent.ACTION_UP:
            myListener.onStopTrackingTouch(this);
            break;

        case MotionEvent.ACTION_CANCEL:
            break;
    }
    return true;
}
}
Peter O.
  • 28,965
  • 14
  • 72
  • 87
Fatal1ty2787
  • 423
  • 5
  • 8
13

I had problems while using this code with setProgress method. To solve them I suggest overriding setProgress and adding onSizeChanged call to it.

Michał M
  • 141
  • 1
  • 2
  • 7
    Add to the class: @Override public synchronized void setProgress(int progress) { super.setProgress(progress); onSizeChanged(getWidth(), getHeight(), 0, 0); } – FeatureCreep Dec 27 '11 at 21:44
11

I had problem while using this code with setProgress method. To solve them I suggest overriding setProgress and adding onSizeChanged call to it.Added code here ..

   private int x,y,z,w;
   protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(h, w, oldh, oldw);
        this.x=w;
        this.y=h;
        this.z=oldw;
        this.w=oldh;
    }
        @Override
        public synchronized void setProgress(int progress) {

            super.setProgress(progress);

                onSizeChanged(x, y, z, w);

        }

selected hover actions are performed by adding the following code:

1.setPressed(true);setSelected(true);//Add this in ACTION_DOWN

2.setPressed(false);setSelected(false);//Add this in ACTION_UP

And Write code for selected hover options in ur xml.

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"
          android:state_window_focused="true"
          android:drawable="@drawable/thumb_h" />
    <item android:state_selected="true"
          android:state_window_focused="true"
          android:drawable="@drawable/thumb_h" />
    <item android:drawable="@drawable/thumb" />
</selector>

This is working for me...

Ramesh Akula
  • 5,542
  • 4
  • 38
  • 65
9

Thanks to Paul Tsupikoff, Fatal1ty2787 and Ramesh for this excellent code.

Personally, I wanted a vertical slider that is upside-down compared to the given code. In other words, the value increases, rather than decreases, the lower the thumb. Changing four lines seems to have taken care of this.

First, I changed the onDraw() method as originally given by Paul. The rotate() and translate() calls now have these arguments:

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

Then I made two changes to the ACTION_MOVE case in onTouchEvent() as given by Fatal1ty2787. The call to setProgress() now looks like this:

setProgress((int) (getMax() * event.getY() / getHeight()));

Finally, the call to onProgressChanged() looks like this:

myListener.onProgressChanged(this, (int) (getMax() * event.getY() / getHeight()), true);

Now, if only Google shared our interest in this feature....

Adam Mackler
  • 1,818
  • 15
  • 32
9

For API 11 and later, can use seekbar's XML attributes(android:rotation="270") for vertical effect.

<SeekBar
android:id="@+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:rotation="270"/>

For older API level (ex API10),use: https://github.com/AndroSelva/Vertical-SeekBar-Android

AndroidGeek
  • 30,803
  • 14
  • 212
  • 262
  • 2
    This is the most relevant and simple solution to the question. Only problem is that it doesn't seem to be accurately reflected in the Android Studio "Design" view. (The bar remains horizontal in the design view, even though it becomes vertical in the final product) – TheIronKnuckle Mar 03 '16 at 05:22
2

Another idea could be to change the X and Y coordinates of the MotionEvent and pass them to the super-implementation:

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);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (!isEnabled()) {
        return false;
    }
    float x = (getHeight() - event.getY()) * getWidth() / getHeight();
    float y = event.getX();
    MotionEvent verticalEvent = MotionEvent
            .obtain(event.getDownTime(), event.getEventTime(), event.getAction(), x, y,
                    event.getPressure(), event.getSize(), event.getMetaState(),
                    event.getYPrecision(), event.getXPrecision(), event.getDeviceId(),
                    event.getEdgeFlags());
    return super.onTouchEvent(verticalEvent);
}

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

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

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

In this case it is not necessary to call the setProgress(int) method and therefore you could use the boolean-flag "fromUser" in OnSeekBarChangeListener.onProgressChanged() to determine if the seeking was produced by an user interaction.

Christopher
  • 7,900
  • 4
  • 38
  • 63
  • I love your solution but it seems that it doesn't work correctly. You need to override setProgress and call onSizeChanged there in order to make it work. – user1991679 Aug 31 '16 at 12:10
1

Target platforms

from Android 2.3.x (Gingerbread) to Android 7.x (Nougat)

Getting started

This library is published on jCenter. Just add these lines to build.gradle.

dependencies {
    compile 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:0.7.2'
}

Usage

Layout XML

<!-- This library requires pair of the VerticalSeekBar and VerticalSeekBarWrapper classes -->
<com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper
    android:layout_width="wrap_content"
    android:layout_height="150dp">
    <com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBar
        android:id="@+id/mySeekBar"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:max="100"
        android:progress="0"
        android:splitTrack="false"
        app:seekBarRotation="CW90" /> <!-- Rotation: CW90 or CW270 -->
</com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper>

NOTE: android:splitTrack="false" is required for Android N+.

Java code

public class TestVerticalSeekbar extends AppCompatActivity {
    private SeekBar volumeControl = null;


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

        volumeControl = (SeekBar) findViewById(R.id.mySeekBar);

        volumeControl.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            int progressChanged = 0;

            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                progressChanged = progress;
            }

            public void onStartTrackingTouch(SeekBar seekBar) {
                // TODO Auto-generated method stub
            }

            public void onStopTrackingTouch(SeekBar seekBar) {
                Toast.makeText(getApplicationContext(), "seek bar progress:" + progressChanged,
                        Toast.LENGTH_SHORT).show();
            }
        });
    }

}
Phadadev
  • 328
  • 2
  • 8
0

Based on Paul Tsupikoff's answer, here is the AppCompatVerticalSeekBar:

package com.my.apppackage;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;

import androidx.appcompat.widget.AppCompatSeekBar;

public class AppCompatVerticalSeekBar extends AppCompatSeekBar {
    public AppCompatVerticalSeekBar(Context context) {
        super(context);
    }

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

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

    @Override
    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());
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        canvas.rotate(-90);
        canvas.translate(-getHeight(), 0);
        super.onDraw(canvas);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled()) {
            return false;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP: {
                setProgress(getMax() - (int) (getMax() * event.getY() / getHeight()));
                onSizeChanged(getWidth(), getHeight(), 0, 0);
                break;
            }
            case MotionEvent.ACTION_CANCEL: {
                break;
            }
        }
        return true;
    }
}

Use it in your layout file:

<com.my.apppackage.AppCompatVerticalSeekBar
    android:id="@+id/verticalSeekBar1"
    android:layout_width="wrap_content"
    android:layout_height="200dp" />
Pierre
  • 6,347
  • 4
  • 48
  • 67