Android开发自定义双向SeekBar拖动条控件

目录
  • 目标:双向拖动的自定义View
  • 实现步骤
    • 自定义属性获取
    • 确定自定义view尺寸
    • 绘制相关的内容部分
    • 滑动事件处理

目标:双向拖动的自定义View

国际惯例先预览后实现

我们要实现的就是一个段位样式的拖动条,用来做筛选条件用的,细心的朋友可能会发现微信设置里面有个一个通用字体的设置,拖动然后改变字体大小;

这个相对比微信那个的自定义view算是一个扩展,因为我们是双向滑动,这个多考虑的一点就是手指拖动的是哪一个滑动块!

我们先看下GIF预览,然后我们今天就一步步实现这个小玩意…

实现步骤

  • 自定义属性的抽取
  • view尺寸的计算
  • 相关内容的绘制(文字,原点,背景进度条,当前进度条等等)
  • 处理滑动事件

大体思路分四部分;我们一步步来;简单的就一部带过了

自定义属性获取

 public ATDragView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setBackgroundColor(ContextCompat.getColor(context, android.R.color.white));
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ATDragView, defStyleAttr, R.style.def_dragview);
        int indexCount = typedArray.getIndexCount();
        for (int i = 0; i < indexCount; i++) {
            int attr = typedArray.getIndex(i);
            switch (attr) {
                case R.styleable.ATDragView_seek_bg_color:
                    seekBgColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATDragView_seek_pb_color:
                    seekPbColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATDragView_seek_ball_solid_color:
                    seekBallSolidColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATDragView_seek_ball_stroke_color:
                    seekBallStrokeColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATDragView_seek_text_color:
                    seekTextColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATDragView_seek_text_size:
                    seekTextSize = typedArray.getDimensionPixelSize(attr, 0);
                    break;
            }
        }
        typedArray.recycle();
        init();
    }

拿到我们设置的属性后,初始化我们需要的工具,比如画笔,等

    private void init() {
        currentMovingType = BallType.LEFT;
        seekTextPaint = creatPaint(seekTextColor, seekTextSize, Paint.Style.FILL, 0);
        seekBgPaint = creatPaint(seekBgColor, 0, Paint.Style.FILL, 0);
        seekBallPaint = creatPaint(seekBallSolidColor, 0, Paint.Style.FILL, 0);
        seekPbPaint = creatPaint(seekPbColor, 0, Paint.Style.FILL, 0);
        seekBallEndPaint = creatPaint(seekPbColor, 0, Paint.Style.FILL, 0);
        seekBallStrokePaint = creatPaint(seekBallStrokeColor, 0, Paint.Style.FILL, 0);
        seekBallStrokePaint.setShadowLayer(5, 2, 2, seekBallStrokeColor);
    }

确定自定义view尺寸

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int measureHeight;
        if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
            measureHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_HEIGHT, getContext().getResources().getDisplayMetrics());
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

绘制相关的内容部分

这里我们分析效果图发现,需要绘制五部分,两个圆,两个进度条一个 一堆文字,我们根据计算出来的view尺寸以及UI给的比例,即可绘制出来他们这个就是canvas的API使用

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawTexts(canvas);
        drawSeekBG(canvas);
        drawSeekPB(canvas);
        drawLeftCircle(canvas);
        drawRightCircle(canvas);
    }

具体的文字绘制,是根据外界传入的数据来绘制的所以细节如下

  private void drawTexts(Canvas canvas) {
        if (null == data) return;
        int size = data.size();
        int unitWidth = getUnitWidth(size - 1);
        for (int i = 0; i < size; i++) {
            String tempDesc = data.get(i);
            float measureTextWidth = seekPbPaint.measureText(tempDesc);
            canvas.drawText(tempDesc, DEF_PADDING + unitWidth * i - measureTextWidth / 2, seekTextY, seekTextPaint);
        }
    }

这个View的核心部分不是绘制,而是计算,描述下我们具体的确定位置的思路

  • 根据外界传入的数据集合平均分view的宽度,求得平均一份的宽度大小
  • 然后循环数据集合根据平均一份的宽度,确定没个文字所在的坐标值

然后我们看下计算的代码;

