Android仪表盘效果

效果如图,带动画

实现步骤,只需四步:

1.在res/values文件夹下新建attrs.xml文件,自定义属性如下:

<?xml version="1.0" encoding="utf-8"?>

    <attr name="color_dial_lower" format="color"/>

    <attr name="color_dial_middle" format="color"/>

    <attr name="color_dial_high" format="color"/>

    <attr name="text_size_dial" format="dimension"/>

    <attr name="stroke_width_dial" format="dimension"/>

    <attr name="radius_circle_dial" format="dimension"/>

    <attr name="text_title_dial" format="string"/>

    <attr name="text_title_size" format="dimension"/>

    <attr name="text_title_color" format="color"/>

    <attr name="text_size_value" format="dimension"/>

    <attr name="animator_play_time" format="integer"/>

    <declare-styleable name="ClockView">

        <attr name="color_dial_lower"/>

        <attr name="color_dial_middle"/>

        <attr name="color_dial_high"/>

        <attr name="text_size_dial"/>

        <attr name="stroke_width_dial"/>

        <attr name="radius_circle_dial"/>

        <attr name="text_title_dial"/>

        <attr name="text_title_size"/>

        <attr name="text_title_color"/>

        <attr name="text_size_value"/>

        <attr name="animator_play_time"/>

</declare-styleable>

</resources>

2.新建自定义控件ClockView.java:

public class ClockViewextends View {

private static final int DEFAULT_COLOR_LOWER = Color.parseColor("#ffffff");//下游颜色

    private static final int DEFAULT_COLOR_MIDDLE = Color.parseColor("#ffffff");//中间颜色

    private static final int DEFAULT_COLOR_HIGH = Color.parseColor("#ffffff");//高的颜色

    private static final int DEAFAULT_COLOR_TITLE = Color.parseColor("#ffffff");//标题颜色

    private static final int DEFAULT_TEXT_SIZE_DIAL =8;//仪表盘字体大小

    private static final int DEFAULT_STROKE_WIDTH =3;//仪表盘线的宽度

    private static final int DEFAULT_RADIUS_DIAL =128;//转盘半径

    private static final int DEAFAULT_TITLE_SIZE =10;//标题大小

    private static final int DEFAULT_VALUE_SIZE =14;//下面百分比字体 值的大小

    private static final int DEFAULT_ANIM_PLAY_TIME =2000;//动画时间

    private int colorDialLower;//转盘下游颜色

    private int colorDialMiddle;//转盘中游颜色

    private int colorDialHigh;//转盘上游颜色

    private int textSizeDial;//转盘文字大小

    private int strokeWidthDial;//转盘中风宽度

    private StringtitleDial;//转盘标题

    private int titleDialSize;//转盘标题大小

    private int titleDialColor;//转盘标题颜色

    private int valueTextSize;//值的大小

    private int animPlayTime;//动画时间

    private int radiusDial;//转盘半径

    private int mRealRadius;//实际半径

    private float currentValue;//当前值

    private PaintarcPaint;//弧的画笔

    private RectFmRect;//矩形

    private PaintpointerPaint;//指针

    private Paint.FontMetricsfontMetrics;//字体度量

    private PainttitlePaint;//标题画笔

    private PathpointerPath;//指示器路径

    private float mDegreesRotate ; //每一份的角度

    public ClockView(Context context) {

this(context, null);

    }

public ClockView(Context context, @Nullable AttributeSet attrs) {

this(context, attrs, 0);

    }

public ClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

        initAttrs(context, attrs);

        initPaint();

    }

private void initAttrs(Context context, AttributeSet attrs){

//获得样式属性

        TypedArray attributes = context.obtainStyledAttributes(attrs,R.styleable.ClockView);

        colorDialLower = attributes.getColor(R.styleable.ClockView_color_dial_lower, DEFAULT_COLOR_LOWER);//转盘下游颜色

        colorDialMiddle = attributes.getColor(R.styleable.ClockView_color_dial_middle, DEFAULT_COLOR_MIDDLE);//转盘中游颜色

        colorDialHigh = attributes.getColor(R.styleable.ClockView_color_dial_high, DEFAULT_COLOR_HIGH);//转盘上游颜色

        textSizeDial = (int) attributes.getDimension(R.styleable.ClockView_text_size_dial, sp2px(DEFAULT_TEXT_SIZE_DIAL));//文字大小

        strokeWidthDial = (int) attributes.getDimension(R.styleable.ClockView_stroke_width_dial, dp2px(DEFAULT_STROKE_WIDTH));//线条宽度

        radiusDial = (int) attributes.getDimension(R.styleable.ClockView_radius_circle_dial, dp2px(DEFAULT_RADIUS_DIAL));//转盘半径周期

        titleDial = attributes.getString(R.styleable.ClockView_text_title_dial);//转盘标题

        titleDialSize = (int) attributes.getDimension(R.styleable.ClockView_text_title_size, dp2px(DEAFAULT_TITLE_SIZE));//转盘标题大小

        titleDialColor = attributes.getColor(R.styleable.ClockView_text_title_color, DEAFAULT_COLOR_TITLE);//转盘标题颜色

        valueTextSize = (int) attributes.getDimension(R.styleable.ClockView_text_size_value, dp2px(DEFAULT_VALUE_SIZE));//转盘值

        animPlayTime = attributes.getInt(R.styleable.ClockView_animator_play_time, DEFAULT_ANIM_PLAY_TIME);//动画时间

    }

