88

Can a SeekBar be vertical? I am not very good at UI design, so how can I make the SeekBar more beautiful, please give me some templates and examples.

Zain
  • 37,492
  • 7
  • 60
  • 84
Chris
  • 6,431
  • 10
  • 44
  • 59
  • 2
    You have to roll your own implementation. An unfinished version is linked [here](http://stackoverflow.com/questions/631238/modifying-the-android-seekbar-widget-to-operate-vertically) and another [here](http://groups.google.com/group/android-developers/browse_thread/thread/bec05b0368c20e03) – Pentium10 Jul 27 '10 at 11:25
  • I recently needed to implement it. Check [this](https://github.com/chanjungkim/VerticalSeekbar) out. – c-an Jan 27 '22 at 11:25

19 Answers19

80
  1. 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"/>
    
  2. For older API level (ex API10), only use Selva's answer:
    https://github.com/AndroSelva/Vertical-SeekBar-Android

Saturn
  • 849
  • 6
  • 3
  • please provide link to Selva's answer. – Nikhil Agrawal Apr 25 '13 at 08:56
  • @Nikhil,Selva's answer is the one accepted by asker above. I think Selva own implement is simple and good ! – Saturn Apr 26 '13 at 06:50
  • I thinked of a an answer from an different post. – Nikhil Agrawal Apr 26 '13 at 06:53
  • 25
    Solution 1 - rotated SeekBar doesn't work properly. Thumb is drawn away, size and position can't be properly set (at least inside of RelativeLayout). – Pointer Null Mar 15 '14 at 11:47
  • 2
    It does work, however, you have to keep in mind that dimentions gets swapped, so what used to be width becomes height and so on. So when you increase the height you are actually stretching it that is why the thumb is drawn away, and that happens even with regular seekbar if you stretched the width. Rotation is annoying, so I think it it easier if you place the seekbar inside a layout and size it correctly then rotate the layout and resize it to fit your main layout. – Fahad Alduraibi Nov 25 '14 at 15:33
  • 2
    It is very annoying: you set the actual height of the SeekBar with the width attribute, but in the remainder of the layout this is still the width of the SeekBar element. If you then give the width attribute of the outer element a smaller value (since you only need a small width for a vertical element), the vertical line of the SeekBar is shortened corresponding to that width. This can be solved by introducing an additional wrapper that still has a large width attribute within the outer container with the small width attribute. But then there is indeed still the thumb offset. I gave up :-p – Matthias Jan 15 '16 at 22:56
  • This is a very problematic hack, but I've had success setting the SeekBar's width and height to the desired height, and then setting the left and right margins as negative values to compensate for the excess width in my layout. – Kadinski Feb 17 '17 at 00:48
  • I'm so smart I just can say about rotation 270 without thinking about size and thumb – user25 Apr 08 '17 at 20:11
  • 5
    Ah, I upvoted you too fast. This hardly works. The slider internally uses the width/height before rotation, so everything is awkwardly positioned and sized. – b005t3r Feb 16 '18 at 10:38
  • 1
    This won't work since the slider uses the width for determining the length of the bar! – Yashovardhan99 Jan 08 '19 at 18:33
55

Here is a very good implementation of vertical seekbar. Have a look.

http://560b.sakura.ne.jp/android/VerticalSlidebarExample.zip

And Here is my own implementation for Vertical and Inverted Seekbar based on this

https://github.com/AndroSelva/Vertical-SeekBar-Android

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:
            int i=0;
            i=getMax() - (int) ((getMax()-getMin()) * event.getY() / getHeight());
            setProgress(i);
            Log.i("Progress",getProgress()+"");
            onSizeChanged(getWidth(), getHeight(), 0, 0);
            break;

        case MotionEvent.ACTION_CANCEL:
            break;
    }
    return true;
}
Jithin
  • 77
  • 5
