价格区间挑选的控件完成

前语

之前咱们的温习中,咱们现已对原生 Canvas 的制作有了详细的了解,咱们对事情的处理也有了简略的了解,这一期咱们就对制作与事情的处理做更进一步的完成。

如图,咱们需求做这么一个区间的挑选控件,此控件也是咱们常用的控件,在一些筛选页面,依据价格,数值进行一些筛选的时分,咱们需求设置一个最小值和一个最大值。然后取一段中心的区间值。

而这个控件的完成便是典型的自界说制作与自界说事情处理的标志性完成。我愿称之为自界说View的筑基操练,假如咱们能自始至终完成一遍,那么对自界说流程基本上现已轻车熟路了。

常规咱们剖析一下完成步骤:

  1. 左面右边的操控圆分别完成,虽然一般情况下它们的特点都是相同的,但是为了避免左右不同的圆,咱们做好兼容处理。
  2. 中心的圆角矩形进展条,咱们也分为默许的底色和选中的色彩。
  3. 对事情的处理,左右的圆形控件的移动处理。
  4. 其他的文本显现。
  5. 自界说特点的抽取与回调处理。

思路咱们现已有了,下面一步一步的来完成吧! Let’s go

Android自定义View交互进阶,价格区间选择控件

1、制作静态的图形

关于静态的作用制作,咱们现已轻车熟路了。 丈量,画笔,矩阵的初始化,制作,一套流程下来,都现已是固定的模板了。

进展矩形条,左右圆形的一些资源,咱们就能完成一个静态的制作。

需求界说的变量如下:

    private int mRangLineHeight = getResources().getDimensionPixelSize(R.dimen.d_4dp);  //圆角矩形线的高度
    private int mRangLineCornerRadius;   //圆角矩形线的圆角半径
    private int mRangLineDefaultColor = Color.parseColor("#CDCDCD");  //默许色彩
    private int mRangLineCheckedColor = Color.parseColor("#0689FD");  //选中色彩
    private int mCircleRadius = getResources().getDimensionPixelSize(R.dimen.d_14dp); //圆半径
    private int mCircleStrokeWidth = getResources().getDimensionPixelSize(R.dimen.d_1d5dp); //圆边框的巨细
    private int mLeftCircleBGColor = Color.parseColor("#0689FD");  //左面实心圆色彩
    private int mLeftCircleStrokeColor = Color.parseColor("#FFFFFF");  //左面圆边框的色彩
    private int mRightCircleBGColor = Color.parseColor("#0689FD");  //右边实心圆色彩
    private int mRightCircleStrokeColor = Color.parseColor("#FFFFFF");  //右边圆边框的色彩
    private float mLeftCircleCenterX;    //左右两个圆心方位
    private float mLeftCircleCenterY;
    private float mRightCircleCenterX;
    private float mRightCircleCenterY;
    private RectF mDefaultCornerLineRect;      //默许色彩的圆角矩形
    private RectF mSelectedCornerLineRect;     //选中色彩的圆角矩形
    private Paint mLeftCirclePaint;        //各种画笔
    private Paint mLeftCircleStrokePaint;
    private Paint mRightCirclePaint;
    private Paint mRightCircleStrokePaint;
    private Paint mDefaultLinePaint;
    private Paint mSelectedLinePaint;

画笔与Rect的初始化:

 private void initPaint() {
        //初始化左面实心圆
        mLeftCirclePaint = new Paint();
        mLeftCirclePaint.setAntiAlias(true);
        mLeftCirclePaint.setDither(true);
        mLeftCirclePaint.setStyle(Paint.Style.FILL);
        mLeftCirclePaint.setColor(mLeftCircleBGColor);
        //初始化左面圆的边框
        mLeftCircleStrokePaint = new Paint();
        mLeftCircleStrokePaint.setAntiAlias(true);
        mLeftCircleStrokePaint.setDither(true);
        mLeftCircleStrokePaint.setStyle(Paint.Style.STROKE);
        mLeftCircleStrokePaint.setColor(mLeftCircleStrokeColor);
        mLeftCircleStrokePaint.setStrokeWidth(mCircleStrokeWidth);
        //初始化右边实心圆
        mRightCirclePaint = new Paint();
        mRightCirclePaint.setAntiAlias(true);
        mRightCirclePaint.setDither(true);
        mRightCirclePaint.setStyle(Paint.Style.FILL);
        mRightCirclePaint.setColor(mRightCircleBGColor);
        //初始化右边圆的边框
        mRightCircleStrokePaint = new Paint();
        mRightCircleStrokePaint.setAntiAlias(true);
        mRightCircleStrokePaint.setDither(true);
        mRightCircleStrokePaint.setStyle(Paint.Style.STROKE);
        mRightCircleStrokePaint.setColor(mRightCircleStrokeColor);
        mRightCircleStrokePaint.setStrokeWidth(mCircleStrokeWidth);
        //默许色彩的圆角矩形线
        mDefaultCornerLineRect = new RectF();
        //中心选中色彩的圆角矩形
        mSelectedCornerLineRect = new RectF();
        mDefaultLinePaint = new Paint();
        mDefaultLinePaint.setAntiAlias(true);
        mDefaultLinePaint.setDither(true);
        mSelectedLinePaint = new Paint();
        mSelectedLinePaint.setAntiAlias(true);
        mSelectedLinePaint.setDither(true);
    }