private void initPaint(){

//圆弧画笔

        arcPaint =new Paint();

        arcPaint.setAntiAlias(true);//抗锯齿

        arcPaint.setStyle(Paint.Style.STROKE);//风格

        arcPaint.setStrokeWidth(strokeWidthDial);//转盘中风宽度

//指针画笔

        pointerPaint =new Paint();

        pointerPaint.setAntiAlias(true);//抗锯齿

        pointerPaint.setTextSize(textSizeDial);//文字大小

        pointerPaint.setTextAlign(Paint.Align.CENTER);//排成一行 居中

        fontMetrics =pointerPaint.getFontMetrics();//获得字体度量

//标题画笔

        titlePaint =new Paint();

        titlePaint.setAntiAlias(true);//抗锯齿

        titlePaint.setTextAlign(Paint.Align.CENTER);//排成一行 居中

        titlePaint.setFakeBoldText(true);//设置黑体

//指针条

        pointerPath =new Path();

    }

@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 mWidth, mHeight;

        if (widthMode == MeasureSpec.EXACTLY){//精确的

            mWidth = widthSize;

        }else {

mWidth = getPaddingLeft() +radiusDial *2 + getPaddingRight();

            if (widthMode == MeasureSpec.AT_MOST){//大概

                mWidth = Math.min(mWidth, widthSize);

            }

}

if (heightMode == MeasureSpec.EXACTLY){//精确的

            mHeight = heightSize;

        }else {

mHeight = getPaddingTop() +radiusDial *2 + getPaddingBottom();

            if (heightMode == MeasureSpec.AT_MOST){//大概

                mHeight = Math.min(mHeight, heightSize);

            }

}

//设置测量的大小

        setMeasuredDimension(mWidth, mHeight);

        radiusDial = Math.min((getMeasuredWidth() - getPaddingLeft() - getPaddingRight()),

                (getMeasuredHeight() - getPaddingTop() - getPaddingBottom())) /2;

        mRealRadius =radiusDial -strokeWidthDial /2;//真实的半径

        mRect =new RectF(-mRealRadius, -mRealRadius, mRealRadius, mRealRadius);//矩形 左上右下

    }

@Override

    protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

        drawArc(canvas);//画弧

        drawPointerLine(canvas);//画指针线

        drawTitleDial(canvas);//画标题

        drawPointer(canvas);//画指针

    }

//画弧

    private void drawArc(Canvas canvas){

//画布转换

        canvas.translate(getPaddingLeft() +radiusDial, getPaddingTop() +radiusDial);

        arcPaint.setColor(colorDialLower);//转盘下游颜色

        canvas.drawArc(mRect, 135, 54, false, arcPaint);

        arcPaint.setColor(colorDialMiddle);//转盘中游颜色

        canvas.drawArc(mRect, 189, 162, false, arcPaint);

        arcPaint.setColor(colorDialHigh);//转盘高游颜色

        canvas.drawArc(mRect, 351, 54, false, arcPaint);

    }

//画指针线 默认100 份

    private void drawPointerLine(Canvas canvas){

float degreesRotate =2.7f ; //每一份的角度

        mDegreesRotate = degreesRotate;

        //画布旋转

        canvas.rotate(135);

        for (int i=0; i<101; i++){//一共需要绘制101个表针

            if (i <=20){

pointerPaint.setColor(colorDialLower);

            }else if (i<=80){

pointerPaint.setColor(colorDialMiddle);

            }else {

pointerPaint.setColor(colorDialHigh);

            }

if (i %10 ==0){//长表针

                pointerPaint.setStrokeWidth(2);

                canvas.drawLine(radiusDial, 0, radiusDial -strokeWidthDial - dp2px(15), 0, pointerPaint);

                drawPointerText(canvas, i, degreesRotate);

            }else {//短表针

                pointerPaint.setStrokeWidth(1);

                canvas.drawLine(radiusDial, 0, radiusDial -strokeWidthDial - dp2px(5), 0, pointerPaint);

            }

canvas.rotate(degreesRotate);

        }

}