Andro Selva
  • 53,910
  • 52
  • 193
  • 240
  • 2
    hey there. I've tried the code you posted as .zip and it works great.. except for one issue: the SeekBar still draws its progress from left to right and not from bottom to top. Haven't found where it's coded. Do you have any suggestions for me? thx – Droidman Apr 22 '13 at 22:08
  • 2
    hi! Love your verticalScrollbar Inverted, I was trying to create one but had a lot of difficulty finding the right rotation and translation. There's one small bug I found anyways: If MAX is not 100, the progress is not calculated correctly. To fix it you have to change (100-i) to (getMax()-i). This way it works perfect for any range. – rupps Apr 22 '14 at 15:40
  • apparently, i can be negative and larger than max as well here, so I changed it into `Math.max(0, Math.min(getMax(), i)` – Matthias Jan 16 '16 at 12:18
  • 1
    @AndroSelva seekbar.setprogress(position) doesnt move vertical seekbar thumb to desired position. ....why so ? – Animesh Mangla Jun 11 '16 at 17:59
  • THUMB DOESN'T WORK – user25 Apr 08 '17 at 19:56
  • this VerticalScrollBar is drawn incorrectly when put into LinearLayout with weight - but it could be solved by enclosing it with a FrameLayout – Piotr Śmietana Nov 22 '17 at 13:36
29

Working example

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
   public synchronized void setProgress(int progress)  // it is necessary for calling setProgress on click of a button
   {
    super.setProgress(progress);
    onSizeChanged(getWidth(), getHeight(), 0, 0); 
   }
    @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;
    }
}

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"
  />

Make sure to create a package android.widget and create VerticalSeekBar.java under this package

Zar E Ahmer
  • 33,936
  • 20
  • 234
  • 300
15

Try:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
xmlns:tools="http://schemas.android.com/tools" 
android:layout_width="match_parent" 
android:layout_height="match_parent" > 

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

</RelativeLayout> 
Rob Hruska
  • 118,520
  • 32
  • 167
  • 192
Balaji Gunasekar
  • 1,421
  • 1
  • 12
  • 6
  • @Mahmoud By adding android:rotation="270" – Ali Alnoaimi Nov 27 '12 at 13:38
  • 9
    Setting rotation rotates the slider but not the layout. layout_width still controls the length of the slider. As such the layouts still think it is width not height. This makes it fun to correctly insert into a design. I still have not figured out how to put a rotated seek bar on a GridLayout. – JT. Jul 10 '13 at 19:17
11

I used Selva's solution but had two kinds of issues:

  • OnSeekbarChangeListener did not work properly
  • Setting progress programmatically did not work properly.

I fixed these two issues. You can find the solution (within my own project package) at

https://github.com/jeisfeld/Augendiagnose/blob/master/AugendiagnoseIdea/augendiagnoseLib/src/main/java/de/jeisfeld/augendiagnoselib/components/VerticalSeekBar.java

Jörg Eisfeld
  • 1,299
  • 13
  • 7
  • Works fine, thank you. You should also add a null check for the mOnSeekBarChangeListener variable. – jekatt Nov 14 '15 at 13:26
  • @JörgEisfeld setting thumb and progressDrawab le failed for me – noobEinstien May 03 '18 at 12:17
  • @JörgEisfeld, Do you know how I can support bidirectional languages like `Arabic` as this `Vertical SeekBar` became upside down and the slider gives false progress direction? – blueware Oct 18 '18 at 14:31
  • @blueware: Play with the lines `c.rotate(-90);` and `c.translate(-getHeight(),0);` - the second line flips it vertically. Try to rotated it +90 or 270 instead of -90 etc. and get ride of the flipping translation. – hippietrail Aug 27 '23 at 10:47
10

We made a vertical SeekBar by using android:rotation="270":

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/camera_sv_preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <LinearLayout
        android:id="@+id/camera_lv_expose"  
        android:layout_width="32dp"
        android:layout_height="200dp"
        android:layout_centerVertical="true"
        android:layout_alignParentRight="true"
        android:layout_marginRight="15dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/camera_tv_expose"
            android:layout_width="32dp"
            android:layout_height="20dp"
            android:textColor="#FFFFFF"
            android:textSize="15sp"
            android:gravity="center"/>

        <FrameLayout
            android:layout_width="32dp"
            android:layout_height="180dp"
            android:orientation="vertical">

            <SeekBar
                android:id="@+id/camera_sb_expose"
                android:layout_width="180dp"
                android:layout_height="32dp" 
                android:layout_gravity="center"
                android:rotation="270"/>

        </FrameLayout>

    </LinearLayout>

    <TextView
        android:id="@+id/camera_tv_help"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="20dp"
        android:text="@string/camera_tv"
        android:textColor="#FFFFFF" />

