自定义可拖拽评分进度条控件

来源:互联网 发布:网络词表妹是什么梗 编辑:程序博客网 时间:2024/06/05 11:16

愉快的周末,在家撸代码如何?刚刚看完《战狼2》,心潮澎湃啊,China No.1 !  23333333  李时珍的皮,言归正传。

最近项目中UI设计了这样一种控件,可以拖拽的评分进度条,这让我一次没有写过自定义View的人如何是好啊?不过经过我一天的查阅资料,终于实现了,先看一下效果吧:

来看下我们实现之后动态的效果图:



好了,效果我们都看见了,下面直接进入正题,自定义View。

自定义view,首先我们先分析一下,这个控件需要哪些属性;①进度条的背景色②三种进度条变化的颜色③滑块的drawable④进度条的高度⑤进度条之间留白间隙的宽度⑥滑块的宽度和高度,我们先列出这么些,如果有需要到时候我们再添加。

需要的属性我们列好后,在values目录下新建attrs资源文件夹,新增以下属性:

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="DragProgressBar">        <!--进度条的默认背景色-->        <attr name="default_background" format="color"/>        <!--进度颜色1-->        <attr name="progress_color1" format="color"/>        <!--进度颜色2-->        <attr name="progress_color2" format="color"/>        <!--进度颜色3-->        <attr name="progress_color3" format="color"/>        <!--滑块的drawable-->        <attr name="seek_btn" format="reference"/>        <!--滑块的宽度-->        <attr name="seek_btn_width" format="dimension"/>        <!--滑块的高度-->        <attr name="seek_btn_height" format="dimension"/>        <!--进度条之间的留白部分-->        <attr name="progress_padding" format="dimension"/>        <!--进度条的高度-->        <attr name="progress_height" format="dimension"/>        <!--是否可以拖拽-->        <attr name="can_drag" format="boolean"/>    </declare-styleable></resources>
这样我们的自定义属性定义好之后,新建一个DragProgressBar继承自View,然后重写构造方法,这个代码就不罗列了,大家应该都知道。接下来就应该获取我们自定义的属性。

TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.DragProgressBar);        this.mDefaultBgColor = mTypedArray.getColor(R.styleable.DragProgressBar_default_background, getResources().getColor(R.color.gray_link_bg));        this.mProgressGradle1Color = mTypedArray.getColor(R.styleable.DragProgressBar_progress_color1, getResources().getColor(R.color.red_progress));        this.mProgressGradle2Color = mTypedArray.getColor(R.styleable.DragProgressBar_progress_color2, getResources().getColor(R.color.yellow_progress));        this.mProgressGradle3Color = mTypedArray.getColor(R.styleable.DragProgressBar_progress_color3, getResources().getColor(R.color.green_progress));        this.mSeekBtnDrawable = mTypedArray.getDrawable(R.styleable.DragProgressBar_seek_btn);        this.mSeekBtnWidth = mTypedArray.getDimension(R.styleable.DragProgressBar_seek_btn_width, 56);        this.mSeekBtnHeight = mTypedArray.getDimension(R.styleable.DragProgressBar_seek_btn_height, 56);        this.mProgressPadding = mTypedArray.getDimension(R.styleable.DragProgressBar_progress_padding, 2);        this.mProgressHeight = mTypedArray.getDimension(R.styleable.DragProgressBar_progress_height, 12);        this.mCanEdit = mTypedArray.getBoolean(R.styleable.DragProgressBar_can_drag, false);        mTypedArray.recycle();
最后一行recycle()方法不要忘记加哦!现在我们的自定义属性的值都获取到了之后,我们需要定义一些变量,方便下面的计算。

    private int centerTop;//进度条的上边界    private int centerBottom;//进度条的下边界    private float roundLeft = 0;//根据left 来更新现在滑块的位置    private float mStartX;    private float mLastX;    private float mDistX;    private onProgressChangeListener mOnProgressChangeListener;//进度改变的回调接口    private Bitmap seekbar;//滑块bitmap    private int mDefaultBgColor;//背景色    private int mProgressGradle1Color;//进度1的颜色    private int mProgressGradle2Color;//进度2的颜色    private int mProgressGradle3Color;//进度3的颜色    private Drawable mSeekBtnDrawable;//滑块的drawable    private float mSeekBtnWidth;//滑块宽度    private float mHalfSeekBtnWidth;//滑块一半宽度    private float mSeekBtnHeight;//滑块高度    private float mHalfSeekBtnHeight;//滑块一半高度    private float mProgressPadding;//进度之间的padding(留白部分)    private float mProgressHeight;//进度条的高度    private float mHalfProgressHeight;//进度条的高度    private float mKeduWidth;//每个刻度的宽度    private boolean mCanEdit;//默认可拖动可编辑