// 计算单位宽度,view宽度-内容的左右边距以及圆球的半径,自己体会下为什么
   private int getUnitWidth(int count) {
        return (viewWidth - 2 * DEF_PADDING - 2 * seekBallRadio) / count;
    }

这个方法可以说是最重要的一个了,

//根据当前手指触摸的x坐标计算,手指离开屏幕以后,应该停留到哪个位置,比如滑动到400到500之间但是不到600,我们不能让他停留在半路上,让他自动找回他停留的左边,也就是GIF中的小小回弹效果
  private int getCurrentSeekX(int upX) {
        if (null == data) {
            return 0;
        }
        int unitWidth = getUnitWidth(data.size() - 1);
        return unitWidth * (upX / unitWidth);
    }

滑动事件处理

核心的代码全部完毕了,我们看下onTouch里面的处理

 public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            //记录手指按下的坐标
                downX = (int) event.getX();
                // 根据当前坐标,确定要移动哪个球球,因为我们说了,我们这个是有两个球的,唯一的一个技巧点就是这个地方,根据手指按下的坐标找到距离哪个球位置最近就移动哪个球,这里注意下.
                currentMovingType = getMovingLeftOrRight(downX);
                if (BallType.LEFT == currentMovingType) {
                    leftSeekBallX = downX;
                } else if (BallType.RIGHT == currentMovingType) {
                    rightSeekBallX = downX;
                }
                seekPbRectF = new RectF(leftSeekBallX, viewHeight * SEEK_BG_SCALE, rightSeekBallX, viewHeight * SEEK_BG_SCALE + BG_HEIGHT);
                break;
            case MotionEvent.ACTION_MOVE:
            //移动的时候根据计算出来的位置以及方向改变两个小球的位置以及举行进度条的RectF的范围
                int moveX = (int) event.getX();
                // 特殊情况处理,两个球重合应该怎么办,
                if (leftSeekBallX == rightSeekBallX) {
                    if (moveX - downX > 0) {
                        currentMovingType = BallType.RIGHT;
                        rightSeekBallX = moveX;
                    } else {
                        currentMovingType = BallType.LEFT;
                        leftSeekBallX = moveX;
                    }
                } else {
                    if (BallType.LEFT == currentMovingType) {
                        leftSeekBallX = leftSeekBallX - rightSeekBallX >= 0 ? rightSeekBallX : moveX;
                    } else if (BallType.RIGHT == currentMovingType) {
                        rightSeekBallX = rightSeekBallX - leftSeekBallX <= 0 ? leftSeekBallX : moveX;
                    }
                }
                seekPbRectF = new RectF(leftSeekBallX, viewHeight * SEEK_BG_SCALE, rightSeekBallX, viewHeight * SEEK_BG_SCALE + BG_HEIGHT);
                break;
            case MotionEvent.ACTION_UP:
            // 手指离开的时候,确定返回给UI的数据集
                if (BallType.LEFT == currentMovingType) {
                    leftPosition = getDataPosition((int) event.getX());
                    leftSeekBallX = leftSeekBallX - rightSeekBallX >= 0 ? rightSeekBallX : getCurrentSeekX((int) event.getX()) + DEF_PADDING + seekBallRadio;
                } else if (BallType.RIGHT == currentMovingType) {
                    rightPosition = getDataPosition((int) event.getX());
                    rightSeekBallX = rightSeekBallX - leftSeekBallX <= 0 ? leftSeekBallX : getCurrentSeekX((int) event.getX()) + DEF_PADDING + seekBallRadio;
                }
                if (null != dragFinishedListener) {
                    dragFinishedListener.dragFinished(leftPosition, rightPosition);
                }
                break;
        }
// 边界处理,确保左边的球不会超过右边的,右边的不会超过左边的
        if (BallType.LEFT == currentMovingType) {
            if (leftSeekBallX < seekBallRadio + DEF_PADDING) {
                leftSeekBallX = seekBallRadio + DEF_PADDING;
            }
            if (leftSeekBallX > viewWidth - seekBallRadio - DEF_PADDING) {
                leftSeekBallX = viewWidth - seekBallRadio - DEF_PADDING;
            }
        } else if (BallType.RIGHT == currentMovingType) {
            if (rightSeekBallX < seekBallRadio + DEF_PADDING) {
                rightSeekBallX = seekBallRadio + DEF_PADDING;
            }
            if (rightSeekBallX > viewWidth - seekBallRadio - DEF_PADDING) {
                rightSeekBallX = viewWidth - seekBallRadio - DEF_PADDING;
            }
        }
        seekPbRectF = new RectF(leftSeekBallX, viewHeight * SEEK_BG_SCALE, rightSeekBallX, viewHeight * SEEK_BG_SCALE + BG_HEIGHT);
        invalidate();
        return true;
    }

