自定义区间滑动取值控件

自定义区间滑动取值控件主要涉及到的知识点有坐标系、画笔、画布以及自定义属性。因此,在自定义控件之前,先来了解一下相关的知识。

相关知识

关于坐标

关于坐标系,android中有两种,分别为Android坐标系和视图坐标系。

  1. Android坐标系:以手机屏幕左上角的顶点为坐标原点,从该点向右为x轴正方向,从该点向下为y轴正方向。而触控事件中,使用getRawX()和getRawY()方法。
  2. 视图坐标系:视图坐标系是以父视图的左上角为坐标原点的。相应的原点向右为x轴正方向,原点向下为y轴正方向。在触控中,通过getX()和getY()来获取的坐标值就是视图坐标系中的坐标值。

在自定义控件时,一定会涉及到控件的大小以及间距的值,而这些值又是如何得到的呢。下面我们来看看获取距离的几种方法。

  1. View提供的获取的坐标以及距离的方法:

    • getTop() :获取到的是view自身的顶边到其父布局顶边的距离
    • getLeft():获取到的是view自身的左边到其父布局左边的距离
    • getRight():获取到的是view自身的右边到其父布局左边的距离
    • getPaddingStart():获取控件的控件内容与控件左边缘的距离,Api17之前为getPaddingLeft()
    • getPaddingTop():获取控件的控件内容与控件底部边缘的距离
    • getPaddingEnd():获取控件的控件内容与控件右边缘的距离,Api17之前为getPaddingRight()
    • getPaddingBottom():获取控件的控件内容与控件底部边缘的距离

    2 . MotionEvent提供的方法(触发屏幕触碰事件时,在onTouchEvent回调方法中传入MotionEvent的参数):

    • getX():获取点击事件距离控件左边的距离,即视图坐标
    • getY():获取点击事件距离控件顶边的距离,即视图坐标
    • getRawX():获取到的是点击事件距离整个屏幕左边的距离,即绝对坐标
    • getRawY():获取到的是点击事件距离整个屏幕顶边的距离,即绝对坐标

PS:需要特别注意,Y轴的正方向是向下的。

画笔

画笔样式:

  • Paint.Style.FILL:填充内部
  • Paint.Style.FILL_AND_STROKE:填充内部和描边
  • Paint.Style.STROKE:描边

关于画笔,这里只说明一下我们用到的几个方法以及其参数。

  • android.graphics.Paint#setColor() 设置画笔颜色。
  • android.graphics.Paint#setStrokeWidth()画笔样式为描边(Paint.Style.STROKE)时,设置画笔的宽度。
  • android.graphics.Paint#setAntiAlias()设置抗锯齿,如果不设置,加载位图的时候可能会出现锯齿状的边界,如果设置,边界就会变的稍微有点模糊,锯齿就看不到了,传入true为设置抗锯齿。
  • android.graphics.Paint#setStyle()设置画笔样式,参数为Paint.Style的枚举类型。
  • android.graphics.Paint#setTextAlign()设置画笔绘制文本的对齐方式,参数为Paint.Align的枚举值,分别为:
    • Paint.Align.LEFT:文本被绘制到x,y原点的右边。
    • Paint.Align.CENTER:文本是在x,y原点上水平居中绘制的。
    • Paint.Align.RIGHT:文本被画在x,y原点的左边。

画布

Canvas的几个draw方法,这些方法都进行了不同形式的重载。

  • canvas.drawText()绘制文本
  • canvas.drawLine()绘制线条
  • canvas.drawCircle()绘制圆形
  • canvas.drawArc()画弧度
  • canvas.drawARGB()在整个画布中填充颜色,有四个参数,参数值0到255
  • canvas.drawBitmap()绘制位图
  • canvas.drawColor()在整个画布中填充颜色
  • canvas.drawOval() 画椭圆
  • canvas.drawRoundRect()绘制圆角矩形
  • canvas.drawRect()绘制矩形
  • canvas.drawPicture()绘制图片
  • canvas.drawPoint()画点
  • canvas.drawPath()根据指定路径(即由一组坐标组成)绘制图形