关于丈量仍是按咱们前面文字介绍说的来,咱们先确认丈量的形式与宽高,再核算一个最小的宽高,然后依据xml里面界说的丈量形式来确认丈量的宽高。

详细完成如下:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int finalWidth, finalHeight;
        //核算的宽度与高度
        int calWidthSize = getPaddingLeft() + mCircleRadius * 2 + getPaddingRight() + mCircleStrokeWidth * 2;
        int calHeightSize = getPaddingTop() + mCircleRadius * 2 + mCircleStrokeWidth * 2 + getPaddingBottom();
        if (widthMode == MeasureSpec.EXACTLY) {
            //假如是精确形式运用丈量的宽度
            finalWidth = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            //假如是WrapContent运用核算的宽度
            finalWidth = Math.min(widthSize, calWidthSize);
        } else {
            //其他形式运用核算的宽度
            finalWidth = calWidthSize;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            //假如是精确形式运用丈量的高度
            finalHeight = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            //假如是WrapContent运用核算的高度
            finalHeight = Math.min(heightSize, calHeightSize);
        } else {
            //其他形式运用核算的高度
            finalHeight = calHeightSize;
        }
        //确认丈量宽高
        setMeasuredDimension(finalWidth, finalHeight);
    }

内部有详细的注释,引荐咱们宽度运用固定的数组,高度wrap_content。

丈量完成之后当显现出来了,咱们就能够对圆形和矩阵进行一些赋值操作。

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //左面圆的圆心坐标
        mLeftCircleCenterX = getPaddingLeft() + strokeRadius;
        mLeftCircleCenterY = getPaddingTop() /*+ rectDialogHeightAndSpace */ + strokeRadius;
        //右边圆的圆心坐标
        mRightCircleCenterX = w - getPaddingRight() - strokeRadius;
        mRightCircleCenterY = getPaddingTop() /*+ rectDialogHeightAndSpace */ + strokeRadius;
        //默许圆角矩形进展条
        mRangLineCornerRadius = mRangLineHeight / 2;//圆角半径
        mDefaultCornerLineRect.left = getPaddingLeft() + strokeRadius;
        mDefaultCornerLineRect.top = getPaddingTop() /*+ rectDialogHeightAndSpace */ + strokeRadius - mRangLineCornerRadius;
        mDefaultCornerLineRect.right = w - getPaddingRight() - strokeRadius;
        mDefaultCornerLineRect.bottom = getPaddingTop() /*+ rectDialogHeightAndSpace */ + strokeRadius + mRangLineCornerRadius;
        //选中状态圆角矩形进展条
        mSelectedCornerLineRect.left = mLeftCircleCenterX;
        mSelectedCornerLineRect.top = getPaddingTop() /*+ rectDialogHeightAndSpace */ + strokeRadius - mRangLineCornerRadius;
        mSelectedCornerLineRect.right = mRightCircleCenterX;
        mSelectedCornerLineRect.bottom = getPaddingTop() /*+ rectDialogHeightAndSpace */ + strokeRadius + mRangLineCornerRadius;
    }

