cekiasoo's blog Android Coder

Android 自定义控件 仿支付宝芝麻信用的刻度盘


一、前言

最近在看芝麻信用的时候突发奇想自己做一个芝麻信用的界面然后可以用来装装逼,可惜的是把支付宝解开来没发现相关的界面图片,虽然是这样但也不影响到咱的动手能力,好了,进入正题。

二、分析

来看看芝麻信用的界面先。
运行结果截图

可以看到有一个外面有个圆弧,前面一段是渐变的,后面一段颜色浅一些没有渐变的,没有渐变的在渐变的底下,圆弧上还有个小圆点,我们放大圆点这里再看, 运行结果截图

其实这个圆点应该是用张图片来做的。

里面是个由一个点一个点组成的圆弧,从上面的放大图也可以看出这不是圆点而是像小矩形的点,总共有56个点,已走过的点比未走过的点的颜色要深。

运行结果截图

这张图是上个版本的芝麻信用的刻度盘,可以看出这个盘如果是圆形的话,大刻度的就是分为8份,每一份就是45度,这里有5份,所以这个盘为225度,现在的版本应该也是一样225度,在这个版本可以看出分数是从350开始到950,350到550为一个大刻度,每一分就是0.225°;550到600为一个大刻度,600到650为一个大刻度,650到700为一个大刻度,这三个间隔是相同的,就是550到700的每一分是0.9°;700到950为一个大刻度,每一分占0.18°。

接下来还有个小指针,再里面就是文字了,

运行结果截图

用ps开启网格看下,文字的位置就很清楚了。

三、效果

来看看实现后的效果先。

运行结果截图

四、实现

(一)自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--刻度圆弧半径-->
    <attr name="scaleArcRadius" format="dimension" />
    <!--刻度圆弧的宽度-->
    <attr name="scaleArcWidth" format="dimension" />
    <!--进度圆弧的半径-->
    <attr name="progressArcRadius" format="dimension" />
    <!--进度圆弧的宽度-->
    <attr name="progressArcWidth" format="dimension" />
    <!--BETA的字体大小-->
    <attr name="betaTextSize" format="dimension" />
    <!--信用级别的字体大小-->
    <attr name="creditLevelTextSize" format="dimension" />
    <!--信用分数的字体大小-->
    <attr name="creditScoreTextSize" format="dimension" />
    <!--评估时间的字体大小-->
    <attr name="evaluationTimeTextSize" format="dimension" />
    <!--字上下行之间的间隔-->
    <attr name="textSpacing" format="dimension" />
    <!--箭头与圆弧的间隔-->
    <attr name="arrowSpacing" format="dimension" />
    <declare-styleable name="DialView">
        <attr name="scaleArcRadius" />
        <attr name="scaleArcWidth" />
        <attr name="progressArcRadius" />
        <attr name="progressArcWidth" />
        <attr name="betaTextSize" />
        <attr name="creditLevelTextSize" />
        <attr name="creditScoreTextSize" />
        <attr name="evaluationTimeTextSize" />
        <attr name="textSpacing" />
        <attr name="arrowSpacing" />
    </declare-styleable>
</resources>

(二)解析属性

