【自定义View】带动画的百分比圆型控件

前言

这个View其实非常的简单,之所以写这个博客,一方面是记录下所学,另一方面是开启我的博客之旅。

效果示例

效果分析

这个效果还是非常简单的,就是画圆、画字外加一个属性动画的结果

既然是自定义View,那我们就按自定义View的流程来介绍:

1.继承合适的View,复写构造方法,初始化一些变量

2.自定义view的宽高测量 onMeasure 方法

3.自定义view的绘制 onDraw 方法

4.属性动画效果

开发流程

自定义View之继承

因为本控件层级不复杂,就是一个单独的View,不需要继承ViewGroup,直接继承View就行。

在这一步,除了继承之外,还需要做一些成员变量的创建和初始化

比如控件的宽高、内圆和外圆的画笔及其画笔的颜色、线宽、比率文字的大小和颜色、动画时长等等,核心代码如下:

/**
 * 上下文
 */
private Context mContext;

/**
 * 控件的宽高
 */
private int viewHeight, viewWidth;

/**
 * 内圆和外圆的画笔
 */
private Paint outCirclePaint, innerCirclePaint;

/**
 * 内圆和外圆的颜色
 */
private int outCircleColor, innerCircleColor;

/**
 * 内圆和外圆的画笔宽度
 */
private int outCircleStrokeWidth, innerCircleStrokeWidth;

/**
 * 比率数字画笔和其他文字的画笔
 */
private Paint ratePaint, expressPaint, percentPaint;

/**
 * 比率数字画笔和其他文字的画笔的颜色
 */
private int rateColor, expressColor, percentColor;

/**
 * 比率数字和其他文字的大小
 */
private int rateSize, expressSize, percentSize;

/**
 * 比率数字  默认45
 */
private String rateText;

/**
 * 比率代表的含义  比如 正确率、签到率等等
 */
private String expressText;

/**
 * 比率对应的弧度
 */
private float sweep;

/**
 * 动画进行的时间
 */
private int duration;


public CircleRateView(Context context) {
    super(context);
    initView(context, null, -1);
}

public CircleRateView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    initView(context, attrs, -1);
}

public CircleRateView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initView(context, attrs, defStyleAttr);
}

private void initView(Context context, AttributeSet attrs, int defStyleAttr) {

        ……
        
        //初始化相关画笔
        outCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        outCirclePaint.setStrokeWidth(dip2px(outCircleStrokeWidth));
        outCirclePaint.setStyle(Paint.Style.STROKE);
        outCirclePaint.setColor(outCircleColor);

        innerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        innerCirclePaint.setStrokeWidth(dip2px(innerCircleStrokeWidth));
        innerCirclePaint.setStyle(Paint.Style.STROKE);
        innerCirclePaint.setColor(innerCircleColor);

        ratePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        ratePaint.setStrokeWidth(dip2px(1));
        ratePaint.setStyle(Paint.Style.FILL);
        ratePaint.setTextSize(rateSize);
        ratePaint.setTextAlign(Paint.Align.CENTER);
        ratePaint.setColor(rateColor);

        expressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        expressPaint.setStrokeWidth(dip2px(1));
        expressPaint.setStyle(Paint.Style.FILL);
        expressPaint.setTextSize(expressSize);
        expressPaint.setTextAlign(Paint.Align.CENTER);
        expressPaint.setColor(expressColor);

        percentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        percentPaint.setStrokeWidth(dip2px(1));
        percentPaint.setStyle(Paint.Style.FILL);
        percentPaint.setTextSize(percentSize);
        percentPaint.setTextAlign(Paint.Align.LEFT);
        percentPaint.setColor(percentColor);

        ……
    }

自定义View之 onMeasure

这个方法的详细介绍可以自己搜搜看,这里就不做叙述,主要是通过 MeasureSpec 这个类来测量宽高,并设置给View

