自定义CircleProgressBar

来源:互联网 发布:qq五笔mac版 编辑:程序博客网 时间:2024/06/08 06:43

概述

这是一个自定义实线分数,进度展示的progressBar。
实现的效果差强人意,但我们可以一起探究下自定义控件的过程。
除此之外,我发现绘制弧线的过程,相对于绘制其他的图形,是比较难以理解的。我后面总结下绘制弧线的过程。

效果图

因为第一次做gif图形,效果不好。其实图形的变化是连贯的。
1. 实线实现的效果
实线
2. 虚线实线的效果
虚线

实现过程分析

1.自定义控件,有两个方法几乎是必写的,那就是onMeasure(int widthMeasureSpec, int heightMeasureSpec)和onDraw(Canvas canvas)方法。
onMeasure()方法实现测量。如果我们直接将控件的大小给一个确定值或者match_parent,就可以不用重写该方法。
如果我们将控件的宽高属性设置为wrap_content,那么我们就必须重写该方法了,毕竟,若你不告诉系统,系统怎么会知道这个content是多大呢。

    /**     * 如果测量模式不是EXACTLY(即精确测量),我们就将该View的长和宽都设置为250dp。     *     * @param widthMeasureSpec     * @param heightMeasureSpec     */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int viewWidth,viewHeight;        /**         * 设置宽度         * 获取测量模式和测量的大小         */        int specMode = MeasureSpec.getMode(widthMeasureSpec);        int specSize = MeasureSpec.getSize(widthMeasureSpec);        if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate        {            viewWidth = specSize;        } else{            /**             * 如果view直接赋值为250,则是250px,通过下面的代码,将sp转化为dip(dp).             */            viewWidth=(int)(2*TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,250,getResources().getDisplayMetrics()));        }        /**         * 设置高度         */        specMode = MeasureSpec.getMode(heightMeasureSpec);        specSize = MeasureSpec.getSize(heightMeasureSpec);        if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate        {            viewHeight = specSize;            Log.d("liang","viewHeight"+viewHeight);        } else{            viewHeight=(int)(2*TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,250,getResources().getDisplayMetrics()));        }        setMeasuredDimension(viewWidth, viewHeight);    }

onDraw()方法是绘制图形的时候调用的方法。我们需要绘制的元素有三个,圆弧(实现或者虚线),图片,分数。
在绘制过程中,需要实现颜色渐变的效果。我们借助Shader进行渲染。

 mLinearGradient1 = new LinearGradient(0, 0, 0, 2*radius, new int[] {                firstColor, secondColor, thirdColor }, null,                Shader.TileMode.CLAMP);mPaint.setShader(mLinearGradient1);

数组前的四个参数确定渲染的范围,int数组里面的三个元素是渐变的颜色。最后一个参数是渲染模式,感兴趣的读者可以自行查询LinearGradient的用法,这里不做过多的表述。

整个onDraw()方法的代码段:

    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPaint.setShader(mLinearGradient1);        RectF rectF=new RectF(50,50,2*radius,2*radius);        //Rect rect=new Rect(50,50,2*radius,2*radius);        //canvas.drawRect(rect,mPaint);        mPaint.setColor(Color.BLUE);        canvas.drawArc(rectF,-90,progress,false,mPaint);        Paint paint=new Paint();        paint.setColor(textColor);        paint.setTextAlign(Paint.Align.CENTER);        paint.setTextSize(textSize);        paint.setSubpixelText(false);        Paint.FontMetrics fontMetrics = paint.getFontMetrics();        float y = radius - fontMetrics.descent + (fontMetrics.bottom - fontMetrics.top) / 2;        int margin=(int)(radius*0.2);        int offest=(int)(radius*0.28);        Rect rect1=new Rect(radius-margin,radius-margin-offest,                radius+margin,radius+margin-offest);        canvas.drawBitmap(icon,null,rect1,paint);        canvas.drawText(grade2+"",radius,y+70,paint);    }

2.进度和数字变化
为了实现进度和数字变化,这里开了两个线程。并且在每个线程里面用postInvalidate()更新画布。
为了实现这个功能,我首先记录了圆弧画完一圈所用的时间,然后用这一圈所花费的时间除以分数的最大值,所得的结果就是每次更新分数所需要的时间。
这么说太过抽象,还是看代码吧:

    private int progress=0;    /**     * 绘制的速度     */    private int speed=20;        /**     * 进度或者分数     */    private int grade=60;    private int grade2=0; new Thread(new Runnable() {            @Override            public void run() {                while (true){                    if(progress<360){                        progress++;                    }else {                        break;                    }                    try {                        Thread.sleep(speed);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }finally {                        postInvalidate();                    }                }            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                int count =360*speed;                while (true){                    if(grade2<grade){                        grade2++;                    }else {                        break;                    }                    try {                        int spaceTime=count/grade;                        Thread.sleep(spaceTime);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }finally {                        postInvalidate();                    }                }            }        }).start();