咱们确认了圆角进展矩形线条的rect,和左右约束圆的圆心和巨细,咱们就能够运用对的画笔进行制作出静态的数据来。

    //左面的操控圆与边框
    private void drawLeftCircle(Canvas canvas) {
        //实心圆
        canvas.drawCircle(mLeftCircleCenterX, mLeftCircleCenterY, mCircleRadius, mLeftCirclePaint);
        //空心圆
        canvas.drawCircle(mLeftCircleCenterX, mLeftCircleCenterY, mCircleRadius, mLeftCircleStrokePaint);
    }
    //右侧的操控圆与边框
    private void drawRightCircle(Canvas canvas) {
        //实心圆
        canvas.drawCircle(mRightCircleCenterX, mRightCircleCenterY, mCircleRadius, mRightCirclePaint);
        //空心圆
        canvas.drawCircle(mRightCircleCenterX, mRightCircleCenterY, mCircleRadius, mRightCircleStrokePaint);
    }
    //中心的圆角矩形进展条-默许的底色
    private void drawDefaultCornerRectLine(Canvas canvas) {
        mDefaultLinePaint.setColor(mRangLineDefaultColor);
        canvas.drawRoundRect(mDefaultCornerLineRect, mRangLineCornerRadius, mRangLineCornerRadius, mDefaultLinePaint);
    }
    //中心的圆角矩形进展条-现已选中的色彩
    private void drawSelectedRectLine(Canvas canvas) {
        mSelectedLinePaint.setColor(mRangLineCheckedColor);
        canvas.drawRoundRect(mSelectedCornerLineRect, mRangLineCornerRadius, mRangLineCornerRadius, mSelectedLinePaint);
    }

这几个东西制作出来,咱们的作用就如下所示:

Android自定义View交互进阶,价格区间选择控件

为了便利显现巨细,我在控件里加一个灰色的布景为了便利观看整个控件的巨细。

静态的完成之后咱们就要开端让两头的约束圆形动起来。

2、让两头的约束圆动起来

咱们在 onDraw 的办法中能够得知,动态的成员变量便是两个圆的X轴坐标即为 mLeftCircleCenterX 和 mRightCircleCenterX ,那么中心的进展线条的制作则是依据 mSelectedCornerLineRect 的矩阵来制作的。矩阵的left 和 right 也是依据 mLeftCircleCenterX 和 mRightCircleCenterX 来核算的。

所以咱们的最终意图便是动态的记录当时事情中的 mLeftCircleCenterX 和 mRightCircleCenterX 值,但是有左右两个操控圆,咱们怎么判别移动的是哪一个圆呢?

先上一个判别办法。

     /**
     * 判别当时移动的是左面约束圆,仍是右侧约束圆
     *
     * @param downX 按下的坐标点
     * @return true表示按下的左面,false表示按下的右侧
     */
    private boolean checkTouchCircleLeftOrRight(float downX) {
        //用一个取巧的办法,假如当时按下的为X坐标,那么左面圆心X的坐标减去按下的X坐标,假如大于右侧的圆心X减去X坐标,那么就阐明在左面,否则就在右侧
        return !(Math.abs(mLeftCircleCenterX - downX) - Math.abs(mRightCircleCenterX - downX) > 0);
    }

当咱们移动的时分咱们怎么核算呢?一般常用的办法是把进展线条分为几份,核算每一份的长度。咱们把这些变量提取出来:

    private int mStrokeRadius;  //半径+边框的总值
    private int slice = 5; //代表全体进展分为多少份
    private float perSlice;   //每一份所占的长度
    private int maxValue = 100;  //最大值,默许为100
    private int minValue = 0;    //最小值,默许为0
    private float downX;
    private boolean touchLeftCircle;

经过进口办法对其赋值,而且显现出来后对每一份长度进行核算:


    /**
     * 设置数据与回调处理
     */
    public void setupData(int minValue, int maxValue, int sliceValue) {
        this.minValue = minValue;
        this.maxValue = maxValue;
        int num = (maxValue - minValue) / sliceValue;
        slice = (maxValue - minValue) % sliceValue == 0 ? num : num + 1;
        invalidate();
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        int realWidth = w - getPaddingLeft() - getPaddingRight();
        mStrokeRadius = mCircleRadius + mCircleStrokeWidth;
        //核算每一份对应的间隔
        perSlice = (realWidth - mStrokeRadius * 2) * 1f / slice;

处处咱们就能写OnTouch办法了,这是核心办法,咱们慢一点来。

咱们先只对按下的事情做处理:


 @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            //按下的时分记录当时操作的是左面约束圆仍是右侧的约束圆
            downX = event.getX();
            touchLeftCircle = checkTouchCircleLeftOrRight(downX);
            if (touchLeftCircle) {
                //假如是左面
                //假如超越右侧最大值则不处理
                if (downX + perSlice > mRightCircleCenterX) {
                    return false;
                }
                mLeftCircleCenterX = downX;
            } else {
                //假如是右侧
                //假如超越左面最小值则不处理
                if (downX - perSlice < mLeftCircleCenterX) {
                    return false;
                }
                mRightCircleCenterX = downX;
            }
        } 
        //中心的进展矩形是依据两头圆心点动态核算的
        mSelectedCornerLineRect.left = mLeftCircleCenterX;
        mSelectedCornerLineRect.right = mRightCircleCenterX;
        //悉数的事情处理完毕,变量赋值完成之后,开端重绘
        invalidate();
        return true;
    }