核心代码如下:

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthValue = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightValue = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            viewWidth = widthValue;
        } else {
            viewWidth = dip2px(162);
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            viewHeight = heightValue;
        } else {
            viewHeight = dip2px(162);
        }

        //将View的宽高设置为正方形,使其宽高一致,等于最小的那个
        if (viewWidth <= viewHeight) {
            viewHeight = viewWidth;
        } else {
            viewWidth = viewHeight;
        }

        setMeasuredDimension(viewWidth, viewHeight);

自定义View之 onDraw

这个方法就是自定义View的核心了,文字和圆的绘制都在这个方法里。

1.绘制外圆:

外圆的半径等于控件宽的一半,中心为宽高的各一半,根据API直接画圆即可。值得一提,因为后续想要将外圆的线宽度可控制,所以圆的半径减去了线宽,至于减1dp的值,是为了美观性,外圆不至于擦边。

//画外圆
canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - outCircleStrokeWidth - dip2px(1), outCirclePaint);

2.绘制内圆:

内圆是具有动画效果的,但是在动画之前,其本质就是一段圆弧,所以先根据API画一段圆弧。这里的sweep是百分比对应的弧度,后续动画的时候会用到

//画内圆
RectF rectF = new RectF(outCircleStrokeWidth + innerCircleStrokeWidth + dip2px(8), outCircleStrokeWidth + innerCircleStrokeWidth + dip2px(8), viewWidth - dip2px(8) - outCircleStrokeWidth - innerCircleStrokeWidth, viewHeight - dip2px(8) - outCircleStrokeWidth - innerCircleStrokeWidth);
canvas.drawArc(rectF, 90, sweep, false, innerCirclePaint);

3.画文字和比率:

绘制文字的时候,就涉及到绘制的中心点和基线的问题了,中心点的对其方式不同,绘制效果就不同,有时候根据位置来选择不同的对其方式,会使绘制更加的简单。关于文字的需要使用到 FontMetrics 这个相关的类,具体不做复述,不了解自己去搜下,后续我也会开一篇新章去讲一下

        //获得比率数字的FontMetricsInt
        Paint.FontMetricsInt rateFont = ratePaint.getFontMetricsInt();
        ratePaint.setTextAlign(Paint.Align.CENTER);
        int rateX = viewWidth / 2;
        int rateY = viewHeight / 2 + (rateFont.descent / 2 - rateFont.ascent / 2 - rateFont.descent);
        canvas.drawText(rateText, rateX, rateY, ratePaint);

        //获取表达意思文字的FontMetricsInt
        Paint.FontMetricsInt textFout = expressPaint.getFontMetricsInt();
        expressPaint.setTextAlign(Paint.Align.CENTER);
        int expressX = viewWidth / 2;
        int expressY = (int) (viewHeight * 0.26 + (textFout.descent / 2 - textFout.ascent / 2 - textFout.descent));
        canvas.drawText(expressText, expressX, expressY, expressPaint);

        //根据比率文字的宽度来计算百分比的x坐标,y坐标则和比率保持一致
        percentPaint.setTextAlign(Paint.Align.LEFT);
        float rateWidth = ratePaint.measureText(rateText);
        int percentX = (int) (rateWidth / 2 + viewWidth / 2 + dip2px(11));
        int percentY = viewHeight / 2 + (rateFont.descent / 2 - rateFont.ascent / 2 - rateFont.descent);
        canvas.drawText("%", percentX, percentY, percentPaint);

至此,静态的绘制过程就完成了,效果如下:

属性动画

前文讲到sweep是动画的弧度,这个动画的效果其实很简单,就是sweep从0到45的过程,只需要对sweep进行属性动画即可,在sweep增加的过程中不断的进行重绘就可以达到效果。

        ValueAnimator valueAnimator = new ValueAnimator().ofFloat(0, sweep);
        valueAnimator.setDuration(duration);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                sweep = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator.start();

        ValueAnimator numAnimator = new ValueAnimator().ofFloat(0, Float.parseFloat(rateText));
        numAnimator.setDuration(duration);
        numAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Float rate = (Float) animation.getAnimatedValue();
                BigDecimal bigDecimal = new BigDecimal(rate);
                //四舍五入,不要小数
                bigDecimal = bigDecimal.setScale(0, BigDecimal.ROUND_HALF_UP);
                rateText = bigDecimal.toString();
                invalidate();
            }
        });
        numAnimator.start();

