Reputation: 3839
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?
Upvotes: 54
Views: 48330
Reputation: 1991
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
}
}
Upvotes: 0
Reputation: 9052
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" />
Upvotes: 2
Reputation: 376
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();
}
});
}
}
Upvotes: 0
Reputation: 10259
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.
Upvotes: 2
Reputation: 34360
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
Upvotes: 9
Reputation: 2080
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....
Upvotes: 9
Reputation: 1650
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"
/>
Upvotes: 155
Reputation: 5740
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...
Upvotes: 11
Reputation: 433
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;
}
}
Upvotes: 31
Reputation: 141
I had problems while using this code with setProgress method. To solve them I suggest overriding setProgress and adding onSizeChanged call to it.
Upvotes: 13