如何在安卓中制作一个垂直的SeekBar?

88

一个 SeekBar 可以垂直吗?由于我不是很擅长UI设计,你能否提供一些模板和示例来帮助���让 SeekBar 更美观。


2
你必须自己实现。这里链接了一个未完成的版本(https://dev59.com/0HRB5IYBdhLWcg3wa2m2),另一个链接在这里(http://groups.google.com/group/android-developers/browse_thread/thread/bec05b0368c20e03)。 - Pentium10
最近我需要实现它。请看这个链接:https://github.com/chanjungkim/VerticalSeekbar。 - c-an
19个回答

80
  1. 对于API 11及以上,可以使用seekbar的XML属性(android:rotation="270")实现垂直效果。

<SeekBar
android:id="@+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:rotation="270"/>
  • 对于旧的API等级(例如API10),只需使用Selva的答案:
    https://github.com/AndroSelva/Vertical-SeekBar-Android


  • 25
    方案1-旋转的SeekBar无法正常工作。拇指被绘制出来,大小和位置无法正确设置(至少在RelativeLayout内部)。 - Pointer Null
    2
    它确实可以工作,但是您必须记住尺寸会被交换,所以曾经的宽度变成了高度等等。因此,当您增加高度时,实际上正在拉伸它,这就是为什么拇指会被绘制远离的原因,即使是常规的seekbar,如果您拉伸了宽度,也会发生这种情况。旋转很烦人,所以我认为更容易的方法是将seekbar放在布局中并正确调整其大小,然后旋转布局并调整大小以适应主布局。 - Fahad Alduraibi
    2
    非常烦人:您使用宽度属性设置SeekBar的实际高度,但在布局的其余部分中,这仍然是SeekBar元素的宽度。如果您将外部元素的宽度属性赋予较小的值(因为垂直元素只需要较小的宽度),则SeekBar的垂直线条会相应缩短。这可以通过引入一个额外的包装器来解决,在具有小宽度属性的外部容器内仍具有大宽度属性。但是,然后确实存在拇指偏移量。我放弃了 :-p - Matthias
    5
    啊,我点赞太快了。这几乎没用。滑块在内部使用旋转前的宽度/高度,因此一切都摆放和大小不协调。 - b005t3r
    1
    这行不通,因为滑块使用宽度来确定条的长度! - Yashovardhan99
    显示剩余5条评论

    55

    这是一个非常好的垂直滑动条的实现方式。看一下。

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

    这是我基于this实现的垂直和倒置SeekBar。

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

    2
    嗨,我尝试了你发布的.zip代码,并且它运行得很好...除了一个问题:SeekBar 仍然从左到右绘制其进度而不是从底部到顶部。我还没有找到它在哪里编码。你有什么建议吗?谢谢 - Droidman
    2
    你好!我很喜欢你的verticalScrollbar Inverted,我也试着创建一个,但是在旋转和平移方面遇到了很多困难。不过我发现了一个小错误:如果MAX不是100,则进度计算不正确。要修复它,你需要将(100-i)更改为(getMax()-i)。这样就可以适用于任何范围,完美解决问题。 - rupps
    显然,这里的值可以是负数或大于最大值,因此我将其更改为 Math.max(0, Math.min(getMax(), i) - Matthias
    1
    @AndroSelva,seekbar.setprogress(position)不能将垂直的seekbar拇指移动到所需位置。...为什么? - Animesh Mangla
    拇指不起作用。 - user25
    当将此VerticalScrollBar放入具有权重的LinearLayout中时,它会被绘制不正确 - 但是可以通过将其包含在FrameLayout中来解决。 - Piotr Śmietana

    29

    工作示例

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

    将代码粘贴并保存。现在可以在您的XML布局中使用它:

    <android.widget.VerticalSeekBar
      android:id="@+id/seekBar1"
      android:layout_width="wrap_content"
      android:layout_height="200dp"
      />
    

    请确保创建一个名为android.widget的包,并在该包下创建VerticalSeekBar.java


    完美运行!谢谢! - mjp66
    这只是滑块,而不是拇指。 - Fahid Nadeem
    尝试使用android:thumb="@drawable/thumb_image"设置自己的拇指。但我没有遇到这个问题。https://dev59.com/qmkw5IYBdhLWcg3ws833#18559778 - Zar E Ahmer
    @XarEAhmer 这个是从 Github 上来的还是你自己写的?我正在寻找许可证以便在我的项目中实现它。 - Vince VD
    它来自于 Github 的某个地方。 - Zar E Ahmer
    显示剩余2条评论

    15

    尝试:

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

    通过添加android:rotation="270"。 - Ali Alnoaimi
    9
    设置旋转会旋转滑块但不会旋转布局。layout_width仍控制滑块的长度。因此,布局仍认为它是宽而不是高。这使得正确地将其插入设计中变得有趣。我仍然没有弄清楚如何将旋转的拖动条放在GridLayout上。 - JT.

    11

    运行良好,谢谢。您还应该为mOnSeekBarChangeListener变量添加空值检查。 - jekatt
    @JörgEisfeld,设置拇指和进度绘制失败了。 - noobEinstien
    @JörgEisfeld,你知道我怎么支持双向语言比如阿拉伯语吗?因为这个垂直SeekBar变成了上下颠倒,滑块给出了错误的进度方向。 - blueware
    @blueware:尝试使用c.rotate(-90);c.translate(-getHeight(),0);这两行代码进行操作 - 第二行代码会使其垂直翻转。可以尝试将旋转角度改为+90或270,而不是-90等,并且去掉翻转的平移操作。 - hippietrail

    10
    我们使用 android:rotation="270" 制作了一个垂直的 SeekBar:
    <?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>
    

    相机曝光补偿的截图:

    enter image description here


    1
    感谢指针!诀窍是设置高度......否则FrameLayout在旋转前使用SeekBar的宽度。 - ShellDude
    不工作.... - Vitaly

    7
    这对我有用,只需将其放入您想要的任何布局中即可。
    <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>
    

    诀窍在于设置高度...否则,FrameLayout将使用旋转前的Seeker宽度。 - ShellDude
    这是之前由Maxim Mikhisor给出的相同答案吗? - federico verchez
    不工作..... - Vitaly

    6

    注意,据我观察,如果您更改宽度,则滑块宽度不会正确更改。 我没有花时间正确修复它,我只是为我的情况修复了它。这是我所做的。 无法联系原始创建者。

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

    6
    将其放入FrameLayout中,以避免出现大小问题。
      <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>
    

    1
    到目前为止最佳解决方案! - Muahmmad Tayyib
    不工作..... - Vitaly

    2

    试试这个

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

    这个方法有一个缺陷 - 如果用户继续将拇指拖动到视图边界之外,报告的“progress”可能会变得小于最小值或最大值。 - Roman Samoilenko
    是的,我看到了问题,当我们快速移动搜索滑块时。 - Herry

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接