How can I get a working vertical SeekBar in Android?
Asked Answered
P

10

54

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?

Plexus answered 3/2, 2011 at 21:56 Comment(1)
follow the link : vertical seek barGregoor
E
155

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"
  />
Exurbia answered 7/9, 2011 at 23:16 Comment(16)
This is a fantastically simple solution - did you write it?Less
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.Minhminho
This is great! Does anyone know why this is not in android to start with?Francklin
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?)Francklin
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).Francklin
Override setProgress to call it from outside the class.@Override public synchronized void setProgress(int progress){ super.setProgress(progress); onSizeChanged(getWidth(), getHeight(), 0, 0); }Severus
Please add code wrote by Xavi to answer avoid unxpected behaviour on "setProgress" method. Thank you all of you for that!Trengganu
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;} Baillieu
in the case you use an other package need to add the line import android.widget.SeekBar; at head importsQuiteria
to set padding Left you should set paddingTopKieserite
This is really good! I was wondering, how do you make it so it starts on the top side of the seekbar?Azal
@Azal 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.Exurbia
Why do I get the Thumb different from the horizontal SeekBar?Iconoduly
@Azal canvas.rotate(90); canvas.translate(0, -getWidth());Kings
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;Shannan
WHERE IS THUMB? it doesn't appear with you solutionSundown
G
31

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;
}
}
Guy answered 31/10, 2011 at 18:5 Comment(6)
I have small doubt, How to change hover images of seekbar thumb from above code.Actually i tried this but i am not succeeded. But i observed in my app that when seekbar gets focus the code is working perfectly.Pozzy
@Guy thnx very much :)Blase
You should also call setProgress(getMax() - (int) (getMax() * event.getY() / getHeight())); in MotionEvent.ACTION_DOWN: and MotionEvent.ACTION_UP:Blase
you have intercepted the behavior using a listener,can't we just override onStartTrackingTouch and the onStopTrackingTouch events??Judgeship
That is brilliant !!Hairsplitting
Thanks. I was stucked with the onStartTouch and onStopTouch because it wasn't working until now.Rosellaroselle
M
13

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

Mattiematting answered 29/10, 2011 at 20:36 Comment(1)
Add to the class: @Override public synchronized void setProgress(int progress) { super.setProgress(progress); onSizeChanged(getWidth(), getHeight(), 0, 0); }Scutcheon
P
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...

Pozzy answered 2/11, 2011 at 10:46 Comment(0)
X
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....

Xuanxunit answered 3/7, 2013 at 0:14 Comment(0)
K
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

Kieserite answered 8/2, 2015 at 18:18 Comment(1)
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)Hosanna
O
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.

Obeah answered 17/12, 2013 at 11:52 Comment(1)
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.Petrie
Y
2

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" />
Youth answered 4/11, 2019 at 10:24 Comment(0)
L
0

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

}
Lorianne answered 7/9, 2017 at 7:53 Comment(0)
N
0

Instead of SeekBar, I was looking for Vertical Slider. Here is my implementation for vertical Slider in Kotlin.

import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.MotionEvent
import com.google.android.material.slider.Slider

class MySlider : Slider {

    constructor(context: Context) : super(context)
    constructor (context: Context, attrs: AttributeSet?) : super(context, attrs) 

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(h, w, oldh, oldw)
    }

    @Synchronized
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(heightMeasureSpec, widthMeasureSpec)
        setMeasuredDimension(measuredHeight, measuredWidth)
    }

    override fun setValue(value: Float) {
        super.setValue(value)
        onSizeChanged(width, height, 0, 0)
    }

    override fun onDraw(canvas: Canvas) {
        canvas.rotate(-90f)
        canvas.translate(-height * 1f, 0f)
        super.onDraw(canvas)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (!isEnabled) {
            return false
        }
        when (event.action) {
            MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE, MotionEvent.ACTION_UP -> {
                setValue((valueTo - (valueTo * event.y / height)).coerceIn(valueFrom, valueTo))
                onSizeChanged(width, height, 0, 0)
            }

            MotionEvent.ACTION_CANCEL -> {}
        }
        return true
    }
}
Nut answered 4/3 at 7:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.