</RelativeLayout>

Screenshot for camera exposure compensation:

enter image description here

stkent
  • 19,772
  • 14
  • 85
  • 111
Maxim Mikhisor
  • 342
  • 4
  • 7
7

This worked for me, just put it into any layout you want to.

<FrameLayout
    android:layout_width="32dp"
    android:layout_height="192dp">

    <SeekBar
        android:layout_width="192dp"
        android:layout_height="32dp"
        android:layout_gravity="center"
        android:rotation="270" />

</FrameLayout>
Nick
  • 183
  • 2
  • 2
6

Wrap it inside a FrameLayout so that there is no Size issue.

  <FrameLayout
                android:layout_width="@dimen/_20dp"
                android:layout_marginStart="@dimen/_15dp"
                android:layout_marginEnd="@dimen/_15dp"
                android:layout_height="match_parent"
                android:orientation="vertical">

                <SeekBar
                    android:layout_width="150dp"
                    android:layout_height="30dp"
                    android:layout_gravity="center"
                    android:rotation="270" />
  </FrameLayout>
Abhishek Sengupta
  • 2,938
  • 1
  • 28
  • 35
6

Note, it appears to me that if you change the width the thumb width does not change correctly. I didn't take the time to fix it right, i just fixed it for my case. Here is what i did. Couldn't figure out how to contact the original creator.