public class DialView extends View {
    /**
     * 圆弧的度数
     */
    private final static float TOTAL_ANGLE = 225.0f;
    /**
     * 刻度圆弧有56个点
     */
    private final static int SCALE_COUNT = 56;
    private final static String CREDIT_LEVEL[] = {"信用较差", "信用中等", "信用良好", "信用优秀", "信用极好"};
    private final static String BETA = "BETA";
    private final static String EVALUATION_TIME = "评估时间:";
    private final static int MIN_ALPHA = 0;
    private final static int MAX_ALPHA = 255;
    private final static int RED = 255;
    private final static int GREEN = 255;
    private final static int BLUE = 255;
    private final static int COLOR_TRANSPARENT = Color.argb(MIN_ALPHA, RED, GREEN, BLUE);
    private final static int COLOR_WHITE = Color.argb(MAX_ALPHA, RED, GREEN, BLUE);
    /**
     * 渐变进度圆弧的颜色
     */
    private final static int GRADIENT_COLORS[] = {COLOR_TRANSPARENT, COLOR_TRANSPARENT, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE};
    /**
     * 画笔
     */
    private Paint mPaint;
    /**
     * 刻度圆弧的半径
     */
    private int mScaleArcRadius;
    /**
     * 刻度圆弧的宽度
     */
    private int mScaleArcWidth;
    /**
     * 进度圆弧的半径
     */
    private int mProgressArcRadius;
    /**
     * 进度圆弧的宽度
     */
    private int mProgressArcWidth;
    /**
     * 进度圆弧上的小圆点的半径
     */
    private int mBallOverstepWidth;
    /**
     * BETA的字体大小
     */
    private int mBetaTextSize;
    /**
     * 信用级别的字体大小
     */
    private int mCreditLevelTextSize;
    /**
     * 信用分数的字体大小
     */
    private int mCreditScoreTextSize;
    /**
     * 评估时间的字体大小
     */
    private int mEvaluationTimeTextSize;
    /**
     * 字上下行的间隔
     */
    private int mTextSpacing;
    /**
     * 箭头与圆弧的间隔
     */
    private int mArrowSpacing;
    /**
     * 信用分数
     */
    private int mCreditScore = 666;
    public DialView(Context context) {
        this(context, null);
    }
    public DialView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public DialView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        parseAttr(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
    }
    /**
     * 解析属性
     *
     * @param context      Context
     * @param attrs        AttributeSet
     * @param defStyleAttr defStyleAttr
     */
    private void parseAttr(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray _TypedArray = context.obtainStyledAttributes(attrs, R.styleable.DialView, defStyleAttr, 0);
        mScaleArcRadius = _TypedArray.getDimensionPixelSize(R.styleable.DialView_scaleArcRadius, dp2px(context, 100));
        mScaleArcWidth = _TypedArray.getDimensionPixelSize(R.styleable.DialView_scaleArcWidth, dp2px(context, 2));
        mProgressArcRadius = _TypedArray.getDimensionPixelSize(R.styleable.DialView_progressArcRadius, dp2px(context, 105));
        mProgressArcWidth = _TypedArray.getDimensionPixelSize(R.styleable.DialView_progressArcWidth, dp2px(context, 1));
        mBetaTextSize = _TypedArray.getDimensionPixelSize(R.styleable.DialView_betaTextSize, dp2px(context, 12));
        mCreditLevelTextSize = _TypedArray.getDimensionPixelSize(R.styleable.DialView_creditLevelTextSize, dp2px(context, 18));
        mCreditScoreTextSize = _TypedArray.getDimensionPixelSize(R.styleable.DialView_creditScoreTextSize, dp2px(context, 40));
        mEvaluationTimeTextSize = _TypedArray.getDimensionPixelSize(R.styleable.DialView_evaluationTimeTextSize, dp2px(context, 12));
        mTextSpacing = _TypedArray.getDimensionPixelSize(R.styleable.DialView_textSpacing, dp2px(context, 12));
        mArrowSpacing = _TypedArray.getDimensionPixelSize(R.styleable.DialView_arrowSpacing, dp2px(context, 5));
        _TypedArray.recycle();
    }
}
    /**
     * dp转px
     * @param pContext Context
     * @param pDpVal dp值
     * @return px值
     */
    private static int dp2px(Context pContext, int pDpVal) {
        float _Scale = pContext.getResources().getDisplayMetrics().density;
        return (int)(pDpVal * _Scale + 0.5f * (pDpVal >= 0 ? 1 : -1));
    }

