Android自定义View-中间向左右滑动的seekbar

seekbar的样式层出不穷,下面这篇文章也只是讲述了其中的一种,自定义view实现从中间向左右滑动的seekbar。如图


seekbar.png

从上面的图片看得出该seekbar的构成分为底部默认的背景条,开始滑动的红色背景条和中间的圆形thumb。那么就按照该结构开始编写代码。
首先我们来看一下所需要的全局变量,以便后面代码的阅读。

 //    中间的拖动bar
    private Drawable mThumb;
    //    默认的背景
    private Drawable mDefaultBack;
    //    滑动后的背景
    private Drawable mSlideBack;
    
    private int mSeekBarWidth;
    private int mSeekBarHeight;
    private int mThumbWidth;
    private int mThumbHeight;
    //     thumb的中心位置
    private int mThumbCenterPosition = 0;
    //     能滑动的总长度
    private int mSlideTotalDistance = 0;

代码中也都注释了,不多说明。我们接着往下看。进行初始化操作。

private void init() {
        mThumb = getResources().getDrawable(R.drawable.thumb);
        mDefaultBack = getResources().getDrawable(R.drawable.back_default);
        mSlideBack = getResources().getDrawable(R.drawable.back_slide);

        mSeekBarHeight = mDefaultBack.getIntrinsicHeight();
        mSeekBarWidth = mDefaultBack.getIntrinsicWidth();

        mThumbHeight = mThumb.getIntrinsicHeight();
        mThumbWidth = mThumb.getIntrinsicWidth();
    }

上述代码中,将中间圆形mThumb,默认背景mDefaultBack,滑动红色背景mSlideBack进行相应的初始化。为了后面mThumb的滑动以及位置的变化,这里需要以默认背景条来作为整条seekbar的宽度和高度,以及获取mThumb的宽度和高度。
对于view的绘制一般分为onMeasure,onLayout,onDraw三个步骤,
-onMeasure():测量自己的大小,为正式布局提供建议;
-onLayout():使用layout()函数对所有子控件布局;
-onDraw():根据布局的位置绘图;
那么先进入第一步对view进行测量。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = measureWidth(widthMeasureSpec);
        mSeekBarWidth = width;
        mThumbCenterPosition = width / 2;
        mSlideTotalDistance = width - mThumbWidth;
        setMeasuredDimension(width, mThumbHeight);
    }

    private int measureWidth(int measureSpec) {
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.AT_MOST) {

        } else if (specMode == MeasureSpec.EXACTLY) {

        }
        return specSize;
    }

上述代码中mThumbCenterPosition指的Thumb的圆心坐标,mSlideTotalDistance为能滑动的总长度。这里最为重要的地方为圆心坐标的值mThumbCenterPosition = width / 2,一般thumb的位置位于seekbar的起始位置,那么这里就是将thumb设置到seekbar的中点,从这里开始向左向右滑动。
上述是做好了view 的测量,一般来说测量只是你建议的值,真正布局是在onLayout中,在这里呢我把onLayout省略掉了,在onMeasure中做好了处理。那么就接着看看onDraw的重头戏。
按照思路,把绘制的过程分为三个部分,第一部分:绘制默认的背景条,这个很简单

mDefaultBack.setBounds(mThumbWidth / 2, 0, mSeekBarWidth - mThumbWidth / 2, mSeekBarHeight);
        mDefaultBack.draw(canvas);

从第一个thumb的中心点到最后一个thumb的中心点绘制默认的背景条。
第二部分:绘制thumb,按照thumb的宽度进行绘制即可。

mThumb.setBounds(mThumbCenterPosition - mThumbWidth / 2, 0, mThumbCenterPosition + mThumbWidth / 2, mThumbHeight);
        mThumb.draw(canvas);