大部分的核心的代码就这么多,然后剩下的view写完了就该把回调借口透出给UI 完活了…..

public void setData(List<String> data, OnDragFinishedListener dragFinishedListener) {
        this.dragFinishedListener = dragFinishedListener;
        this.data = data;
        leftPosition = 0;
        if (null != data && data.size() != 0) {
            rightPosition = data.size() - 1;
        }
    }

源代码下载地址 https://github.com/GuoFeilong/ATDragViewDemo

以上就是Android开发自定义双向SeekBar拖动条控件的详细内容,更多关于Android自定义双向SeekBar的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android SeekBar 自定义thumb旋转动画效果

    目录 简介 示例 dimens.xml drawable shape_thumb_round_1.xml layers_thumb_ring_sweep_1.xml rotate_thumb_1.xml layers_seek_bar_progress_1.xml layout Activity中调用 小结 简介 某些音乐播放或者视频播放的界面上,资源还在加载时,进度条的原点(thumb)会显示一个转圈的效果. 资源加载完成后,又切换回静态效果.这个效果增强了用户体验. 一般来说有美术人员负责设

  • Android双向选择控件DoubleSeekBar使用详解

    本文实例为大家分享了Android双向选择控件DoubleSeekBar的使用方法,供大家参考,具体内容如下 先看效果图 1.DoubleSlideSeekBar public class DoubleSlideSeekBar extends View { /** * 线条(进度条)的宽度 */ private int lineWidth; /** * 线条(进度条)的长度 */ private int lineLength = 400; /** * 字所在的高度 100$ */ private

  • Android中SeekBar拖动条使用方法详解

    本文实例为大家分享了Android中SeekBar拖动条使用方法的具体代码,供大家参考,具体内容如下 SeekBar控件效果展示 拖动条SeekBar继承了ProgressBar,因此ProgressBar所支持的xml属性和方法完全适合SeekBar.只是进度条ProgressBar采用颜色填充来表明进度完成程度,拖动条SeekBar则通过滑块的外置来标识--拖动滑块允许进度值的改变.(例如:条件Android系统的音量) 如上图,通过拖动SeekBar滑块,实现图片透明度的修改.实现代码如下

  • Android 自定义SeekBar 实现分段显示不同背景颜色的示例代码

    在最近的开发工作中,要实现一个调色板的进度条,SeekBar要分成10段显示不同颜色,功夫不负有心人,终于实现了这个功能,下面分享给大家 示例图: 1.自定义SeekBar import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import

  • Android seekbar实现可拖动进度条

    本文实例为大家分享了Android seekbar实现可拖动进度条的具体代码,供大家参考,具体内容如下 SeekBar通过滑块的位置来标识数值 允许用户通过拖动滑块来改变进度值的大小 控件:SeekBar            两个TextView 显示状态 实现SeekBar.OnSeekBarChangeListener接口 对事件进行监听 xml文件: <?xml version="1.0" encoding="utf-8"?> <Linea

  • Android开发自定义双向SeekBar拖动条控件

    目录 目标:双向拖动的自定义View 实现步骤 自定义属性获取 确定自定义view尺寸 绘制相关的内容部分 滑动事件处理 目标:双向拖动的自定义View 国际惯例先预览后实现 我们要实现的就是一个段位样式的拖动条,用来做筛选条件用的,细心的朋友可能会发现微信设置里面有个一个通用字体的设置,拖动然后改变字体大小; 这个相对比微信那个的自定义view算是一个扩展,因为我们是双向滑动,这个多考虑的一点就是手指拖动的是哪一个滑动块! 我们先看下GIF预览,然后我们今天就一步步实现这个小玩意… 实现步骤

  • Android中SeekBar拖动条控件使用方法详解

    SeekBar拖动条控件使用方法,具体内容如下 一.简介 1.  二.SeekBar拖动条控件使用方法 1.创建SeekBar控件 <SeekBar android:id="@+id/SeekBar1" android:layout_width="match_parent" android:layout_height="wrap_content" android:progress="30" /> 2.添加setOn

  • Android开发实现AlertDialog中View的控件设置监听功能分析

    本文实例讲述了Android开发实现AlertDialog中View的控件设置监听功能.分享给大家供大家参考,具体如下: 之前给弹出的AlertDialog中的控件设置监听时,老是报空指针异常,之所以报空指针异常,是因为我findViewById写的有问题,因为我们需要给弹出框中的控件设置监听,直接用findViewById是找不到弹出框中的控件的,需要利用Dialog.findViewById或者利用你找到的弹出框中的View,然后view.findViewById;具体看下面代码 packa

  • Android开发中DatePicker日期与时间控件实例代码

    一.简介 二.方法 最日常的使用方法了 日期控件DatePicker 时间控件TimePicker 月份从0开始 三.代码实例 效果图: 代码: fry.Activity01 package fry; import com.example.DatePicherDemo1.R; import android.app.Activity; import android.os.Bundle; import android.widget.DatePicker; import android.widget.

  • Android自定义View圆形进度条控件(三)

    继续练习自定义View,这次带来的圆形进度条控件与之前的圆形百分比控件大同小异,这次涉及到了渐变渲染以及画布旋转等知识点,效果如下: 虽然步骤类似,但是我还是要写,毕竟基础的东西就是要多练 1.在res/values文件夹下新建attrs.xml文件,编写自定义属性: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="Circ

  • Android利用Paint自定义View实现进度条控件方法示例

    前言 View的三大流程:测量,布局,绘制,自定义View学的是啥?无非就两种:绘制文字和绘制图像. 我们在上一篇文章<Android绘图之Paint的使用>中学习了Paint的基本用法,但是具体的应用我们还没有实践过.从标题中可知,本文是带领读者使用Paint,自定义一个进度条控件. 效果图 上图就是本文要实现的效果图. 实现过程 既然是自定义控件,本文的该控件是直接继承View,然后重写View的onMeasure和onDraw方法来实现.其中onMeasure主要作用是测量控件的宽/高.

  • Android开发之拖动条/滑动条控件、星级评分控件功能的实例代码

    ProgressBar有2个子控件: SeekBar   拖动条控件 RatingBar   星级评分控件 1.拖动条控件 <SeekBar android:layout_width="300dp" android:layout_height="wrap_content" android:id="@+id/seekBar" android:min="0" android:max="100" andro

  • Android的进度条控件描述

     一.ProgressBar进度条 在某项延续性工作的进展过程中为了不让用户觉得程序死掉了,需要有个活动的进度条,表示此过程正在进行中. 用户浏览网页的时候,中间肯定有个传输过程,所以用进度条让用户耐心等待.再比如在下载应用中,它也会有进度条显示下载进度. ProgressDialog是继承自Android.app.ProgressDialog所设计的互动对话窗口,应用时,必须新建ProgressDialog对象,运行时会弹出"对话框"作为提醒,此时应用程序后台失去焦点,直到进程结束后

  • Android高级界面组件之拖动条和评星条的功能实现

    一      拖动条 安卓手机音量设置都是给出一个拖动条,使得用户能够拖动滑块进行设置,这里我们介绍拖动条. 安卓拖动条控件是继承自ProgressBar控件,所以它能够支持ProgressBar的xml属性.但是他有自己的独特属性: android:max       设置最大的拖动两 android:progress     设置初始化进度 android:thumb        设置滑块图形 事件监听方面,拖动条需要注意:我们不在监听用户的点击操作,而是监听滑块的改变,下面用一个实例简单

  • Android开发手册SeekBar拖动条使用实例

    目录 实践过程 常用属性 基本使用 自定义样式 自定义样式 实践过程 常用属性 因为Seekbar继承自ProgressBar,所以ProgressBar支持的XML属性SeekBar都适用. [android:max="100"]:设置该进度条的最大值[android:progress="50"]:设置该进度条的已完成进度值[android:progressDrawable="@drawable/icon_xinsui"]:自定义drawabl

随机推荐