注意,每次执行postInvalidate()方法,android都会调用onDraw()方法。

圆弧的绘制

  1. 简述
    首先我们要知道,所谓圆弧就是椭圆的一部分。圆是一种特殊的椭圆。
    在本例中,我们圆弧其实是一个360度的圆环。
    绘制椭圆,我们需要确定它的外接矩形的大小和位置。所谓大小和位置的我确定,我们只需要确定就行左上角和右下角的坐标即可。这四个值就是我们绘制椭圆RectF的参数。
    确定椭圆的大小位置:
RectF rectF=new RectF(50,50,2*radius,2*radius);

(50,50)是左上角的坐标,(2*radius,2*radius)是右下角的坐标。

有了椭圆,我们就可以绘制圆弧了。

canvas.drawArc(rectF,-90,progress,false,mPaint)

第一个参数rectF就是我们的椭圆,它确定了圆弧所在的位置。第二个参数-90表示开始绘制的角度。-90代表从最上边的顶点处开始绘制。第三个参数表示绘制多少度。在本例中,progress的值是定值360。即绘制整整一圈。
第四个参数确定圆弧的形状。如果是true,则和圆心相连接成为扇形。如果是false,那么就仅仅是弧形。感兴趣的读者可以在网上找资料看看,这里就不贴图举例啦。

代码

  1. 自定义的控件