mHalfSeekBtnWidth = mSeekBtnWidth / 2;        mHalfSeekBtnHeight = mSeekBtnHeight / 2;        mHalfProgressHeight = mProgressHeight / 2;        mRadiusPaint = new Paint();        mRadiusPaint.setAntiAlias(true);        mRadiusPaint.setColor(mDefaultBgColor);        centerTop = (int) ((mSeekBtnHeight - mProgressHeight) / 2);        centerBottom = (int) (centerTop + mProgressHeight);        this.seekbar = drawableToBitmap(mSeekBtnDrawable);
这里有一个drawableToBitmap方法,是用来把drawable资源转成bitmap用的:

    private Bitmap drawableToBitmap(Drawable drawable) {        if (drawable == null) return null;        Bitmap bitmap = Bitmap.createBitmap((int) mSeekBtnWidth, (int) mSeekBtnHeight, Bitmap.Config.ARGB_8888);        Canvas canvas = new Canvas(bitmap);        drawable.setBounds(0, 0, (int) mSeekBtnWidth, (int) mSeekBtnHeight);        drawable.draw(canvas);        return bitmap;    }

在我们绘制之前,有必要先理一下思路,对于这个view来说,我们首先要做的肯定是绘制进度条,这个进度条左右都有一个半圆,中间还有留白的padding部分,进度条的刻度应该怎么画呢?

仔细分析一下,除了左右两边的那个刻度,中间的其实都是一个矩形而已,至于中间的留白,drawRect方法会传矩形的左右两条边的值,在每个矩形之间空出padding就可以;那中间的分析好之后,左右两边的呢?其实左右两边都是一个半圆而已,直径就是进度条的高度,我们画一个圆,然后裁剪一下,得到半圆就ok啦!

简单写下代码:

//绘制背景        for (int i = 0; i < 10; i++) {            mRadiusPaint.setColor(mDefaultBgColor);            if (i == 0) {                //最左边是有圆角的 而圆角起始x点应该是seekBtn宽度的一半                RectF leftOval = new RectF(mHalfSeekBtnWidth, centerTop, mHalfSeekBtnWidth + mProgressHeight, centerBottom);                //绘制扇形 取90°~270°                canvas.drawArc(leftOval, 90, 180, true, mRadiusPaint);                //绘制除圆弧之外 剩余正常进度条部分                canvas.drawRect(mHalfSeekBtnWidth + mHalfProgressHeight, centerTop, mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i                        + mKeduWidth, centerBottom, mRadiusPaint);            } else if (i == 9) {                canvas.drawRect(mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i, centerTop,                        mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i + mKeduWidth - mHalfProgressHeight, centerBottom, mRadiusPaint);                //移动画布                RectF rightOval = new RectF(mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i + mKeduWidth - mHalfProgressHeight - mHalfProgressHeight,                        centerTop, mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i + mKeduWidth - mHalfProgressHeight - mHalfProgressHeight + mProgressHeight, centerBottom);                canvas.drawArc(rightOval, 90, -180, true, mRadiusPaint);                //绘制完第10个进度条 移动画布            } else {                canvas.drawRect(mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i, centerTop,                        mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i + mKeduWidth, centerBottom, mRadiusPaint);            }        }

这样以后,我们就绘制好了进度条的背景色,看下效果。

OK 背景绘制好了,就考虑进度的问题了,以及滑块的位置问题。

同样,我们先分析一下进度应该怎么显示,既然是可拖拽的,那么肯定和事件处理相关,我们需要计算当前点击的位置,其实这里只用到了x轴的位置,当我们点击进度条

的时候,在ACTION_DOWM里会获取到点击的x位置,然后根据x位置计算出当前的进度,根据进度绘制不同颜色的进度。

至于滑块的滑动问题,我们需要定义一个字段roundLeft,就是滑块左边界的x点起始位置,根据这个值,不停更新滑块位置。