public void setThumb(Drawable thumb) {
    if (thumb != null) {
        thumb.setCallback(this);

        // Assuming the thumb drawable is symmetric, set the thumb offset
        // such that the thumb will hang halfway off either edge of the
        // progress bar.
        //This was orginally divided by 2, seems you have to adjust here when you adjust width.
        mThumbOffset = (int)thumb.getIntrinsicHeight();
    }
user602622
  • 101
  • 3
  • 10
2

When moving the thumb with an EditText, the Vertical Seekbar setProgress may not work. The following code can help:

    @Override
public synchronized void setProgress(int progress) {
    super.setProgress(progress);
    updateThumb();
}

private void updateThumb() {
    onSizeChanged(getWidth(), getHeight(), 0, 0);
}

This snippet code found here: https://stackoverflow.com/a/33064140/2447726

Community
  • 1
  • 1
Guy West
  • 424
  • 4
  • 17
2

Try this

import android.content.Context;
import android.graphics.Canvas;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.SeekBar;

/**
 * Implementation of an easy vertical SeekBar, based on the normal SeekBar.
 */
public class VerticalSeekBar extends SeekBar {
    /**
     * The angle by which the SeekBar view should be rotated.
     */
    private static final int ROTATION_ANGLE = -90;

    /**
     * A change listener registrating start and stop of tracking. Need an own listener because the listener in SeekBar
     * is private.
     */
    private OnSeekBarChangeListener mOnSeekBarChangeListener;

    /**
     * Standard constructor to be implemented for all views.
     *
     * @param context The Context the view is running in, through which it can access the current theme, resources, etc.
     * @see android.view.View#View(Context)
     */
    public VerticalSeekBar(final Context context) {
        super(context);
    }

    /**
     * Standard constructor to be implemented for all views.
     *
     * @param context The Context the view is running in, through which it can access the current theme, resources, etc.
     * @param attrs   The attributes of the XML tag that is inflating the view.
     * @see android.view.View#View(Context, AttributeSet)
     */
    public VerticalSeekBar(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * Standard constructor to be implemented for all views.
     *
     * @param context  The Context the view is running in, through which it can access the current theme, resources, etc.
     * @param attrs    The attributes of the XML tag that is inflating the view.
     * @param defStyle An attribute in the current theme that contains a reference to a style resource that supplies default
     *                 values for the view. Can be 0 to not look for defaults.
     * @see android.view.View#View(Context, AttributeSet, int)
     */
    public VerticalSeekBar(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
    }

    /*
     * (non-Javadoc) ${see_to_overridden}
     */
    @Override
    protected final void onSizeChanged(final int width, final int height, final int oldWidth, final int oldHeight) {
        super.onSizeChanged(height, width, oldHeight, oldWidth);
    }

    /*
     * (non-Javadoc) ${see_to_overridden}
     */
    @Override
    protected final synchronized void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(heightMeasureSpec, widthMeasureSpec);
        setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
    }

    /*
     * (non-Javadoc) ${see_to_overridden}
     */
    @Override
    protected final void onDraw(@NonNull final Canvas c) {
        c.rotate(ROTATION_ANGLE);
        c.translate(-getHeight(), 0);

        super.onDraw(c);
    }

    /*
     * (non-Javadoc) ${see_to_overridden}
     */
    @Override
    public final void setOnSeekBarChangeListener(final OnSeekBarChangeListener listener) {
        // Do not use super for the listener, as this would not set the fromUser flag properly
        mOnSeekBarChangeListener = listener;
    }

    /*
     * (non-Javadoc) ${see_to_overridden}
     */
    @Override
    public final boolean onTouchEvent(@NonNull final MotionEvent event) {
        if (!isEnabled()) {
            return false;
        }

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            setProgressInternally(getMax() - (int) (getMax() * event.getY() / getHeight()), true);
            if (mOnSeekBarChangeListener != null) {
                mOnSeekBarChangeListener.onStartTrackingTouch(this);
            }
            break;

        case MotionEvent.ACTION_MOVE:
            setProgressInternally(getMax() - (int) (getMax() * event.getY() / getHeight()), true);
            break;

        case MotionEvent.ACTION_UP:
            setProgressInternally(getMax() - (int) (getMax() * event.getY() / getHeight()), true);
            if (mOnSeekBarChangeListener != null) {
                mOnSeekBarChangeListener.onStopTrackingTouch(this);
            }
            break;

        case MotionEvent.ACTION_CANCEL:
            if (mOnSeekBarChangeListener != null) {
                mOnSeekBarChangeListener.onStopTrackingTouch(this);
            }
            break;

        default:
            break;
        }

        return true;
    }

    /**
     * Set the progress by the user. (Unfortunately, Seekbar.setProgressInternally(int, boolean) is not accessible.)
     *
     * @param progress the progress.
     * @param fromUser flag indicating if the change was done by the user.
     */
    public final void setProgressInternally(final int progress, final boolean fromUser) {
        if (progress != getProgress()) {
            super.setProgress(progress);
            if (mOnSeekBarChangeListener != null) {
                mOnSeekBarChangeListener.onProgressChanged(this, progress, fromUser);
            }
        }
        onSizeChanged(getWidth(), getHeight(), 0, 0);
    }

    /*
     * (non-Javadoc) ${see_to_overridden}
     */
    @Override
    public final void setProgress(final int progress) {
        setProgressInternally(progress, false);
    }
}
Saef Myth
  • 287
  • 8
  • 19
1

I tried in many different ways, but the one which worked for me was. Use Seekbar inside FrameLayout

<FrameLayout
    android:id="@+id/VolumeLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_above="@id/MuteButton"
    android:layout_below="@id/volumeText"
    android:layout_centerInParent="true">
        <SeekBar
        android:id="@+id/volume"
        android:layout_width="500dp"
        android:layout_height="60dp"
        android:layout_gravity="center"
        android:progress="50"
        android:secondaryProgress="40"
        android:progressDrawable="@drawable/seekbar_volume"
        android:secondaryProgressTint="@color/tint_neutral"
        android:thumbTint="@color/tint_neutral"
    />

And in Code.