按下的过程中对,最大最小值做判别,而且赋值进展矩阵的 left 和 right ,那么咱们就能完成指定的点击作用,如下图所示:

Android自定义View交互进阶,价格区间选择控件

这只是点击呢,作用太挫了,咱们想要按着滑动怎么办?那咱们就需求重写Move事情。

3、动态滑动并核算当时的区间值

滑动相对来说是比较难得,咱们要处理两个约束圆的翻滚,而且当它们两个圆碰撞在一起的时分,咱们要处理交流的逻辑,而且还需求留意滑动边界的处理。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            //按下的时分记录当时操作的是左面约束圆仍是右侧的约束圆
            downX = event.getX();
            touchLeftCircle = checkTouchCircleLeftOrRight(downX);
            if (touchLeftCircle) {
                //假如是左面
                //假如超越右侧最大值则不处理
                if (downX + perSlice > mRightCircleCenterX) {
                    return false;
                }
                mLeftCircleCenterX = (int) downX;
            } else {
                //假如是右侧
                //假如超越左面最小值则不处理
                if (downX - perSlice < mLeftCircleCenterX) {
                    return false;
                }
                mRightCircleCenterX = downX;
            }
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            float moveX = event.getX();
            if (mLeftCircleCenterX + perSlice > mRightCircleCenterX) {
                //两圆重合的情况下的处理
                if (touchLeftCircle) {
                    // 左面到最右边
                    if (mLeftCircleCenterX == getWidth() - getPaddingRight() - mStrokeRadius) {
                        touchLeftCircle = true;
                        mLeftCircleCenterX = getWidth() - getPaddingRight() - mStrokeRadius;
                    } else {
                        //交流右侧滑动
                        touchLeftCircle = false;
                        mRightCircleCenterX = (int) moveX;
                    }
                } else {
                    //右侧到最左面
                    if (mRightCircleCenterX == getPaddingLeft() + mStrokeRadius) {
                        touchLeftCircle = false;
                        mRightCircleCenterX = getPaddingLeft() + mStrokeRadius;
                    } else {
                        //交流左面滑动
                        touchLeftCircle = true;
                        mLeftCircleCenterX = (int) moveX;
                    }
                }
            } else {
                //假如是正常的移动
                if (touchLeftCircle) {
                    //滑动左面约束圆,假如左面圆超越右边圆,那么把右边圆赋值给左面圆,假如没超越就赋值当时的moveX
                    mLeftCircleCenterX = mLeftCircleCenterX - mRightCircleCenterX >= 0 ? mRightCircleCenterX : moveX;
                } else {
                    //滑动右边约束圆,假如右边圆超越左面圆,那么把左面圆赋值给右边圆,假如没超越就赋值当时的moveX
                    mRightCircleCenterX = mRightCircleCenterX - mLeftCircleCenterX <= 0 ? mLeftCircleCenterX : moveX;
                }
            }
        } 
        //对一切的手势作用进行过滤操作,不能超越最大最小值
        limitMinAndMax();
        //中心的进展矩形是依据两头圆心点动态核算的
        mSelectedCornerLineRect.left = mLeftCircleCenterX;
        mSelectedCornerLineRect.right = mRightCircleCenterX;
        //悉数的事情处理完毕,变量赋值完成之后,开端重绘
        invalidate();
        return true;
    }

首要需求处理的是交流身位的办法,当两个圆相撞的时分,需求赋值处理,交流X的赋值,然后切换 touchLeftCircle 的值,然后对另一个圆进行移动。