细说一下接下来要用的三个方法:

  1. drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint)

    • startX:线条的起始点的x坐标
    • startY:线条的起始点的y坐标
    • stopX:线条的终止点的x坐标
    • stopY:线条的终止点的y坐标
    • paint:绘制线条的画笔
  2. drawCircle(float cx, float cy, float radius, @NonNull Paint paint)

    • cx:圆心的x坐标
    • cy:圆心的y坐标
    • radius:圆半径
    • paint:绘制圆形的画笔
  3. drawText(@NonNull String text, float x, float y, @NonNull Paint paint)

    • text:将要绘制的文字
    • x:绘制文字起始点的x坐标
    • y:绘制文字起始点的y坐标
    • paint:绘制文字的画笔

Canvas还有几个应该关注的方法:

  • canvas.save():把当前的绘制的图像保存起来,让后续的操作相当于是在一个新的图层上的操作。
  • canvas.restore():把当前画布返回(调整)到上一个save()状态之前。
  • canvas.translate(dx, dy):把当前画布的原点移到(dx,dy),后面的操作都以(dx,dy)作为参照点,默认原点为(0,0)。
  • canvas.scale(x,y):放大。x为水平方向的放大倍数,y为竖直方向的放大倍数。
  • canvas.rotate(angel):旋转.angle指旋转的角度,顺时针旋转。
  • canvas.transform():切变。所谓切变,把图像的顶部或底部推到一边。
  • canvas.saveLayer(bounds, paint, saveFlags):保存图层。

View的几个关键方法

onMeasure(int widthMeasureSpec, int heightMeasureSpec):这两个参数widthMeasureSpec, heightMeasureSpec由ViewGroup中的layout_width,la

  • yout_height和padding以及View自身的layout_margin共同决定。权值weight也是尤其需要考虑的因素,有它的存在情况可能会稍微复杂点。
  • 这个方法主要是对控件的测量,通过重写这个方法,根据传入的两个参数,并调用MeasureSpec.getSize()方法,我们才能获得控件的宽高,如下

    • MeasureSpec.getSize(heightMeasureSpec);//获取总高度,是包含padding值
    • MeasureSpec.getSize(widthMeasureSpec);//获取总宽度,是包含padding值
  • onDraw(Canvas canvas):根据设置的画笔及控件的相关参数来绘制控件,对于canvas的相关操作,已在上面进行说明。

  • 还有一个onLayou()方法,在自定义控件时,也是很关键的,但是由于此次没有用到,因此就先不说明了。

View构造方法的参数

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}
  • 第一个属于程序内实例化时采用,之传入Context即可。
  • 第二个用于layout文件实例化,会把XML内的参数通过AttributeSet带入到View内。
  • 第三个主题的style信息,也会从XML里带入。
  • 从API21起,还有第四个参数,默认样式资源,这里传入样式资源id。

自定义属性

在res/valuse的attrs.xml中自定义属性,几个关键词的理解:

  • declare-styleable: 表示一个属性组。它的name必须和你自定义view的名字相同。
  • attr:表示单独的一个属性。
    • name是属性名称,会和属性组的name通过下划线拼接,来唯一标识一个属性。
    • format代表属性的格式。格式包括很多种:比如颜色,数值,枚举等。

format类型有:

  • string:支付窜
  • dimension:尺寸值
  • boolean:布尔值
  • float:浮点值
  • integer:整形值
  • color:颜色值
  • enum:枚举值
  • flag:位或运算
  • fraction:百分数
  • reference:参考某一资源ID

在自定义控件时,我们通过Context实例调用obtainStyledAttributes(android.util.AttributeSet, int[], int, int)方法得到TypedArray实例,该实例中有相应的方法来获取对应格式的属性值。在自定义控件的构造方法中去调用,最后还要调用TypedArray实例的recycle()方法来释放资源(注:TypeArray内部是通过一个静态方法来维护实例的,也就是说这一个典型的单例模式)。

//自定义属性