自定义属性

为了方便使用,自定义了一些属性,可在xml中直接使用属性进行设置。

1.定义属性

        <declare-styleable name="CircleRateView">
        <attr name="outCircleColor" format="color" />
        <attr name="innerCircleColor" format="color" />
        <attr name="rateColor" format="color" />
        <attr name="expressColor" format="color" />
        <attr name="percentColor" format="color" />
        <attr name="rateSize" format="dimension" />
        <attr name="expressSize" format="dimension" />
        <attr name="percentSize" format="dimension" />
        <attr name="outCircleStrokeWidth" format="dimension" />
        <attr name="innerCircleStrokeWidth" format="dimension" />
        <attr name="rateText" format="string" />
        <attr name="expressText" format="string" />
        <attr name="duration" format="integer" />

2.赋值属性

        //通过读取attr文件获取属性值,无配置即默认值
        TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.CircleRateView);
        outCircleColor = typedArray.getColor(R.styleable.CircleRateView_outCircleColor, Color.BLACK);
        innerCircleColor = typedArray.getColor(R.styleable.CircleRateView_innerCircleColor, Color.BLACK);
        rateColor = typedArray.getColor(R.styleable.CircleRateView_rateColor, Color.BLACK);
        expressColor = typedArray.getColor(R.styleable.CircleRateView_expressColor, Color.BLACK);
        percentColor = typedArray.getColor(R.styleable.CircleRateView_percentColor, Color.BLACK);
        rateSize = typedArray.getDimensionPixelSize(R.styleable.CircleRateView_rateSize, dip2px(50));
        expressSize = typedArray.getDimensionPixelSize(R.styleable.CircleRateView_expressSize, dip2px(16));
        percentSize = typedArray.getDimensionPixelSize(R.styleable.CircleRateView_percentSize, dip2px(16));
        outCircleStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.CircleRateView_outCircleStrokeWidth, dip2px(0.5f));
        innerCircleStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.CircleRateView_innerCircleStrokeWidth, dip2px(0.5f));
        rateText = typedArray.getString(R.styleable.CircleRateView_rateText);
        if (TextUtils.isEmpty(rateText)) {
            rateText = "45";
        }
        expressText = typedArray.getString(R.styleable.CircleRateView_expressText);
        if (TextUtils.isEmpty(expressText)) {
            expressText = "正确率";
        }
        duration = typedArray.getInt(R.styleable.CircleRateView_duration, 2000);
        //记得回收对象
        typedArray.recycle();

3.使用属性

    <com.feyolny.view.CircleRateView
        android:id="@+id/circleRateView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        app:duration="2000"
        app:expressColor="#BBBBBB"
        app:expressSize="18dp"
        app:expressText="错误率"
        app:innerCircleColor="#4755E8"
        app:innerCircleStrokeWidth="1dp"
        app:outCircleColor="#dbdbdb"
        app:outCircleStrokeWidth="2dp"
        app:percentColor="#BBBBBB"
        app:percentSize="18dp"
        app:rateColor="#4755E8" />

分享

本文至此结束,本着分享和重复使用的初衷,本控件源码已经上传至github,可直接在项目中添加依赖,进行使用。

源码及详细的使用方法和相关API介绍请点击:

GitHub: https://github.com/feyolny/CircleRateView

交流

我的GitHub:https://github.com/feyolny
我的简书:https://www.jianshu.com/u/79cb1fd42ea9
我的CSDN:https://blog.csdn.net/feyolny

欢迎大家一起交流!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值