//画指针线 260 份

    private void drawPointerLine260(Canvas canvas){

float degreesRotate =1.04f ; //每一份的角度

        mDegreesRotate = degreesRotate;

        //画布旋转

        canvas.rotate(135);

        for (int i=0; i<261; i++){//一共需要绘制261个表针

            if (i <=20){

pointerPaint.setColor(colorDialLower);

            }else if (i<=80){

pointerPaint.setColor(colorDialMiddle);

            }else {

pointerPaint.setColor(colorDialHigh);

            }

if (i %20 ==0){//长表针

//                pointerPaint.setStrokeWidth(6);

                pointerPaint.setStrokeWidth(2);

                canvas.drawLine(radiusDial, 0, radiusDial -strokeWidthDial - dp2px(15), 0, pointerPaint);

                drawPointerText(canvas, i, degreesRotate);

            }else if (i %2 ==0){//短表针

//                pointerPaint.setStrokeWidth(3);

                pointerPaint.setStrokeWidth(1);

                canvas.drawLine(radiusDial, 0, radiusDial -strokeWidthDial - dp2px(5), 0, pointerPaint);

            }

canvas.rotate(degreesRotate);

        }

}

//画指针文字

    private void drawPointerText(Canvas canvas, int i, float degreesRotate){

canvas.save();

        int currentCenterX = (int) (radiusDial -strokeWidthDial - dp2px(21) -pointerPaint.measureText(String.valueOf(i)) /2);

        canvas.translate(currentCenterX, 0);

        canvas.rotate(360 -135 - degreesRotate * i);        //坐标系总旋转角度为360度

        int textBaseLine = (int) (0 + (fontMetrics.bottom -fontMetrics.top) /2 -fontMetrics.bottom);

        canvas.drawText(String.valueOf(i), 0, textBaseLine, pointerPaint);

        canvas.restore();

    }

//画标题的值

    private void drawTitleDial(Canvas canvas){

titlePaint.setColor(titleDialColor);

        titlePaint.setTextSize(titleDialSize);

        canvas.rotate( -47.7f);      //恢复坐标系为起始中心位置

        canvas.drawText(titleDial, 0, -radiusDial /3, titlePaint);

        if (currentValue <=20){

titlePaint.setColor(colorDialLower);

        }else if (currentValue <=80){

titlePaint.setColor(colorDialMiddle);

        }else {

titlePaint.setColor(colorDialHigh);

        }

titlePaint.setTextSize(valueTextSize);

        canvas.drawText(currentValue +"%", 0, radiusDial *2/3, titlePaint);

    }

//画旋转的指针

    private void drawPointer(Canvas canvas){

int currentDegree = (int) (currentValue *mDegreesRotate +135);

        canvas.rotate(currentDegree);

        pointerPath.moveTo(radiusDial -strokeWidthDial - dp2px(12), 0);

        pointerPath.lineTo(0, -dp2px(5));

        pointerPath.lineTo(-12, 0);

        pointerPath.lineTo(0, dp2px(5));

        pointerPath.close();

        canvas.drawPath(pointerPath,titlePaint);

    }

//设置完成程度

    public void setCompleteDegree(float degree){

ValueAnimator animator = ValueAnimator.ofFloat(0, degree);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

            public void onAnimationUpdate(ValueAnimator animation) {

currentValue = (float)(Math.round((float) animation.getAnimatedValue() *100)) /100;

                invalidate();

            }

});

        animator.setInterpolator(new AccelerateDecelerateInterpolator());

        animator.setDuration(animPlayTime);

        animator.start();

    }

protected int dp2px(int dpVal) {

return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics());

    }

protected int sp2px(int spVal) {

return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, getResources().getDisplayMetrics());

    }

public void setNum(float degree){

currentValue = degree;

        invalidate();

    }

//动态设置指针最终位置,附带动画效果

    public void setNumAnimator(float degree){

ValueAnimator animator = ValueAnimator.ofFloat(currentValue, degree);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

            public void onAnimationUpdate(ValueAnimator animation) {

currentValue = (float)(Math.round((float) animation.getAnimatedValue() *100)) /100;

                invalidate();

            }

});

        animator.setInterpolator(new AccelerateDecelerateInterpolator());

        animator.setDuration(1000);

        animator.start();

    }

}

3.xml里面使用控件:

<LinearLayout

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:gravity="center_vertical"

    android:paddingLeft="10dp"

    android:paddingRight="10dp"

    android:orientation="horizontal">

        android:id="@+id/clock_view"

        android:layout_width="0dp"

        android:layout_weight="1"

        android:layout_marginRight="20dp"

        android:layout_height="wrap_content"

        app:text_title_dial="完成率"

        />

        android:id="@+id/clock_view2"

        android:layout_width="0dp"

        android:layout_marginLeft="20dp"

        android:layout_weight="1"

        android:layout_height="wrap_content"

        app:text_title_dial="完成率"/>

</LinearLayout>

4.代码动态设置值:

左图:

clockView.setCompleteDegree(32.25f); //设置指针最终位置,附带动画效果

右图:

String aaa ="50.12";

float num1 = Float.valueOf(aaa);

clockView2.setNumAnimator(num1);  //设置指针最终位置,附带动画效果

到这里就开发完成了,可以看看效果。

如果所需刻度总数不是100,代码里面有个刻度总数260的方法,可以参照修改。

参考:

https://www.cnblogs.com/changyiqiang/p/10874639.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354