需求留意的是咱们必定要在赋值之前对最大值与最小值进行校验,避免滑到天边去了。


    private void limitMinAndMax() {
        //假如是操作的左面约束圆,超越最小值了
        if (mLeftCircleCenterX < getPaddingLeft() + mStrokeRadius) {
            mLeftCircleCenterX = getPaddingLeft() + mStrokeRadius;
        }
        //假如是操作的左面约束圆,超越最大值了
        if (mLeftCircleCenterX > getWidth() - getPaddingRight() - mStrokeRadius) {
            mLeftCircleCenterX = getWidth() - getPaddingRight() - mStrokeRadius;
        }
        //假如是操作的右侧约束圆,超越最大值了
        if (mRightCircleCenterX > getWidth() - getPaddingRight() - mStrokeRadius) {
            mRightCircleCenterX = getWidth() - getPaddingRight() - mStrokeRadius;
        }
        //假如是操作的右侧约束圆,超越最小值了
        if (mRightCircleCenterX < getPaddingLeft() + mStrokeRadius) {
            mRightCircleCenterX = getPaddingLeft() + mStrokeRadius;
        }
    }

此刻大致的作用现已出来了,作用如图:

Android自定义View交互进阶,价格区间选择控件

4、核算当时值与回调处理

咱们一直都是核算的是两个圆的中心点X轴的核算,那么咱们真实选中的值是多少呢?总不能把X轴坐标给调用者吧。所以咱们需求经过滑动的百分比动态的核算详细的值。

    //依据移动的间隔核算当时的值
    private int getPercentMax(float distance) {
        //核算此刻的方位坐标对应的间隔能分多少份
        int lineLength = getWidth() - getPaddingLeft() - getPaddingRight() - mStrokeRadius * 2;
        distance = distance <= 0 ? 0 : (distance >= lineLength ? lineLength : distance);
        //核算滑动的百分比
        float percentage = distance / lineLength;
        return (int) (percentage * maxValue);
    }

那咱们需求在Move事情中一直回调吗?没必要,咱们在撤销的时分,或者说事情完毕的时分,当用户选好了区间之后,咱们回调一次即可。

    if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
        //核算当时的左面右侧真实的约束值
        int moveLeftData = getPercentMax(mLeftCircleCenterX - getPaddingLeft() - mStrokeRadius);
        int moveRightData = getPercentMax(mRightCircleCenterX - getPaddingLeft() - mStrokeRadius);
        //趁便赋值当时的真实值,便于后边的回调
       int leftValue = Math.min(moveLeftData, maxValue);
       int rightValue = Math.min(moveRightData, maxValue);
        if (mListener != null) mListener.onMoveValue(leftValue, rightValue);
    }
    //回调区间值的监听
    private OnRangeValueListener mListener;
    public interface OnRangeValueListener {
        void onMoveValue(int leftValue, int rightValue);
    }

咱们在Activity中经过setup办法就能够设置值并监听到最后的区间事情

    override fun init() {
        findViewById<RangeView>(R.id.range_view).setupData(0, 100, 1) { leftValue, rightValue ->
            toast("leftValue:$leftValue rightValue:$rightValue")
        }
    }

作用:

Android自定义View交互进阶,价格区间选择控件

5、实时文本显现与后续的扩展

这么看起来却是似模似样了,咱们的需求是在拖动的时分实时在顶部展现一个弹窗,展现当时的值,这怎么搞?

其实便是在顶部制作一个圆角矩形,在矩形内部制作文本,然后咱们经过左右约束圆的方位核算出中心 的方位,让顶部的圆角矩形在中心方位显现不就行了嘛。开干

先界说需求用到的成员变量:

    private int mTopDialogTextSize = getResources().getDimensionPixelSize(R.dimen.d_12dp);  //顶部文字的巨细
    private int mTopDialogTextColor = Color.parseColor("#000000");  //顶部文字的色彩
    private int mTopDialogWidth = getResources().getDimensionPixelSize(R.dimen.d_70dp);  //顶部描绘信息弹窗的宽度
    private int mTopDialogCornerRadius = getResources().getDimensionPixelSize(R.dimen.d_15dp);  //顶部描绘信息弹窗圆角半径
    private int mTopDialogBGColor = Color.parseColor("#0689FD");  //顶部框的色彩
    private int mTopDialogSpaceToProgress = getResources().getDimensionPixelSize(R.dimen.d_2dp); //顶部描绘信息弹窗间隔进展条的间隔(装备)
    private int mRealDialogDistanceSpace;  //顶部弹窗与进展条的间隔(顶部弹窗与进展的真实间隔)核算得出
    private Path mTrianglePath;     //画小三角形途径
    private int mTriangleLength = 15;  //等边三角形边长
    private int mTriangleHeight;     //等边三角形的高
    private Paint textPaint;

