如何在 Android 中建立一个垂直的 SeekBar?

SeekBar可以是垂直的吗?我不是很擅长用户界面设计,所以我怎样才能使 SeekBar更漂亮,请给我一些模板和例子。

117782 次浏览

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

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


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

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

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

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

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

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

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>

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

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

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

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>

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.

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!

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

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.