<resources>
    <declare-styleable name="RangeSelectionView">
        
        <attr name="backLineColor" format="color" />
        
        <attr name="connectLineColor" format="color" />
        
        <attr name="circleColor" format="color" />
        
        <attr name="whileCircleColor" format="color" />

        
        <attr name="startValueColor" format="color" />
        
        <attr name="endValueColor" format="color" />
        
        <attr name="resultValueColor" format="color" />
        
        <attr name="isShowResult" format="boolean" />
        
        <attr name="isInteger" format="boolean" />
        
        <attr name="valuePrecision" format="integer" />
        
        <attr name="startValue" format="float" />
        
        <attr name="endValue" format="float" />
        
        <attr name="leftUnit" format="string" />
        
        <attr name="rightUnit" format="string" />
    declare-styleable>
resources>

自定义控件

关键值

这个控件中,需要计算的几个关键值

  1. 基线的起始和终点坐标值;

    //无论是起点还是终点,Y轴的值均是控件高度的一半
    //起点的X轴:设定的边距值+控件的paddingStart
    //终点的x轴:控件宽度-设定的边距值-paddingEnd
  2. 起始和终点圆圈的坐标值;
  3. 起始和终点值的显示位置的坐标值;
  4. 顶部值显示位置的坐标值;
  5. 滑动进度值。

由于代码中已经有很明确的注释,因此对于所定义的变量和一些计算,在这里就不进行解释了。下面直接上代码。

控件代码

/**
 * Description:区间滑动取值控件
 * Created by Kevin.Li on 2018-01-11.
 */
public class RangeSelectionView extends View {
    private Paint paintBackground;//背景线的画笔
    private Paint paintCircle;//起始点圆环的画笔
    private Paint paintWhileCircle;//起始点内圈白色区域的画笔
    private Paint paintStartText;//起点数值的笔
    private Paint paintEndText;//终点数值的画笔
    private Paint paintResultText;//顶部结果数值的画笔
    private Paint paintConnectLine;//起始点连接线的画笔

    private int mHeight = 0;//控件的高度
    private int mWidth = 0;//控件的宽度

    private float centerVertical = 0;//y轴的中间位置

    private float backLineWidth = 5;//底线的宽度

    private float marginHorizontal = 1;//横向边距

    private float marginTop = 60;//文字距基线顶部的距离
    private float marginBottom = 40;//文字距基线底部的距离

    private float pointStart = 0;//起点的X轴位置

    private float pointEnd = 0;//始点的Y轴位置

    private float circleRadius = 30;//起始点圆环的半径

    private float numStart = 0;//数值的开始值

    private float numEnd = 0;//数值的结束值

    private int textSize = 35;//文字的大小

    private String strConnector = " - ";//连接符

    private boolean isRunning = false;//是否可以滑动
    /**
     * 起点还是终点 true:起点;false:终点。
     */
    private boolean isStart = true;
    private int pdStart;//控件padding值
    private int pdEnd;
    private float scaling;//取值比例

    /**
     * 进度值范围——起点值
     */
    private float startNum = 0.00F;
    /**
     * 进度值范围——终点值
     */
    private float endNum = 100.00F;
    /**
     * 左侧单位
     */
    private String leftUnit;
    /**
     * 右侧单位
     */
    private String rightUnit;
    /**
     * 是否保留整形
     */
    private boolean isInteger = false;
    /**
     * 保留精度,默认为2。
     */
    private int precision = 2;
    /**
     * 是否显示结果值,默认显示。
     */
    private boolean isShowResult = true;

    /**
     * 开始文字颜色
     */
    private int startValueColor;
    /**
     * 终点文字颜色
     */
    private int endValueColor;
    /**
     * 结果值文字颜色
     */
    private int resultValueColor;

    /**
     * 基线颜色
     */
    private int backLineColor;
    /**
     * 连接线颜色
     */
    private int connectLineColor;
    /**
     * 外圆填充色
     */
    private int circleColor;
    /**
     * 圆形填充色
     */
    private int whileCircleColor;

    private OnChangeListener mOnChangeListener;

    public RangeSelectionView(Context context) {
        super(context);
        init();
    }