Setup Pre Draw callback on Seekbar, Where you can change the Width and height of the Seekbar I did this part in c#, so Code i used was

            var volumeSlider = view.FindViewById<SeekBar>(Resource.Id.home_link_volume);

            var volumeFrameLayout = view.FindViewById<FrameLayout>(Resource.Id.linkVolumeFrameLayout);

            void OnPreDrawVolume(object sender, ViewTreeObserver.PreDrawEventArgs e)
            {
                volumeSlider.ViewTreeObserver.PreDraw -= OnPreDrawVolume;
                var h = volumeFrameLayout.Height;
                volumeSlider.Rotation = 270.0f;
                volumeSlider.LayoutParameters.Width = h;
                volumeSlider.RequestLayout();
            }

            volumeSlider.ViewTreeObserver.PreDraw += OnPreDrawVolume;

Here i Add listener to PreDraw Event and when its triggered, I remove the PreDraw so that it doesnt go into Infinite loop.

So when Pre Draw gets executed, I fetch the Height of FrameLayout and assign it to Seekbar. And set the rotation of seekbar to 270. As my seekbar is inside frame Layout and its Gravity is set as Center. I dont need to worry about the Translation. As Seekbar always stay in middle of Frame Layout.

Reason i remove EventHandler is because seekbar.RequestLayout(); Will make this event to be executed again.

soan saini
  • 230
  • 3
  • 9
1

By using RotateLayout, having vertical SeekBar is a breeze. Just wrap that horrible SeekBar into it and Bob is your uncle:

<com.github.rongi.rotate_layout.layout.RotateLayout
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:angle="-90"
    >

        <androidx.appcompat.widget.AppCompatSeekBar
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
        />

</com.github.rongi.rotate_layout.layout.RotateLayout>

https://github.com/rongi/rotate-layout

Peter
  • 340
  • 4
  • 13
0

Getting started

Add these lines to build.gradle.

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

Usage

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

}

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+.

Phadadev
  • 366
  • 2
  • 8
0

In my case, I used an ordinary seekBar and just flipped out the layout.

seekbark_layout.xml - my layout that containts seekbar which we need to make vertical.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rootView"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

<SeekBar
    android:id="@+id/seekBar"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:layout_alignParentBottom="true"/>

</RelativeLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.vgfit.seekbarexample.MainActivity">

<View
    android:id="@+id/headerView"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:background="@color/colorAccent"/>

<View
    android:id="@+id/bottomView"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:layout_alignParentBottom="true"
    android:background="@color/colorAccent"/>

<include
    layout="@layout/seekbar_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_above="@id/bottomView"
    android:layout_below="@id/headerView"/>

 </RelativeLayout>

And in MainActivity I rotate seekbar_layout:

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.RelativeLayout
import kotlinx.android.synthetic.main.seekbar_layout.*


class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    rootView.post {
        val w = rootView.width
        val h = rootView.height

        rootView.rotation = 270.0f
        rootView.translationX = ((w - h) / 2).toFloat()
        rootView.translationY = ((h - w) / 2).toFloat()

        val lp = rootView.layoutParams as RelativeLayout.LayoutParams
        lp.height = w
        lp.width = h
        rootView.requestLayout()
    }
}
}

As a result we have necessary vertical seekbar: enter image description here

Vdovin
  • 107
  • 2
  • 9
0

You can do it by yourself - it's now so difficult. Here is an example from my project: https://github.com/AlShevelev/WizardCamera

Let start from settings (attrs.xml).

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ExpositionBar">
        <attr name="button_icon" format="reference" />
        <attr name="button_icon_size" format="dimension" />

        <attr name="stroke_width" format="dimension" />

        <attr name="stroke_color" format="color" />
        <attr name="button_color" format="color" />
        <attr name="button_color_pressed" format="color" />

        <attr name="min_value" format="float" />
        <attr name="max_value" format="float" />
    </declare-styleable>
</resources>

Here is a couple of utility functions:

fun <T: Comparable<T>>T.fitInRange(range: Range<T>): T =
    when {
        this < range.lower -> range.lower
        this > range.upper -> range.upper
        else -> this
    }

fun Float.reduceToRange(rangeFrom: Range<Float>, rangeTo: Range<Float>): Float =
    when {
        this == rangeFrom.lower -> rangeTo.lower
        this == rangeFrom.upper -> rangeTo.upper
        else -> {
            val placeInRange = (this - rangeFrom.lower) / (rangeFrom.upper - rangeFrom.lower)
            ((rangeTo.upper - rangeTo.lower) * placeInRange) + rangeTo.lower
        }
    }