switch (event.getAction()) {                case MotionEvent.ACTION_DOWN: {                    Log.d("hyq", "ACTION_DOWN  x:" + event.getX());                    mStartX = mLastX = event.getX();                    setSelectByLeft(mStartX);                    break;                }                case MotionEvent.ACTION_MOVE: {                    mLastX = event.getX();                    mDistX = mLastX - mStartX;                    Log.d("hyq", "ACTION_MOVE  left:" + roundLeft + "   distx:" + mDistX + "  maxwidth:" + getMeasuredWidth() + "  x:" + mLastX);                    if (Math.abs(mDistX) > 5) {                        //保证不出界                        roundLeft += mDistX;                        if (roundLeft <= 0) {                            roundLeft = 0;                        }                        if (roundLeft > getMeasuredWidth() - mSeekBtnWidth) {                            roundLeft = getMeasuredWidth() - mSeekBtnWidth;                        }                        float center = roundLeft + mHalfSeekBtnWidth;                        mStartX = event.getX();                        setMoveLeft(center);                    }                    break;                }                case MotionEvent.ACTION_UP: {                    //根据抬起时候  圆心的位置 最接近哪个刻度 就给哪个值                    float center = roundLeft + mHalfSeekBtnWidth;                    setSelectByLeft(center);                    break;                }            }


这里有两个方法,分别是setMoveLeft,setSelectByLeft方法,是根据当前滑块中心点的位置,计算出当前的progress,而且需要处理滑动超过每个进度的一半的时候,手指

抬起后滑块自动选择最相近的进度。

private void setMoveLeft(float center) {        if (center < mHalfSeekBtnWidth + mKeduWidth / 2) {            select = 0;        } else if (center > getMeasuredWidth() - mHalfSeekBtnWidth - mKeduWidth / 2) {            select = 10;        } else {            select = (int) (center / (mKeduWidth + 2));        }        Log.d("hyq", "center:" + center + "    select:" + select);        invalidate();        if (mOnProgressChangeListener != null) {            if (select != lastSelect) {                mOnProgressChangeListener.onProgressChange(select);            }        }        lastSelect = select;    }

    /**     * 根据 点击 或 抬起时候的 中心点 定位到当前的选中progress     *     * @param center     */    private void setSelectByLeft(float center) {        if (center < mHalfSeekBtnWidth + mKeduWidth / 2) {            select = 0;        } else if (center > getMeasuredWidth() - mHalfSeekBtnWidth - mKeduWidth / 2) {            select = 10;        } else {            select = (int) (center / (mKeduWidth + 2));        }        Log.d("hyq", "center:" + center + "    select:" + select);        getRoundLeft();        invalidate();        if (mOnProgressChangeListener != null) {            if (select != lastSelect) {                mOnProgressChangeListener.onProgressChange(select);            }        }        lastSelect = select;    }

现在应该很简单了吧!想要的位置的值啥的全都有啦。只需要复制绘制背景的代码,然后把循环的条件改成当前的刻度,不就可以绘制出当前进度了吗?那进度的颜色呢?

这还不简单吗...根据进度设置画笔的颜色呀!

if (select < 5) {            mRadiusPaint.setColor(mProgressGradle1Color);        } else if (select < 7) {            mRadiusPaint.setColor(mProgressGradle2Color);        } else {            mRadiusPaint.setColor(mProgressGradle3Color);        }
现在颜色啥的也设置好了,直接绘制:

for (int i = 0; i < select; i++) {            if (i == 0) {                RectF leftOval = new RectF(mHalfSeekBtnWidth, centerTop, mHalfSeekBtnWidth + mProgressHeight, centerBottom);                canvas.drawArc(leftOval, 90, 180, true, mRadiusPaint);                canvas.drawRect(mHalfSeekBtnWidth + mHalfProgressHeight, centerTop, mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i + mKeduWidth,                        centerBottom, mRadiusPaint);            } else if (i == 9) {                canvas.drawRect(mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i, centerTop,                        mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i + mKeduWidth - mHalfProgressHeight, centerBottom, mRadiusPaint);                RectF rightOval = new RectF(mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i + mKeduWidth - mHalfProgressHeight - mHalfProgressHeight,                        centerTop, mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i + mKeduWidth - mHalfProgressHeight - mHalfProgressHeight + mProgressHeight, centerBottom);                canvas.drawArc(rightOval, 90, -180, true, mRadiusPaint);            } else {                canvas.drawRect(mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i, centerTop,                        mHalfSeekBtnWidth + (mKeduWidth + mProgressPadding) * i + mKeduWidth, centerBottom, mRadiusPaint);            }        }
绘制好了之后,现在就根据roundLeft的值绘制当前的滑块的位置,这里因为是bitmap,需要先将canvs画布移动到roundLeft的位置,所以绘制的时候需要这么写:

canvas.translate(roundLeft, 0);        canvas.drawRect(0, 0, mSeekBtnWidth, mSeekBtnHeight, mSeekBarPaint);

我们再看一下现在的效果


我们只需要在activity中实现我们自定义view里面定义好的进度回调接口,就能实时的获取当前的进度,到此就大功告成啦!


好啦,第一次写blog,还不是很熟练这个编辑工具。在今后的日子里,还希望能和大家一起讨论新知识,新技术。

ps:周末愉快!


源码点此下载

原创粉丝点击