    public RangeSelectionView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
        init();
    }

    public RangeSelectionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        handleAttrs(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 获取自定义属性的值
     */
    private void handleAttrs(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RangeSelectionView, defStyleAttr, 0);

        backLineColor = ta.getColor(R.styleable.RangeSelectionView_backLineColor, Color.CYAN);
        connectLineColor = ta.getColor(R.styleable.RangeSelectionView_connectLineColor, Color.BLUE);
        circleColor = ta.getColor(R.styleable.RangeSelectionView_circleColor, Color.BLUE);
        whileCircleColor = ta.getColor(R.styleable.RangeSelectionView_whileCircleColor, Color.WHITE);

        startValueColor = ta.getColor(R.styleable.RangeSelectionView_startValueColor, Color.MAGENTA);
        endValueColor = ta.getColor(R.styleable.RangeSelectionView_endValueColor, Color.MAGENTA);
        resultValueColor = ta.getColor(R.styleable.RangeSelectionView_resultValueColor, Color.MAGENTA);
        isShowResult = ta.getBoolean(R.styleable.RangeSelectionView_isShowResult, true);
        isInteger = ta.getBoolean(R.styleable.RangeSelectionView_isInteger, false);
        precision = ta.getInteger(R.styleable.RangeSelectionView_valuePrecision, 2);
        startNum = ta.getFloat(R.styleable.RangeSelectionView_startValue, startNum);
        endNum = ta.getFloat(R.styleable.RangeSelectionView_endValue, endNum);
        if (ta.getString(R.styleable.RangeSelectionView_leftUnit) != null) {
            leftUnit = ta.getString(R.styleable.RangeSelectionView_leftUnit);
        }
        if (ta.getString(R.styleable.RangeSelectionView_rightUnit) != null) {
            rightUnit = ta.getString(R.styleable.RangeSelectionView_rightUnit);
        }

        ta.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //获取控件的宽高、中线位置、起始点、起始数值
        mHeight = MeasureSpec.getSize(heightMeasureSpec);//获取总高度,是包含padding值
        mWidth = MeasureSpec.getSize(widthMeasureSpec);//获取总宽度,是包含padding值
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            pdStart = getPaddingStart();
            pdEnd = getPaddingEnd();
        } else {
            pdStart = getPaddingLeft();
            pdEnd = getPaddingRight();
        }

        centerVertical = mHeight / 2;

        pointStart = marginHorizontal + pdStart + circleRadius;
        pointEnd = mWidth - marginHorizontal - pdEnd - circleRadius;
        initBaseData();
    }

    /**
     * 初始化基础值
     */
    private void initBaseData() {
        // (父级控件宽度-左右边距-圆直径)/(结束值-起点值)
        scaling = (mWidth - 2 * marginHorizontal - (pdStart + pdEnd) - 2 * circleRadius) / (endNum - startNum);

        numStart = getProgressNum(pointStart);
        numEnd = getProgressNum(pointEnd);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //如果点击的点在第一个圆内就是起点,并且可以滑动
                if (event.getX() >= (pointStart - circleRadius) && event.getX() <= (pointStart + circleRadius)) {
                    isRunning = true;
                    isStart = true;

                    pointStart = event.getX();
                    //如果点击的点在第二个圆内就是终点,并且可以滑动
                } else if (event.getX() <= (pointEnd + circleRadius) && event.getX() >= (pointEnd - circleRadius)) {
                    isRunning = true;
                    isStart = false;

                    pointEnd = event.getX();
                } else {
                    //如果触控点不在圆环内,则不能滑动
                    isRunning = false;
                }

                break;
            case MotionEvent.ACTION_MOVE:
                if (isRunning) {
                    if (isStart) {
                        //起点滑动时,重置起点的位置和进度值
                        pointStart = event.getX();
                        if (pointStart < marginHorizontal + pdStart + circleRadius) {
                            pointStart = marginHorizontal + pdStart + circleRadius;
                            numStart = startNum;
                        } else {
                            if (pointStart + circleRadius < pointEnd - circleRadius) {//防止起点不动而值增加的问题
                                numStart = getProgressNum(pointStart);
                            }
                        }
                    } else {
                        //始点滑动时,重置始点的位置和进度值
                        pointEnd = event.getX();
                        if (pointEnd > mWidth - marginHorizontal - pdEnd - circleRadius) {
                            pointEnd = mWidth - marginHorizontal - pdEnd - circleRadius;
                            numEnd = endNum;
                        } else {
                            if (pointEnd < marginHorizontal + pdStart + 3 * circleRadius) {//防止终点和起点在起始点相连时,往左移动,终点不动,而值减小的问题。
                                pointEnd = marginHorizontal + pdStart + 3 * circleRadius;
                            }
                            numEnd = getProgressNum(pointEnd);
                        }
                    }

                    flushState();//刷新状态
                }

                break;
            case MotionEvent.ACTION_UP:

                flushState();
                break;
        }

        return true;
    }

    /**
     * 刷新状态和屏蔽非法值
     */
    private void flushState() {

        //起点非法值
        if (pointStart < marginHorizontal + pdStart + circleRadius) {
            pointStart = marginHorizontal + pdStart + circleRadius;
        }
        //终点非法值
        if (pointEnd > mWidth - marginHorizontal - pdEnd - circleRadius) {
            pointEnd = mWidth - marginHorizontal - pdEnd - circleRadius;
        }

        //防止起点位置大于终点位置(规定:如果起点位置大于终点位置,则将起点位置放在终点位置前面,即:终点可以推着起点走,而起点不能推着终点走)
        if (pointStart + circleRadius > pointEnd - circleRadius) {
            pointStart = pointEnd - 2 * circleRadius;
            numStart = getProgressNum(pointStart);//更新起点值
        }

        //防止终点把起点推到线性范围之外
        if (pointEnd < marginHorizontal + pdStart + 3 * circleRadius) {
            pointEnd = marginHorizontal + pdStart + 3 * circleRadius;
            pointStart = marginHorizontal + pdStart + circleRadius;
        }

        invalidate();//这个方法会导致onDraw方法重新绘制
        if (mOnChangeListener != null) {// call back listener.
            mOnChangeListener.leftCursor(String.valueOf(numStart));
            mOnChangeListener.rightCursor(String.valueOf(numEnd));
        }
    }

    /**
     * 根据屏幕像素值计算进度数值
     */
    private float getProgressNum(float progress) {
        if (progress == marginHorizontal + pdStart + circleRadius) {// 处理边界问题,起始值
            return startNum;
        }
        if (progress == mWidth - marginHorizontal - pdEnd - circleRadius) {// 处理边界问题,终止值
            return endNum;
        }
        // (坐标点-左边距-圆半径)/比例 + 起始值
        return (progress - marginHorizontal - pdEnd - circleRadius) / scaling + startNum;
    }

    /**
     * 初始化画笔
     */
    private void init() {

        paintBackground = new Paint();
        paintBackground.setColor(backLineColor);
        paintBackground.setStrokeWidth(backLineWidth);
        paintBackground.setAntiAlias(true);

        paintCircle = new Paint();
        paintCircle.setColor(circleColor);
        paintCircle.setStrokeWidth(backLineWidth);
        paintCircle.setStyle(Paint.Style.STROKE);
        paintCircle.setAntiAlias(true);

        paintWhileCircle = new Paint();
        paintWhileCircle.setColor(whileCircleColor);
        paintCircle.setStyle(Paint.Style.FILL);
        paintWhileCircle.setAntiAlias(true);

        paintStartText = new Paint();
        paintStartText.setColor(startValueColor);
        paintStartText.setTextSize(textSize);
        paintStartText.setAntiAlias(true);

        paintEndText = new Paint();
        paintEndText.setColor(endValueColor);
        paintEndText.setTextSize(textSize);
        paintEndText.setAntiAlias(true);
        paintEndText.setTextAlign(Paint.Align.RIGHT);

        paintResultText = new Paint();
        paintResultText.setColor(resultValueColor);
        paintResultText.setTextSize(textSize);
        paintResultText.setAntiAlias(true);

        paintConnectLine = new Paint();
        paintConnectLine.setColor(connectLineColor);
        paintConnectLine.setStrokeWidth(backLineWidth + 5);
        paintConnectLine.setAntiAlias(true);

    }

    @Override
    protected void onDraw(Canvas canvas) {
//        super.onDraw(canvas);

        //背景线
        //无论是起点还是终点,Y轴的值均是控件高度的一半
        //起点的X轴:设定的边距值+控件的paddingStart
        //终点的x轴:控件宽度-设定的边距值-paddingEnd
        canvas.drawLine(marginHorizontal + pdStart, centerVertical, mWidth - marginHorizontal - pdEnd, centerVertical, paintBackground);
        //起点位置的外圈圆
        canvas.drawCircle(pointStart, centerVertical, circleRadius, paintCircle);
        //起点位置的内圈圆
        canvas.drawCircle(pointStart, centerVertical, circleRadius - backLineWidth, paintWhileCircle);
        //终点位置的外圈圆
        canvas.drawCircle(pointEnd, centerVertical, circleRadius, paintCircle);
        //终点位置的内圈圆
        canvas.drawCircle(pointEnd, centerVertical, circleRadius - backLineWidth, paintWhileCircle);
        //起始点连接线
        canvas.drawLine(pointStart + circleRadius, centerVertical, pointEnd - circleRadius, centerVertical, paintConnectLine);
        //起点数值
        canvas.drawText(assembleStartText(), pointStart - circleRadius, centerVertical + marginBottom + circleRadius, paintStartText);
        //终点数值
        canvas.drawText(assembleEndText(), pointEnd + circleRadius, centerVertical + marginBottom + circleRadius, paintEndText);
        if (isShowResult) {
            //结果值
            canvas.drawText(assembleResultText(), marginHorizontal + pdStart, centerVertical - marginTop, paintResultText);
        }
    }

    /**
     * 处理起点值精度
     */
    private float handleNumStartPrecision(float value) {
        BigDecimal bd = new BigDecimal(value);
        bd = bd.setScale(precision, BigDecimal.ROUND_HALF_UP);
        return bd.floatValue();
    }

    /**
     * 处理终点值精度
     */
    private float handleNumEndPrecision(float value) {
        BigDecimal bd = new BigDecimal(value);
        bd = bd.setScale(precision, BigDecimal.ROUND_HALF_DOWN);
        return bd.floatValue();
    }

    /**
     * 组装起点文字
     */
    private String assembleStartText() {
        StringBuilder sb = new StringBuilder();
        if (!TextUtils.isEmpty(leftUnit)) sb.append(leftUnit);
        //必须在此调用String.valueOf()来提前转化为String,否则会因为append()重载而导致整形参数无效的问题。
        sb.append(isInteger ? String.valueOf((int) numStart) : String.valueOf(handleNumStartPrecision(numStart)));
        if (!TextUtils.isEmpty(rightUnit)) sb.append(" ").append(rightUnit);
        return sb.toString();
    }

    /**
     * 组装终点文字
     */
    private String assembleEndText() {
        StringBuilder sb = new StringBuilder();
        if (!TextUtils.isEmpty(leftUnit)) sb.append(leftUnit);
        sb.append(isInteger ? String.valueOf((int) numEnd) : handleNumEndPrecision(numEnd));
        if (!TextUtils.isEmpty(rightUnit)) sb.append(" ").append(rightUnit);
        return sb.toString();
    }

    /**
     * 组装结果值
     */
    private String assembleResultText() {
        StringBuilder sb = new StringBuilder();
        if (!TextUtils.isEmpty(leftUnit)) sb.append(leftUnit);
        sb.append(isInteger ? String.valueOf((int) numStart) : handleNumStartPrecision(numStart));
        if (!TextUtils.isEmpty(rightUnit)) sb.append(" ").append(rightUnit);
        sb.append(strConnector);
        if (!TextUtils.isEmpty(leftUnit)) sb.append(leftUnit);
        sb.append(isInteger ? String.valueOf((int) numEnd) : handleNumEndPrecision(numEnd));
        if (!TextUtils.isEmpty(rightUnit)) sb.append(" ").append(rightUnit);
        return sb.toString();
    }

    /**
     * 左侧单位
     */
    public RangeSelectionView setLeftUnit(String leftUnit) {
        this.leftUnit = leftUnit;
        return this;
    }

    /**
     * 右侧单位
     */
    public RangeSelectionView setRightUnit(String rightUnit) {
        this.rightUnit = rightUnit;
        return this;
    }


    /**
     * 是否保留整形
     */
    public RangeSelectionView setInteger(boolean integer) {
        isInteger = integer;
        return this;
    }

    /**
     * 是否显示结果值,默认显示。
     */
    public RangeSelectionView setShowResult(boolean showResult) {
        isShowResult = showResult;
        return this;
    }

    /**
     * 保留精度,默认为2。
     */
    public RangeSelectionView setPrecision(int precision) {
        this.precision = precision;
        return this;
    }

    /**
     * 起始值
     */
    public RangeSelectionView setStartNum(float startNum) {
        this.startNum = startNum;
        return this;
    }

    /**
     * 结束值
     */
    public RangeSelectionView setEndNum(float endNum) {
        this.endNum = endNum;
        return this;
    }

    /**
     * 开始文字颜色
     */
    public RangeSelectionView setStartValueColor(int startValueColor) {
        this.startValueColor = startValueColor;
        return this;
    }

    /**
     * 终点文字颜色
     */
    public RangeSelectionView setEndValueColor(int endValueColor) {
        this.endValueColor = endValueColor;
        return this;
    }

    /**
     * 结果值文字颜色
     */
    public RangeSelectionView setResultValueColor(int resultValueColor) {
        this.resultValueColor = resultValueColor;
        return this;
    }

    /**
     * 基线颜色
     */
    public RangeSelectionView setBackLineColor(int backLineColor) {
        this.backLineColor = backLineColor;
        return this;
    }

    /**
     * 连接线颜色
     */
    public RangeSelectionView setConnectLineColor(int connectLineColor) {
        this.connectLineColor = connectLineColor;
        return this;
    }

    /**
     * 外圆填充色
     */
    public RangeSelectionView setCircleColor(int circleColor) {
        this.circleColor = circleColor;
        return this;
    }

    /**
     * 圆形填充色
     */
    public RangeSelectionView setWhileCircleColor(int whileCircleColor) {
        this.whileCircleColor = whileCircleColor;
        return this;
    }

    /**
     * 通知刷新。
     * 在调用系列setXxx方法之后,需要调用此方法,才会生效。
     */
    public void notifyRefresh() {
        init();
        initBaseData();
        invalidate();//这个方法会导致onDraw方法重新绘制
    }

    /**
     * 主要充值起点和终点的画笔值
     */
    public void reSetValue() {
        pointStart = marginHorizontal + pdStart + circleRadius;
        pointEnd = mWidth - marginHorizontal - pdEnd - circleRadius;
    }

    public void setOnChangeListener(OnChangeListener onChangeListener) {
        mOnChangeListener = onChangeListener;
    }

    /**
     * 值变化监听器
     */
    public interface OnChangeListener {
        /**
         * 起点进度值变化回调
         */
        void leftCursor(String resultValue);

        /**
         * 终点进度值变化回调
         */
        void rightCursor(String resultValue);
    }
}