第三部分:开始随着手势绘制滑动的进度条


        if (mThumbCenterPosition > mSeekBarWidth / 2) {
            mSlideBack.setBounds(mSeekBarWidth / 2, 0, mThumbCenterPosition, mSeekBarHeight);

        } else if (mThumbCenterPosition < mSeekBarWidth / 2) {
            mSlideBack.setBounds(mThumbCenterPosition, 0, mSeekBarWidth / 2, mSeekBarHeight);
        } else {
            mSlideBack.setBounds(mThumbCenterPosition, 0, mSeekBarWidth / 2, mSeekBarHeight);
        }
        mSlideBack.draw(canvas);

咱们来分析下这个代码,因为今天所做的seekbar分为左右两部分,所以根据不同情况做不同处理。首先如果小圆球的中心位于有半部分,那么将绘制的范围设置为(mSeekBarWidth / 2到mThumbCenterPosition)即seekbar中心点到小圆球的中心位置,小圆球的中心位置是随着手势变化的,所以就就形成了动态绘制的效果。
理解了上面的第一段代码,那么接下来的两段就很好理解了。和上面一样,如果小圆球处于左半部分,做相同处理,只不过范围变了罢了。最后一种情况是位于中心位置,就不多说了。最后就将上述不同的情况进行绘制了。
从上面的分析,我们知道了图形是怎么绘制的,我们也知道能动态绘制的重点在于小圆球的中心点,也就是mThumbCenterPosition,那么mThumbCenterPosition是如何随手势获取的呢?
我们带着这个疑问,来看下面的代码:

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (mSeekBarChangeListener != null) {

                }
                mFlag = getAreaFlag(event);
                if (mFlag == CLICK_ON_PRESS) {
                    mThumb.setState(STATE_PRESSED);
                    mSeekBarChangeListener.onProgressBefore();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mFlag == CLICK_ON_PRESS) {
                    if (event.getX() < 0 || event.getX() <= mThumbWidth / 2) {
                        mThumbCenterPosition = mThumbWidth / 2;
                    } else if (event.getX() >= mSeekBarWidth - mThumbWidth / 2) {
                        mThumbCenterPosition = mSlideTotalDistance + mThumbWidth / 2;
                    } else {
                        mThumbCenterPosition = (int) event.getX();
                    }
                }
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                mThumb.setState(STATE_NORMAL);
                if (mSeekBarChangeListener != null) {
                    mSeekBarChangeListener.onProgressAfter();
                }
                break;
            default:
                break;
        }
        return true;
    }

从知道随手势变化来动态的获取小圆球的中心值,就能想到要用Touch事件了,上面代码分为down,move和up,我们现在只看move部分,首先获取手势在x方向的值,然后判断是否超出了seekbar的范围,超过左边就将设置为小圆球的半径的长度位置,如果超过了右边就设置为seekbar长度减小圆球半径的位置,这里如果有不清楚的可以在纸上自己画一下图。最后就是处于seekbar范围内,直接就随着手势获取手势的位置,即 mThumbCenterPosition = (int) event.getX();到这里,基本的布局和绘制也就结束了。
如果你想使用seekbar的change事件,可以自己定义一个changeListener,如下:

public interface OnSeekBarChangeListener {
        void onProgressBefore();

        void onProgressChanged(MidThumbSeekBar seekBar, double progress);

        void onProgressAfter();
    }

好了,从中间向左右滑动的seekbar全部绘制完了,最后我们来调用 并运行
xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.fusy.midthumbseekbar.MidThumbSeekBar
        android:id="@+id/seekBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        />

</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity implements MidThumbSeekBar.OnSeekBarChangeListener {
    private MidThumbSeekBar mBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        mBar = findViewById(R.id.seekBar);
        mBar.setOnSeekBarChangeListener(this);
    }

    @Override
    public void onProgressBefore() {

    }

    @Override
    public void onProgressChanged(MidThumbSeekBar seekBar, double progress) {

    }

    @Override
    public void onProgressAfter() {

    }
}

End.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容