然后咱们在初始化画笔与资源的时分,初始化文本的画笔和顶部弹窗的矩阵:

  private void initPaint() {
       //顶部圆角矩形
        mTopDialogRect = new RectF();
        //画小三角形指针
        mTrianglePath = new Path();
        //小三角形的高
        mTriangleHeight = (int) Math.sqrt(mTriangleLength * mTriangleLength - mTriangleLength / 2 * (mTriangleLength / 2));
        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setDither(true);
        textPaint.setTextSize(mTopDialogTextSize);
        textPaint.setColor(mTopDialogTextColor);
  }

因为咱们加了顶部的高度,那么咱们就需求在丈量的时分也要把高度加上去


  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        //核算的宽度与高度
        int calWidthSize = getPaddingLeft() + mCircleRadius * 2 + getPaddingRight() + mCircleStrokeWidth * 2;
        int calHeightSize = getPaddingTop() + mTopDialogCornerRadius * 2 + mTriangleHeight + mTopDialogSpaceToProgress
                + mCircleRadius * 2 + mCircleStrokeWidth * 2 + getPaddingBottom();
        ...        
    }

显现的时分咱们核算真实的高度,咱们比设置的再高出一点点便利展现。而且对顶部弹窗的矩阵赋值

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        ...
        mRealDialogDistanceSpace = mTopDialogCornerRadius * 2 + mTopDialogSpaceToProgress;
        //数值描绘圆角矩形
        mTopDialogRect.left = w / 2 - mTopDialogWidth / 2;
        mTopDialogRect.top = getPaddingTop();
        mTopDialogRect.right = w / 2 + mTopDialogWidth / 2;
        mTopDialogRect.bottom = getPaddingTop() + mTopDialogCornerRadius * 2;
        ...
    }

然后咱们就能制作顶部的弹窗布景与内部的文本,再制作弹窗下面的小三角指针

 //顶部的文字框
    private void drawTopTextRectDialog(Canvas canvas) {
        if (leftValue == minValue && (rightValue == maxValue || rightValue < maxValue)) {
            textDesc = "低于 " + rightValue;
        } else if (leftValue > minValue && rightValue == maxValue) {
            textDesc = "高于 " + leftValue;
        } else if (leftValue > minValue && rightValue < maxValue) {
            if (leftValue == rightValue) {
                textDesc = "低于 " + rightValue;
            } else
                textDesc = leftValue + "-" + rightValue;
        }
        if (isShowRectDialog) {
            //制作圆角矩形框
            mSelectedLinePaint.setShader(null);
            mSelectedLinePaint.setColor(mTopDialogBGColor);
            canvas.drawRoundRect(mTopDialogRect, mTopDialogCornerRadius, mTopDialogCornerRadius, mSelectedLinePaint);
            //制作文本
            textPaint.setColor(mTopDialogTextColor);
            textPaint.setTextSize(mTopDialogTextSize);
            float textWidth = textPaint.measureText(textDesc);
            float textLeft = mTopDialogRect.left + mTopDialogWidth / 2 - textWidth / 2;
            canvas.drawText(textDesc, textLeft, getPaddingTop() + mTopDialogCornerRadius + mTopDialogTextSize / 4, textPaint);
        }
    }
    //顶部文字框下面的三角箭头
    private void drawSmallTriangle(Canvas canvas) {
        if (isShowRectDialog) {
            mTrianglePath.reset();
            mTrianglePath.moveTo(mTopDialogRect.left + mTopDialogWidth / 2 - mTriangleLength / 2, getPaddingTop() + mTopDialogCornerRadius * 2);
            mTrianglePath.lineTo(mTopDialogRect.left + mTopDialogWidth / 2 + mTriangleLength / 2, getPaddingTop() + mTopDialogCornerRadius * 2);
            mTrianglePath.lineTo(mTopDialogRect.left + mTopDialogWidth / 2, getPaddingTop() + mTopDialogCornerRadius * 2 + mTriangleHeight);
            mTrianglePath.close();
            canvas.drawPath(mTrianglePath, mSelectedLinePaint);
        }
    }