调用代码

"http://schemas.android.com/apk/res-auto"
    android:id="@+id/rsv_view"
    android:layout_width="match_parent"
    android:layout_height="120dp"
    android:background="@android:color/white"
    android:visibility="visible"
    rsv:backLineColor="@color/blue_transparent_background_70"
    rsv:circleColor="@color/blue_transparent_background_70"
    rsv:connectLineColor="@color/auxiliary_blue"
    rsv:endValue="1.0"
    rsv:endValueColor="@color/warn"
    rsv:isInteger="true"
    rsv:leftUnit="@string/monetary_unit_rmb"
    rsv:resultValueColor="@color/danger"
    rsv:startValue="10.0"
    rsv:startValueColor="@color/warn"
    rsv:whileCircleColor="@color/white" />
//java代码调用
final RangeSelectionView rsv;
rsv = (RangeSelectionView) findViewById(R.id.rsv_view);
rsv.setBackLineColor(Color.RED);
rsv.setResultValueColor(Color.DKGRAY);
rsv.setStartNum(5);
rsv.setEndNum(20);
rsv.notifyRefresh();

效果一览

自定义区间滑动取值控件_第1张图片


Demo源码

Github:https://github.com/ysg-lijinwen/IntervalValueControl.git

相关知识详细介绍

Android Paint的使用详解:Android Paint的使用详解
Android自定义属性之format解析:Android自定义属性之format解析
Android自定义View(三、深入解析控件测量onMeasure):Android自定义View(三、深入解析控件测量onMeasure)

你可能感兴趣的:(自定义控件,基础理论)