And at last, but not least - a class for vertical seek bar:

class ExpositionBar
@JvmOverloads
constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val drawingRect = RectF(0f, 0f, 0f, 0f)
    private val drawingPaint = Paint(Paint.ANTI_ALIAS_FLAG)

    private val strokeWidth: Float

    @ColorInt
    private val strokeColor: Int
    @ColorInt
    private val buttonFillColor: Int
    @ColorInt
    private val buttonFillColorPressed: Int

    private val icon: VectorDrawable

    private val valuesRange: Range<Float>

    private var centerX = 0f
    private var minY = 0f
    private var maxY = 0f

    private var buttonCenterY = 0f
    private var buttonRadiusExt = 0f
    private var buttonRadiusInt = 0f
    private var buttonMinY = 0f
    private var buttonMaxY = 0f
    private var buttonCenterBoundsRange = Range(0f, 0f)

    private var iconTranslationX = 0f
    private var iconTranslationY = 0f

    private var isInDragMode = false

    private var onValueChangeListener: ((Float) -> Unit)? = null

    private var oldOutputValue = Float.MIN_VALUE

    init {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExpositionBar)

        icon =  typedArray.getDrawable(R.styleable.ExpositionBar_button_icon) as VectorDrawable
        val iconSize = typedArray.getDimensionPixelSize(R.styleable.ExpositionBar_button_icon_size, 0)
        icon.setBounds(0, 0, iconSize, iconSize)

        strokeWidth = typedArray.getDimensionPixelSize(R.styleable.ExpositionBar_stroke_width, 0).toFloat()
        drawingPaint.strokeWidth = strokeWidth

        strokeColor = typedArray.getColor(R.styleable.ExpositionBar_stroke_color, Color.WHITE)
        buttonFillColor = typedArray.getColor(R.styleable.ExpositionBar_button_color, Color.BLACK)
        buttonFillColorPressed = typedArray.getColor(R.styleable.ExpositionBar_button_color_pressed, Color.BLUE)

        val minValue = typedArray.getFloat(R.styleable.ExpositionBar_min_value, 0f)
        val maxValue = typedArray.getFloat(R.styleable.ExpositionBar_max_value, 0f)
        valuesRange = Range(minValue, maxValue)

        typedArray.recycle()
    }

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

        drawingRect.right = width.toFloat()
        drawingRect.bottom = height.toFloat()

        buttonCenterY = drawingRect.centerY()

        recalculateDrawingValues()
    }

    override fun onDraw(canvas: Canvas) {
        drawingPaint.color = strokeColor
        drawingPaint.style = Paint.Style.STROKE

        // Draw the center line
        canvas.drawLine(centerX, minY, centerX, buttonMinY, drawingPaint)
        canvas.drawLine(centerX, buttonMaxY, centerX, maxY, drawingPaint)

        // Draw the button
        canvas.drawCircle(centerX, buttonCenterY, buttonRadiusExt, drawingPaint)
        drawingPaint.style = Paint.Style.FILL
        drawingPaint.color = if(isInDragMode) buttonFillColorPressed else buttonFillColor
        canvas.drawCircle(centerX, buttonCenterY, buttonRadiusInt, drawingPaint)

        // Draw button icon
        canvas.translate(iconTranslationX, iconTranslationY)
        icon.draw(canvas)
        canvas.translate(-iconTranslationX, -iconTranslationY)
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if(!isEnabled) {
            return false
        }

        when(event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                if(isButtonHit(event.y)){
                    isInDragMode = true
                    invalidate()
                }
            }
            MotionEvent.ACTION_MOVE -> {
                if(isInDragMode) {
                    buttonCenterY = event.y.fitInRange(buttonCenterBoundsRange)
                    recalculateDrawingValues()
                    invalidate()

                    val outputValue = buttonCenterY.reduceToRange(buttonCenterBoundsRange, valuesRange)
                    if (outputValue != oldOutputValue) {
                        onValueChangeListener?.invoke(outputValue)
                        oldOutputValue = outputValue
                    }
                }
            }
            MotionEvent.ACTION_UP,
            MotionEvent.ACTION_CANCEL -> {
                isInDragMode = false
                invalidate()
            }
        }
        return true
    }

    fun setOnValueChangeListener(listener: ((Float) -> Unit)?) {
        onValueChangeListener = listener
    }

    private fun recalculateDrawingValues() {
        centerX = drawingRect.left + drawingRect.width()/2
        minY = drawingRect.top
        maxY = drawingRect.bottom

        buttonRadiusExt = drawingRect.width() / 2 - strokeWidth / 2
        buttonRadiusInt = buttonRadiusExt - strokeWidth / 2
        buttonMinY = buttonCenterY - buttonRadiusExt
        buttonMaxY = buttonCenterY + buttonRadiusExt

        val buttonCenterMinY = minY + buttonRadiusExt + strokeWidth / 2
        val buttonCenterMaxY = maxY - buttonRadiusExt - strokeWidth / 2
        buttonCenterBoundsRange = Range(buttonCenterMinY, buttonCenterMaxY)

        iconTranslationX = centerX - icon.bounds.width() / 2
        iconTranslationY = buttonCenterY - icon.bounds.height() / 2
    }

    private fun isButtonHit(y: Float): Boolean {
        return y >= buttonMinY && y <= buttonMaxY
    }
}