因为要制作的参数是顶部的矩阵,所以咱们在onTouch中,还需求对顶部的矩阵进行重新动态赋值,才能让他动起来:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
     ...
         } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            //核算当时的左面右侧真实的约束值
            int moveLeftData = getPercentMax(mLeftCircleCenterX - getPaddingLeft() - mStrokeRadius);
            int moveRightData = getPercentMax(mRightCircleCenterX - getPaddingLeft() - mStrokeRadius);
            //趁便赋值当时的真实值,能够让顶部文字显现
            leftValue = Math.min(moveLeftData, maxValue);
            rightValue = Math.min(moveRightData, maxValue);
         } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
            //核算当时的左面右侧真实的约束值
            int moveLeftData = getPercentMax(mLeftCircleCenterX - getPaddingLeft() - mStrokeRadius);
            int moveRightData = getPercentMax(mRightCircleCenterX - getPaddingLeft() - mStrokeRadius);
            //赋值便利回调
            leftValue = Math.min(moveLeftData, maxValue);
            rightValue = Math.min(moveRightData, maxValue);
            if (mListener != null) mListener.onMoveValue(leftValue, rightValue);
            //移除消息,并重新开端延时封闭顶部弹窗
            removeCallbacks(dismissRunnable);
            postDelayed(dismissRunnable, 1000);
        }
     ...
        //顶部的文本框矩阵也要居中布局
        mTopDialogRect.left = (mRightCircleCenterX + mLeftCircleCenterX) / 2 - mTopDialogWidth / 2;
        mTopDialogRect.right = (mRightCircleCenterX + mLeftCircleCenterX) / 2 + mTopDialogWidth / 2;
        //悉数的事情处理完毕,变量赋值完成之后,开端重绘
        invalidate();
        return true;
    }
    //顶部弹窗的显现
    Runnable dismissRunnable = new Runnable() {
        @Override
        public void run() {
            if (!isRectDialogShowing) {
                isShowRectDialog = false;
            }
            postInvalidate();
        }
    };

整体来说制作和动态赋值矩阵的left right并不算太难,相比两个圆的接触事情要相对简略一点。

完成的作用便是如下:

Android自定义View交互进阶,价格区间选择控件

跋文

当然后边假如咱们有更对的需求还能持续制作一些东西,例如:

Android自定义View交互进阶,价格区间选择控件

假如咱们想在图片红框处增加固定的最小值和最大值,也简略,咱们直接drawText到指定的方位即可,咱们不是现已有进展条Rect的 left 和 right 了吗?就能够很便利的增加文本。

因为是我自用的一个控件,现在需求并没有更多的要求就并没有进行更多的扩展,轮子现已在这儿了,假如咱们有兴趣也能够很便利的修改的。咱们跟着一起温习一遍,是不是感觉自界说的制作和自界说的事情现已入门了呢 – -!

现在居家办公实在是没有心境搞这些,说实话在家里我只想躺,等上班了我会把特点抽取出来上传到 Maven 开源出去。

假如等不及,也能够检查这儿的完成源码,并没有抽取和封装的哦。【传送门】。你也能够关注我的这个Kotlin项目,我有时间都会持续更新。

2022-12-05更新

现在现已单独创建了项目并上传到Maven仓库,【传送门】

Maven依靠

implementation "com.gitee.newki123456:range_view:1.0.0"

自界说特点运用

<com.newki.rang_view.RangeView
    android:id="@+id/range_view"
    android:layout_width="250dp"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:layout_marginTop="50dp"
    android:background="#f1f1f1"
    app:circle_radius="14dp"
    app:circle_stroke_width="1.5dp"
    app:rang_line_height="4dp"
    app:rang_top_dialog_corner_radius="15dp"
    app:rang_top_dialog_width="70dp"
    app:rang_top_space_to_progress="2dp"
    app:rang_top_text_size="12dp" />

常规,我如有解说不到位或错漏的当地,希望同学们能够指出交流。

假如感觉本文对你有一点点的启发,还望你能点赞支撑一下,你的支撑是我最大的动力。

Ok,这一期就此结束。

Android自定义View交互进阶,价格区间选择控件

本文正在参加「金石方案 . 瓜分6万现金大奖」