(三)覆盖onMeasure()方法修改下宽度或高度

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int _WidthMode=MeasureSpec.getMode(widthMeasureSpec);
        int _HeightMode = MeasureSpec.getMode(heightMeasureSpec);
        Bitmap _Ball = BitmapFactory.decodeResource(getResources(), R.drawable.ic_ball);
        mBallOverstepWidth =(int) Math.ceil(_Ball.getHeight() / 2.0 - mProgressArcWidth / 2.0);

        if (_WidthMode != MeasureSpec.EXACTLY) {
            int _Width = Math.max(mScaleArcRadius, mProgressArcRadius) * 2 + mBallOverstepWidth * 2
                    + getPaddingLeft() + getPaddingRight();
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(_Width, MeasureSpec.EXACTLY);
        }
        if (_HeightMode != MeasureSpec.EXACTLY) {
            int _MaxRadius = Math.max(mScaleArcRadius, mProgressArcRadius);
            int _Height =(int) (_MaxRadius + _MaxRadius * Math.sin(Math.toRadians(22.5))
                    + mBallOverstepWidth + _Ball.getHeight() / 2 + getPaddingTop() + getPaddingBottom());
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(_Height, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

如果是EXACTLY模式,我们就不需要处理了,其他情况我们就需要重新计算下宽度或高度,高度的计算也简单,主要是下边的部分的计算,下图绿色那条边所对的角是22.5°,深蓝色那条边是圆的半径了,所对的角是90°,这就是三角函数的知识了。

运行结果截图

(四)onDraw()

1.onDraw()

    @Override
    protected void onDraw(Canvas canvas) {
        int _MaxRadius = Math.max(mProgressArcRadius, mScaleArcRadius);
        //移动原点到中心位置
        canvas.translate(_MaxRadius + mBallOverstepWidth, _MaxRadius + mBallOverstepWidth);
        drawProgressArc(canvas);                //画底层圆弧
        drawGradientProgressArc(canvas);        //画渐变的进度画弧
        drawProgressArcBall(canvas);            //画进度圆弧上的小球
        drawScaleArc(canvas, 80, SCALE_COUNT);  //画刻度圆弧
        drawArrow(canvas);                      //画箭头
        drawText(canvas);                       //画文字
    }

这里把坐标原点移动到中心位置,位置如下图,接下来就方便画一系列东西。

运行结果截图

2.drawProgressArc()

    /**
     * 画底层圆弧
     * @param canvas Canvas
     */
    private void drawProgressArc(Canvas canvas) {
        canvas.save();
        canvas.rotate(-202.5f);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mProgressArcWidth);
        mPaint.setColor(getResources().getColor(R.color.colorWhite2));
        float _ProgressArcL = mProgressArcRadius - mProgressArcWidth / 2.0f;
        RectF _ProgressArcRectF = new RectF(-_ProgressArcL, -_ProgressArcL, _ProgressArcL, _ProgressArcL);
        canvas.drawArc(_ProgressArcRectF, 0, TOTAL_ANGLE, false, mPaint);
        canvas.restore();
    }

画外面一层浅颜色的圆弧,为了方便计算我们先旋转一下画布,逆时针旋转202.5度,这样起点的位置就是0度了,接着就是根据圆弧的半径画个圆弧了。

现在的效果。

运行结果截图

3.drawGradientProgressArc()

    /**
     * 画渐变的进度画弧
     * @param canvas Canvas
     */
    private void drawGradientProgressArc(Canvas canvas) {
        canvas.save();
        canvas.rotate(-202.5f);
        mPaint.setStrokeWidth(mProgressArcWidth);
        SweepGradient _Shader = new SweepGradient(0, 0, GRADIENT_COLORS, null);
        mPaint.setShader(_Shader);
        mPaint.setColor(getResources().getColor(R.color.colorWhite1));
        float _TargetAngle = getTargetAngle(mCreditScore);
        float _ProgressArcL = mProgressArcRadius - mProgressArcWidth / 2.0f;
        RectF _ProgressArcRectF = new RectF(-_ProgressArcL, -_ProgressArcL, _ProgressArcL, _ProgressArcL);
        canvas.drawArc(_ProgressArcRectF, 0, _TargetAngle, false, mPaint);
        mPaint.setShader(null);
        canvas.rotate(22.5f);
        canvas.restore();
    }

现在画上面一层渐变的圆弧,同样的旋转一下画布,毕竟画法和drawProgressArc()是一样的,不过要设置渐变效果,渐变用的是梯度渲染SweepGradient,还有计算出当前分数所对占圆弧的角度,因为画到那个角度为止就好了。

运行结果截图

4.drawProgressArcBall()

    /**
     * 画进度圆弧上的小球
     * @param canvas Canvas
     */
    private void drawProgressArcBall(Canvas canvas) {
        canvas.save();
        float _TargetAngle = getTargetAngle(mCreditScore);
        canvas.rotate(_TargetAngle - 202.5f);
        mPaint.setStyle(Paint.Style.FILL);
        Bitmap _Ball = BitmapFactory.decodeResource(getResources(), R.drawable.ic_ball);
        canvas.drawBitmap(_Ball, mProgressArcRadius - _Ball.getHeight() / 2.0f, -(_Ball.getWidth() / 2.0f), mPaint);
        canvas.restore();
    }

画圆弧上的小球,这时我们要把画布旋转到x轴正方向到目标位置(画小球的地方)那,因为本来起点到x轴正方向是202.5度,所以使用起点到目标位置的度数减去202.5度就是要旋转的度数了,现在小球的位置x和y就好计算了,那个小球是我自己p的,由于ps术太差,p出来不太像。

运行结果截图

5.drawScaleArc()

    /**
     * 画刻度圆弧
     * @param canvas Canvas
     * @param pAlpha 没有到达的透明度 0~255
     * @param pCount 点的个数
     */
    private void drawScaleArc(Canvas canvas, int pAlpha, int pCount) {
        canvas.save();
        canvas.rotate(-202.5f);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(getResources().getColor(R.color.colorWhite3));
        mPaint.setStrokeWidth(mScaleArcWidth);
        float _ScaleArcL = mScaleArcRadius - mScaleArcWidth / 2.0f;
        RectF _ScaleArcRectF = new RectF(-_ScaleArcL, -_ScaleArcL, _ScaleArcL, _ScaleArcL);
        //画完每个刻度所要旋转的度数
        float _DialSpacing = (TOTAL_ANGLE - pCount) / (pCount - 1) + 1;
        float _TargetAngle = getTargetAngle(mCreditScore);
        float _CurrentAngle = 0;
        boolean _SetAlpha = false;
        for (int i = 0; i < pCount; i++) {
            if (_CurrentAngle > _TargetAngle && !_SetAlpha) {
                //设置未达到的点的透明度
                mPaint.setAlpha(pAlpha);
                _SetAlpha = true;
            }
            canvas.drawArc(_ScaleArcRectF, 0, 1, false, mPaint);
            canvas.rotate(_DialSpacing);
            _CurrentAngle += _DialSpacing;
        }
        //恢复透明度
        mPaint.setAlpha(255);
        canvas.restore();
    }

画里面那个一个点一个点的刻度圆弧,先逆时针旋转202.5度,计算出画完每个刻度所要旋转的度数,这里我设置每个点为一个度数的跨度,每画完每个点就旋转一下坐标轴再画下一个点,到达目标位置后就改下透明度画剩下的点。

运行结果截图

运行结果截图

6.drawArrow()

    /**
     * 画箭头
     * @param canvas Canvas
     */
    private void drawArrow(Canvas canvas) {
        canvas.save();
        float _TargetAngle = getTargetAngle(mCreditScore);
        canvas.rotate(_TargetAngle - 202.5f);
        mPaint.setStyle(Paint.Style.FILL);
        Bitmap _Arrow = BitmapFactory.decodeResource(getResources(), R.drawable.ic_arrow);
        int _MinRadius = Math.min(mProgressArcRadius, mScaleArcRadius);
        float _Left;
        if (_MinRadius == mScaleArcRadius) {
            _Left = mScaleArcRadius - mScaleArcWidth / 2 - mArrowSpacing - _Arrow.getWidth();
        } else {
            _Left = mProgressArcRadius - mProgressArcWidth / 2 - mArrowSpacing - _Arrow.getWidth();
        }
        float _Top = -(_Arrow.getHeight() / 2.0f);
        canvas.drawBitmap(_Arrow, _Left, _Top, mPaint);
        canvas.restore();
    }

画箭头,这个和画那个圆弧上的小球差不多的,那个箭头也是我p的。

运行结果截图

7.drawText()

    /**
     * 画文字
     * @param canvas Canvas
     */
    private void drawText(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(getResources().getColor(R.color.colorWhite1));
        //信用分数
        mPaint.setTextSize(mCreditScoreTextSize);
        String _CreditScore = String.valueOf(mCreditScore);
        float _Width = mPaint.measureText(_CreditScore);
        Rect _Rect = new Rect();
        mPaint.getTextBounds(_CreditScore, 0, _CreditScore.length(), _Rect);
        float _Y = 0;
        canvas.drawText(_CreditScore, -(_Width / 2.0f), _Y, mPaint);
        //信用级别
        _Y = _Y - _Rect.height() - mTextSpacing;
        mPaint.setTextSize(mCreditLevelTextSize);
        String _CreditLevel = getCreditLevel(mCreditScore);
        _Width = mPaint.measureText(_CreditLevel);
        mPaint.getTextBounds(_CreditLevel, 0, _CreditLevel.length(), _Rect);
        canvas.drawText(_CreditLevel, -(_Width / 2.0f), _Y, mPaint);
        //BETA
        _Y = _Y - _Rect.height() - mTextSpacing;
        mPaint.setTextSize(mBetaTextSize);
        mPaint.setAlpha(150);
        _Width = mPaint.measureText(BETA);
        canvas.drawText(BETA, -(_Width / 2.0f), _Y, mPaint);
        //评估时间
        String _EvaluationTime = EVALUATION_TIME + getDate();
        _Width = mPaint.measureText(_EvaluationTime);
        mPaint.getTextBounds(_EvaluationTime, 0, _EvaluationTime.length(), _Rect);
        mPaint.setTextSize(mEvaluationTimeTextSize);
        _Y = mTextSpacing + _Rect.height();
        canvas.drawText(_EvaluationTime, -(_Width / 2.0f), _Y, mPaint);
    }

画文字了,从分析那的图可以看出文字的位置了,从信用分数画起是最方便的了,该怎么画这个就不用我多说了。

运行结果截图

8.几个辅助方法

    /**
     * 根据信用分数计算出目标角度
     * @param pCreditScore 信用分数
     * @return 目标角度
     */
    private float getTargetAngle(float pCreditScore) {
        if (pCreditScore > 700) {
            return 180 + (pCreditScore - 700) * 0.18f;
        } else if (pCreditScore > 550) {
            return 45 + (pCreditScore - 550) * 0.9f;
        } else {
            return (pCreditScore - 350) * 0.225f;
        }
    }

在分析那有分析过了每个阶段的每一分所占的度数。

    /**
     * 根据信用分数获取信用级别
     * @param pCreditScore 信用分数
     * @return 信用级别
     */
    private String getCreditLevel(int pCreditScore) {
        if (pCreditScore >= 350 && pCreditScore < 550) {
            return CREDIT_LEVEL[0];
        } else if (pCreditScore >= 550 && pCreditScore < 600) {
            return CREDIT_LEVEL[1];
        } else if (pCreditScore >= 600 && pCreditScore < 650) {
            return CREDIT_LEVEL[2];
        } else if (pCreditScore >= 650 && pCreditScore < 700) {
            return CREDIT_LEVEL[3];
        } else if (pCreditScore >= 700 && pCreditScore <= 950){
            return CREDIT_LEVEL[4];
        } else {
            return CREDIT_LEVEL[0];
        }
    }

看分析那上个版本的支付宝的芝麻信用的图。

    /**
     * 获取yyyy-MM-dd格式的日期
     * @return yyyy-MM-dd
     */
    private String getDate() {
        SimpleDateFormat _DateFormat = new SimpleDateFormat("yyyy-MM-dd");
        return _DateFormat.format(new Date());
    }

用在评估时间那的日期。

(五)完整的代码

package com.ce.sesamecredit;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.view.View;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DialView extends View {
    /**
     * 圆弧的度数
     */
    private final static float TOTAL_ANGLE = 225.0f;
    /**
     * 刻度圆弧有56个点
     */
    private final static int SCALE_COUNT = 56;
    private final static String CREDIT_LEVEL[] = {"信用较差", "信用中等", "信用良好", "信用优秀", "信用极好"};
    private final static String BETA = "BETA";
    private final static String EVALUATION_TIME = "评估时间:";
    private final static int MIN_ALPHA = 0;
    private final static int MAX_ALPHA = 255;
    private final static int RED = 255;
    private final static int GREEN = 255;
    private final static int BLUE = 255;
    private final static int COLOR_TRANSPARENT = Color.argb(MIN_ALPHA, RED, GREEN, BLUE);
    private final static int COLOR_WHITE = Color.argb(MAX_ALPHA, RED, GREEN, BLUE);
    /**
     * 渐变进度圆弧的颜色
     */
    private final static int GRADIENT_COLORS[] = {COLOR_TRANSPARENT, COLOR_TRANSPARENT, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE};
    /**
     * 画笔
     */
    private Paint mPaint;
    /**
     * 刻度圆弧的半径
     */
    private int mScaleArcRadius;
    /**
     * 刻度圆弧的宽度
     */
    private int mScaleArcWidth;
    /**
     * 进度圆弧的半径
     */
    private int mProgressArcRadius;
    /**
     * 进度圆弧的宽度
     */
    private int mProgressArcWidth;
    /**
     * 进度圆弧上的小圆点的半径
     */
    private int mBallOverstepWidth;
    /**
     * BETA的字体大小
     */
    private int mBetaTextSize;
    /**
     * 信用级别的字体大小
     */
    private int mCreditLevelTextSize;
    /**
     * 信用分数的字体大小
     */
    private int mCreditScoreTextSize;
    /**
     * 评估时间的字体大小
     */
    private int mEvaluationTimeTextSize;
    /**
     * 字上下行的间隔
     */
    private int mTextSpacing;
    /**
     * 箭头与圆弧的间隔
     */
    private int mArrowSpacing;
    /**
     * 信用分数
     */
    private int mCreditScore = 666;
    public DialView(Context context) {
        this(context, null);
    }
    public DialView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public DialView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        parseAttr(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
    }
    /**
     * 解析属性
     *
     * @param context      Context
     * @param attrs        AttributeSet
     * @param defStyleAttr defStyleAttr
     */
    private void parseAttr(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray _TypedArray = context.obtainStyledAttributes(attrs, R.styleable.DialView, defStyleAttr, 0);
        mScaleArcRadius = _TypedArray.getDimensionPixelSize(R.styleable.DialView_scaleArcRadius, dp2px(context, 100));
        mScaleArcWidth = _TypedArray.getDimensionPixelSize(R.styleable.DialView_scaleArcWidth, dp2px(context, 2));
        mProgressArcRadius = _TypedArray.getDimensionPixelSize(R.styleable.DialView_progressArcRadius, dp2px(context, 105));
        mProgressArcWidth = _TypedArray.getDimensionPixelSize(R.styleable.DialView_progressArcWidth, dp2px(context, 1));
        mBetaTextSize = _TypedArray.getDimensionPixelSize(R.styleable.DialView_betaTextSize, dp2px(context, 12));
        mCreditLevelTextSize = _TypedArray.getDimensionPixelSize(R.styleable.DialView_creditLevelTextSize, dp2px(context, 18));
        mCreditScoreTextSize = _TypedArray.getDimensionPixelSize(R.styleable.DialView_creditScoreTextSize, dp2px(context, 40));
        mEvaluationTimeTextSize = _TypedArray.getDimensionPixelSize(R.styleable.DialView_evaluationTimeTextSize, dp2px(context, 12));
        mTextSpacing = _TypedArray.getDimensionPixelSize(R.styleable.DialView_textSpacing, dp2px(context, 12));
        mArrowSpacing = _TypedArray.getDimensionPixelSize(R.styleable.DialView_arrowSpacing, dp2px(context, 5));
        _TypedArray.recycle();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int _WidthMode=MeasureSpec.getMode(widthMeasureSpec);
        int _HeightMode = MeasureSpec.getMode(heightMeasureSpec);
        Bitmap _Ball = BitmapFactory.decodeResource(getResources(), R.drawable.ic_ball);
        mBallOverstepWidth =(int) Math.ceil(_Ball.getHeight() / 2.0 - mProgressArcWidth / 2.0);
        if (_WidthMode != MeasureSpec.EXACTLY) {
            int _Width = Math.max(mScaleArcRadius, mProgressArcRadius) * 2 + mBallOverstepWidth * 2
                    + getPaddingLeft() + getPaddingRight();
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(_Width, MeasureSpec.EXACTLY);
        }
        if (_HeightMode != MeasureSpec.EXACTLY) {
            int _MaxRadius = Math.max(mScaleArcRadius, mProgressArcRadius);
            int _Height =(int) (_MaxRadius + _MaxRadius * Math.sin(Math.toRadians(22.5))
                    + mBallOverstepWidth + _Ball.getHeight() / 2 + getPaddingTop() + getPaddingBottom());
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(_Height, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        int _MaxRadius = Math.max(mProgressArcRadius, mScaleArcRadius);
        //移动原点到中心位置
        canvas.translate(_MaxRadius + mBallOverstepWidth, _MaxRadius + mBallOverstepWidth);
        drawProgressArc(canvas);                //画底层圆弧
        drawGradientProgressArc(canvas);        //画渐变的进度画弧
        drawProgressArcBall(canvas);            //画进度圆弧上的小球
        drawScaleArc(canvas, 80, SCALE_COUNT);  //画刻度圆弧
        drawArrow(canvas);                      //画箭头
        drawText(canvas);                       //画文字
    }
    /**
     * 画底层圆弧
     * @param canvas Canvas
     */
    private void drawProgressArc(Canvas canvas) {
        canvas.save();
        canvas.rotate(-202.5f);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mProgressArcWidth);
        mPaint.setColor(getResources().getColor(R.color.colorWhite2));
        float _ProgressArcL = mProgressArcRadius - mProgressArcWidth / 2.0f;
        RectF _ProgressArcRectF = new RectF(-_ProgressArcL, -_ProgressArcL, _ProgressArcL, _ProgressArcL);
        canvas.drawArc(_ProgressArcRectF, 0, TOTAL_ANGLE, false, mPaint);
        canvas.restore();
    }
    /**
     * 画渐变的进度画弧
     * @param canvas Canvas
     */
    private void drawGradientProgressArc(Canvas canvas) {
        canvas.save();
        canvas.rotate(-202.5f);
        mPaint.setStrokeWidth(mProgressArcWidth);
        SweepGradient _Shader = new SweepGradient(0, 0, GRADIENT_COLORS, null);
        mPaint.setShader(_Shader);
        mPaint.setColor(getResources().getColor(R.color.colorWhite1));
        float _TargetAngle = getTargetAngle(mCreditScore);
        float _ProgressArcL = mProgressArcRadius - mProgressArcWidth / 2.0f;
        RectF _ProgressArcRectF = new RectF(-_ProgressArcL, -_ProgressArcL, _ProgressArcL, _ProgressArcL);
        canvas.drawArc(_ProgressArcRectF, 0, _TargetAngle, false, mPaint);
        mPaint.setShader(null);
        canvas.restore();
    }
    /**
     * 画进度圆弧上的小球
     * @param canvas Canvas
     */
    private void drawProgressArcBall(Canvas canvas) {
        canvas.save();
        float _TargetAngle = getTargetAngle(mCreditScore);
        canvas.rotate(_TargetAngle - 202.5f);
        mPaint.setStyle(Paint.Style.FILL);
        Bitmap _Ball = BitmapFactory.decodeResource(getResources(), R.drawable.ic_ball);
        canvas.drawBitmap(_Ball, mProgressArcRadius - _Ball.getHeight() / 2.0f, -(_Ball.getWidth() / 2.0f), mPaint);
        canvas.restore();
    }
    /**
     * 画刻度圆弧
     * @param canvas Canvas
     * @param pAlpha 没有到达的透明度 0~255
     * @param pCount 点的个数
     */
    private void drawScaleArc(Canvas canvas, int pAlpha, int pCount) {
        canvas.save();
        canvas.rotate(-202.5f);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(getResources().getColor(R.color.colorWhite3));
        mPaint.setStrokeWidth(mScaleArcWidth);
        float _ScaleArcL = mScaleArcRadius - mScaleArcWidth / 2.0f;
        RectF _ScaleArcRectF = new RectF(-_ScaleArcL, -_ScaleArcL, _ScaleArcL, _ScaleArcL);
        //画完每个刻度所要旋转的度数
        float _DialSpacing = (TOTAL_ANGLE - pCount) / (pCount - 1) + 1;
        float _TargetAngle = getTargetAngle(mCreditScore);
        float _CurrentAngle = 0;
        boolean _SetAlpha = false;
        for (int i = 0; i < pCount; i++) {
            if (_CurrentAngle > _TargetAngle && !_SetAlpha) {
                //设置未达到的点的透明度
                mPaint.setAlpha(pAlpha);
                _SetAlpha = true;
            }
            canvas.drawArc(_ScaleArcRectF, 0, 1, false, mPaint);
            canvas.rotate(_DialSpacing);
            _CurrentAngle += _DialSpacing;
        }
        //恢复透明度
        mPaint.setAlpha(255);
        canvas.restore();
    }
    /**
     * 画箭头
     * @param canvas Canvas
     */
    private void drawArrow(Canvas canvas) {
        canvas.save();
        float _TargetAngle = getTargetAngle(mCreditScore);
        canvas.rotate(_TargetAngle - 202.5f);
        mPaint.setStyle(Paint.Style.FILL);
        Bitmap _Arrow = BitmapFactory.decodeResource(getResources(), R.drawable.ic_arrow);
        int _MinRadius = Math.min(mProgressArcRadius, mScaleArcRadius);
        float _Left;
        if (_MinRadius == mScaleArcRadius) {
            _Left = mScaleArcRadius - mScaleArcWidth / 2 - mArrowSpacing - _Arrow.getWidth();
        } else {
            _Left = mProgressArcRadius - mProgressArcWidth / 2 - mArrowSpacing - _Arrow.getWidth();
        }
        float _Top = -(_Arrow.getHeight() / 2.0f);
        canvas.drawBitmap(_Arrow, _Left, _Top, mPaint);
        canvas.restore();
    }
    /**
     * 画文字
     * @param canvas Canvas
     */
    private void drawText(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(getResources().getColor(R.color.colorWhite1));
        //信用分数
        mPaint.setTextSize(mCreditScoreTextSize);
        String _CreditScore = String.valueOf(mCreditScore);
        float _Width = mPaint.measureText(_CreditScore);
        Rect _Rect = new Rect();
        mPaint.getTextBounds(_CreditScore, 0, _CreditScore.length(), _Rect);
        float _Y = 0;
        canvas.drawText(_CreditScore, -(_Width / 2.0f), _Y, mPaint);
        //信用级别
        _Y = _Y - _Rect.height() - mTextSpacing;
        mPaint.setTextSize(mCreditLevelTextSize);
        String _CreditLevel = getCreditLevel(mCreditScore);
        _Width = mPaint.measureText(_CreditLevel);
        mPaint.getTextBounds(_CreditLevel, 0, _CreditLevel.length(), _Rect);
        canvas.drawText(_CreditLevel, -(_Width / 2.0f), _Y, mPaint);
        //BETA
        _Y = _Y - _Rect.height() - mTextSpacing;
        mPaint.setTextSize(mBetaTextSize);
        mPaint.setAlpha(150);
        _Width = mPaint.measureText(BETA);
        canvas.drawText(BETA, -(_Width / 2.0f), _Y, mPaint);
        //评估时间
        String _EvaluationTime = EVALUATION_TIME + getDate();
        _Width = mPaint.measureText(_EvaluationTime);
        mPaint.getTextBounds(_EvaluationTime, 0, _EvaluationTime.length(), _Rect);
        mPaint.setTextSize(mEvaluationTimeTextSize);
        _Y = mTextSpacing + _Rect.height();
        canvas.drawText(_EvaluationTime, -(_Width / 2.0f), _Y, mPaint);
    }
    /**
     * dp转px
     * @param pContext Context
     * @param pDpVal dp值
     * @return px值
     */
    private static int dp2px(Context pContext, int pDpVal) {
        float _Scale = pContext.getResources().getDisplayMetrics().density;
        return (int)(pDpVal * _Scale + 0.5f * (pDpVal >= 0 ? 1 : -1));
    }
    /**
     * 根据信用分数计算出目标角度
     * @param pCreditScore 信用分数
     * @return 目标角度
     */
    private float getTargetAngle(float pCreditScore) {
        if (pCreditScore > 700) {
            return 180 + (pCreditScore - 700) * 0.18f;
        } else if (pCreditScore > 550) {
            return 45 + (pCreditScore - 550) * 0.9f;
        } else {
            return (pCreditScore - 350) * 0.225f;
        }
    }
    /**
     * 根据信用分数获取信用级别
     * @param pCreditScore 信用分数
     * @return 信用级别
     */
    private String getCreditLevel(int pCreditScore) {
        if (pCreditScore >= 350 && pCreditScore < 550) {
            return CREDIT_LEVEL[0];
        } else if (pCreditScore >= 550 && pCreditScore < 600) {
            return CREDIT_LEVEL[1];
        } else if (pCreditScore >= 600 && pCreditScore < 650) {
            return CREDIT_LEVEL[2];
        } else if (pCreditScore >= 650 && pCreditScore < 700) {
            return CREDIT_LEVEL[3];
        } else if (pCreditScore >= 700 && pCreditScore <= 950){
            return CREDIT_LEVEL[4];
        } else {
            return CREDIT_LEVEL[0];
        }
    }
    /**
     * 获取yyyy-MM-dd格式的日期
     * @return yyyy-MM-dd
     */
    private String getDate() {
        SimpleDateFormat _DateFormat = new SimpleDateFormat("yyyy-MM-dd");
        return _DateFormat.format(new Date());
    }
}

五、使用

(一)布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.ce.sesamecredit.MainActivity">
    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar_layout"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        app:elevation="0dp"
        android:theme="@style/AppTheme.AppBarOverlay">
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:navigationIcon="@drawable/ic_arrow_back_black_24dp"
            app:title="芝麻信用"/>
    </android.support.design.widget.AppBarLayout>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="240dp"
        android:layout_below="@id/appbar_layout"
        android:background="@color/colorGreenBlue">
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp">
            <com.ce.sesamecredit.DialView
                android:id="@+id/dialview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                android:layout_centerHorizontal="true"
                app:progressArcRadius="105dp"
                app:progressArcWidth="1dp"
                app:scaleArcRadius="100dp"
                app:scaleArcWidth="2dp"
                app:betaTextSize="12dp"
                app:creditScoreTextSize="40dp"
                app:creditLevelTextSize="18dp"
                app:evaluationTimeTextSize="12dp"
                app:textSpacing="12dp"
                app:arrowSpacing="5dp"/>
            <LinearLayout
                android:layout_marginTop="20dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@id/dialview">
                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:gravity="center">
                    <TextView
                        android:layout_width="114dp"
                        android:layout_height="30dp"
                        android:layout_marginLeft="15dp"
                        android:background="@drawable/textview_boundaries"
                        android:textColor="#EEFFFFFF"
                        android:textAlignment="center"
                        android:gravity="center"
                        android:text="晒晒分"/>
                </LinearLayout>
                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:gravity="center">
                    <TextView
                        android:layout_width="114dp"
                        android:layout_height="30dp"
                        android:layout_marginRight="15dp"
                        android:background="@drawable/textview_boundaries"
                        android:textColor="#EEFFFFFF"
                        android:textAlignment="center"
                        android:gravity="center"
                        android:text="了解分"/>
                </LinearLayout>
            </LinearLayout>
        </RelativeLayout>
    </RelativeLayout>
</RelativeLayout>

(二)Activity

package com.ce.sesamecredit;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar _Toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(_Toolbar);
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main_menu, menu);
        return true;
    }
}

Similar Posts

Comments