public class MyCircleProgressBar extends View {    private Paint mPaint;    /**     * 圆弧的宽度     */    private int circleWidth=40;    private int progress=0;    /**     * 绘制的速度     */    private int speed=20;    /**     * 圆弧的半径     */    private int radius=250;    /**     * 线性着色器,使画笔在这三种色彩上形成渐变的效果     */    private Shader mLinearGradient1 = null;    /**     * 绘制过程中,三种渐变的色彩     */    private int firstColor=Color.RED;    private int secondColor=Color.GREEN;    private int thirdColor=Color.BLUE;    /**     * 进度或者分数     */    private int grade=60;    private int grade2=0;    /**     * 控件中间的小图片     */    private Bitmap icon;    /**     * 字体的大小和颜色     */    private int textSize=150;    private int textColor;    /**     * 是否为虚线     */    private boolean isDashedLine=true;    public void setTextSize(int textSize) {        this.textSize = textSize;    }    public void setCircleWidth(int circleWidth) {        this.circleWidth = circleWidth;    }    public void setSpeed(int speed) {        this.speed = speed;    }    public void setRadius(int radius) {        this.radius = radius;    }    public void setFirstColor(int firstColor) {        this.firstColor = firstColor;    }    public void setSecondColor(int secondColor) {        this.secondColor = secondColor;    }    public void setThirdColor(int thirdColor) {        this.thirdColor = thirdColor;    }    public void setGrade(int grade) {        this.grade = grade;    }    public void setIcon(Bitmap icon) {        this.icon = icon;    }    public void setDashedLine(boolean dashedLine) {        isDashedLine = dashedLine;    }    public MyCircleProgressBar(Context context) {        this(context, null);    }    public MyCircleProgressBar(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public MyCircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        getAttrs(context,attrs,defStyleAttr);        mPaint=new Paint();        mPaint.setColor(getResources().getColor(R.color.colorAccent));        mPaint.setAntiAlias(true);        mPaint.setStrokeWidth(circleWidth);        mPaint.setStyle(Paint.Style.STROKE);        if(isDashedLine){            PathEffect effect = new DashPathEffect(new float[] { 10, 10}, 1);            mPaint.setPathEffect(effect);        }        mLinearGradient1 = new LinearGradient(0, 0, 0, 2*radius, new int[] {                firstColor, secondColor, thirdColor }, null,                Shader.TileMode.CLAMP);        new Thread(new Runnable() {            @Override            public void run() {                while (true){                    if(progress<360){                        progress++;                    }else {                        break;                    }                    try {                        Thread.sleep(speed);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }finally {                        postInvalidate();                    }                }            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                int count =360*speed;                while (true){                    if(grade2<grade){                        grade2++;                    }else {                        break;                    }                    try {                        int spaceTime=count/grade;                        Thread.sleep(spaceTime);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }finally {                        postInvalidate();                    }                }            }        }).start();    }    /**     * 如果测量模式不是EXACTLY(即精确测量),我们就将该View的长和宽都设置为250dp。     *     * @param widthMeasureSpec     * @param heightMeasureSpec     */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int viewWidth,viewHeight;        /**         * 设置宽度         * 获取测量模式和测量的大小         */        int specMode = MeasureSpec.getMode(widthMeasureSpec);        int specSize = MeasureSpec.getSize(widthMeasureSpec);        if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate        {            Log.e("xxx", "EXACTLY");            viewWidth = specSize;        } else{            /**             * 如果view直接赋值为250,则是250sp,通过下面的代码,将sp转化为dip(dp).             */            viewWidth=(int)(2*TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,250,getResources().getDisplayMetrics()));        }        /**         * 设置高度         */        specMode = MeasureSpec.getMode(heightMeasureSpec);        specSize = MeasureSpec.getSize(heightMeasureSpec);        if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate        {            Log.e("xxx", "EXACTLY");            viewHeight = specSize;            Log.d("liang","viewHeight"+viewHeight);        } else{            viewHeight=(int)(2*TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,250,getResources().getDisplayMetrics()));        }        setMeasuredDimension(viewWidth, viewHeight);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPaint.setShader(mLinearGradient1);        RectF rectF=new RectF(50,50,2*radius,2*radius);        //Rect rect=new Rect(50,50,2*radius,2*radius);        //canvas.drawRect(rect,mPaint);        mPaint.setColor(Color.BLUE);        canvas.drawArc(rectF,-90,progress,false,mPaint);        Paint paint=new Paint();        paint.setColor(textColor);        paint.setTextAlign(Paint.Align.CENTER);        paint.setTextSize(textSize);        paint.setSubpixelText(false);        Paint.FontMetrics fontMetrics = paint.getFontMetrics();        float y = radius - fontMetrics.descent + (fontMetrics.bottom - fontMetrics.top) / 2;        int margin=(int)(radius*0.2);        int offest=(int)(radius*0.28);        Rect rect1=new Rect(radius-margin,radius-margin-offest,                radius+margin,radius+margin-offest);        canvas.drawBitmap(icon,null,rect1,paint);        canvas.drawText(grade2+"",radius,y+70,paint);    }    /**     * 获取自定义属性     * @param context     * @param attrs     * @param defStyleAttr     */    void getAttrs(Context context, AttributeSet attrs, int defStyleAttr){        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomProgressBar, defStyleAttr, 0);        int circleWidth1=(int)a.getDimension(R.styleable.CustomProgressBar_circleWidth,40);        circleWidth=(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,circleWidth1,                getResources().getDisplayMetrics());        speed=a.getInt(R.styleable.CustomProgressBar_speed,20);        radius=(int)a.getDimension(R.styleable.CustomProgressBar_radius,250);        firstColor=a.getColor(R.styleable.CustomProgressBar_firstColor,Color.RED);        secondColor=a.getColor(R.styleable.CustomProgressBar_secondColor,Color.GREEN);        thirdColor=a.getColor(R.styleable.CustomProgressBar_thirdColor,Color.BLUE);        grade=a.getInt(R.styleable.CustomProgressBar_grade,20);        Drawable drawable=a.getDrawable(R.styleable.CustomProgressBar_icons);        if(null!=drawable){            BitmapDrawable bd = (BitmapDrawable)drawable;            icon = bd.getBitmap();        }else{            icon= BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);        }        textSize=(int)a.getDimension(R.styleable.CustomProgressBar_textSizes,150);        textColor=a.getColor(R.styleable.CustomProgressBar_textColor,Color.RED);a.recycle();    }}

2.自定义的属性

<resources>    <attr name="firstColor" format="color" />    <attr name="secondColor" format="color" />    <attr name="thirdColor" format="color" />    <attr name="textColor" format="color" />    <attr name="circleWidth" format="dimension" />    <attr name="speed" format="integer" />    <attr name="radius" format="dimension" />    <attr name="grade" format="integer" />    <attr name="icons" format="reference|color" />    <attr name="textSizes" format="dimension" />    <declare-styleable name="CustomProgressBar">        <attr name="firstColor" />        <attr name="secondColor" />        <attr name="thirdColor" />        <attr name="circleWidth" />        <attr name="speed" />        <attr name="radius" />        <attr name="grade" />        <attr name="icons" />        <attr name="textSizes" />        <attr name="textColor" />    </declare-styleable></resources>

3.布局文件

   <com.zhimei.customcircleprogressbar.widget.MyCircleProgressBar       android:layout_width="wrap_content"       android:layout_height="wrap_content"       custom:circleWidth="10dp"       custom:speed="20"       custom:firstColor="@color/colorAccent"       custom:icons="@mipmap/ic_launcher"       custom:grade="46"       custom:textSizes="100sp"       custom:radius="150dp"       custom:textColor="@color/colorPrimaryDark"       />

源码下载

点击下载

0 0
原创粉丝点击