You can use it as shown here:

<com.shevelev.wizard_camera.main_activity.view.widgets.ExpositionBar
    android:id="@+id/expositionBar"
    android:layout_width="@dimen/mainButtonSize"
    android:layout_height="300dp"
    android:layout_gravity="end|center_vertical"

    android:layout_marginEnd="@dimen/marginNormal"
    android:layout_marginBottom="26dp"

    app:button_icon = "@drawable/ic_brightness"
    app:button_icon_size = "@dimen/toolButtonIconSize"
    app:stroke_width = "@dimen/strokeWidthNormal"
    app:stroke_color = "@color/mainButtonsForeground"
    app:button_color = "@color/mainButtonsBackground"
    app:button_color_pressed = "@color/mainButtonsBackgroundPressed"
    app:min_value="-100"
    app:max_value="100"
/>

Voila!

Alex Shevelev
  • 681
  • 7
  • 14
0

Hidden Vertical SeekBar

In a simpler way, you can make a SeekBar like this.

Like the way to increase or decrease the volume in video players. All three Last attributes can be manipulated in the SeekBar.

<LinearLayout
        android:layout_width="@dimen/_40dp"
        android:layout_height="wrap_content"
        android:layout_marginVertical="100dp"
        android:gravity="center"
        android:orientation="vertical"
        >
        <SeekBar
            android:layout_width="500dp"
            android:layout_height="300dp"
            android:layout_gravity="center"
            android:rotation="270"
            android:secondaryProgress="6"
            android:progress="15"
            android:progressDrawable="@null"
            android:thumbTint="@null"
            android:secondaryProgressTint="@null"
            />
    </LinearLayout>
0

I made vertical seekbar library years ago for solve this see:

https://github.com/ASE55471/Android-VSlider

This library does not rotate original seekbar 90 or 270 degree, It's rewrite from scratch for resolve "Thumb not in the right place", "Can't set position from code", "Can't line up background drawable", "Touch position run away" etc. And also have some extra useful feature.

flutroid
  • 1,166
  • 8
  • 24
-1

Simple answer

Instead of using android:rotation="270" inside of a seek bar, use it inside of a FrameLayout that wraps around it.

 <FrameLayout
    android:background="@color/gray"
    android:layout_width="300dp"
    android:layout_height="5dp"
    android:layout_marginEnd="-126dp"
    android:rotation="270"
    android:orientation="vertical"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent">
<SeekBar
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />
</FrameLayout>

To get my frame layout to be 24dp margin right I calculated width -150dp + 24dp because the frame layout is first drawn horizontally and then rotated vertically.

Pavle37
  • 571
  • 9
  • 24