Android自定义View实现仪表盘

最终效果

仪表盘演示

代码实现
package com.example.chartdemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

/**
 * 仪表View
 */
public class MeterChartView extends View {

    public MeterChartView(Context context) {
        this(context,null);
    }

    public MeterChartView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

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

    private int widthMode;
    private int heightMode;
    private int widthSize;
    private int heightSize;

    private Paint linePaint;
    private Paint fullPaint;
    private Paint textPaint;

    private float cX;
    private float cY;
    private float RADIUS;

    private float dataMax=240;//最大值
    private float sectionNumber=6;//区间个数
    private float scaleNumber=24;//刻度个数

    private float data=0;//当前数据值
    private String unit="";
    private float refreshData=0;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        widthMode = MeasureSpec.getMode(widthMeasureSpec);
        heightMode = MeasureSpec.getMode(heightMeasureSpec);
        widthSize = MeasureSpec.getSize(widthMeasureSpec);
        heightSize = MeasureSpec.getSize(heightMeasureSpec);

        cX=widthSize/2f;
        cY=heightSize/2f;

        if (cY<cX){
            RADIUS=cY-20;
        }else {
            RADIUS=cX-20;
        }

        linePaint=new Paint();
        linePaint.setAntiAlias(true);//消除锯齿
        linePaint.setColor(Color.WHITE);
        linePaint.setStrokeWidth(4);
        linePaint.setStyle(Paint.Style.STROKE);//决定样式,填充还是画线

        fullPaint=new Paint();
        fullPaint.setAntiAlias(true);//消除锯齿
        fullPaint.setStrokeWidth(4);

        textPaint=new Paint();
        textPaint.setAntiAlias(true);//消除锯齿
        textPaint.setTextSize(30);
        textPaint.setColor(Color.WHITE);
        textPaint.setTextAlign(Paint.Align.CENTER);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //刷屏幕扇形特效
        RectF mRectRefresh = new RectF(cX - RADIUS, cY-RADIUS, cX + RADIUS, cY + RADIUS);
        fullPaint.setColor(Color.RED);
        float refreshStep=240/dataMax;
        canvas.drawArc(mRectRefresh, 150, refreshData*refreshStep, true, fullPaint);

        //绘制背景黑色扇形
        fullPaint.setColor(Color.BLACK);
        canvas.drawCircle(cX,cY,RADIUS-20,fullPaint);

        //绘制扇形
        linePaint.setColor(Color.WHITE);
        RectF mRectF = new RectF(cX - RADIUS, cY-RADIUS, cX + RADIUS, cY + RADIUS);
        canvas.drawArc(mRectF, 150, 240, true, linePaint);

        //绘制遮罩扇形
        fullPaint.setColor(Color.BLACK);
        canvas.drawCircle(cX,cY,RADIUS-50,fullPaint);

        //绘制细线圆
        linePaint.setStrokeWidth(2);
        canvas.drawCircle(cX,cY,RADIUS-20,linePaint);

        float sectionAngle=240/sectionNumber;//每个大区间的角度值
        float sectionStep=dataMax/sectionNumber;
        for (int k=0;k<sectionNumber+1;k++){

            float keDuStartX = cX + RADIUS * (float) Math.cos((150+(sectionAngle*k)) / 180 * Math.PI);
            float keDuStartY = cY + RADIUS * (float) Math.sin((150+(sectionAngle*k)) / 180 * Math.PI);
            float keDuEndX = cX + (RADIUS-50) * (float) Math.cos((150+(sectionAngle*k)) / 180 * Math.PI);
            float keDuEndY = cY + (RADIUS-50) * (float) Math.sin((150+(sectionAngle*k)) / 180 * Math.PI);
            //绘制区间刻度线
            linePaint.setStrokeWidth(4);
            canvas.drawLine(keDuStartX,keDuStartY,keDuEndX,keDuEndY,linePaint);

            int textValue= (int) (k*sectionStep);
            //绘制刻度值
            if (keDuEndX<cX){
                //左侧刻度
                textPaint.setColor(Color.parseColor("#FF9800"));
                textPaint.setTextSize(30);
                canvas.drawText(textValue+"",keDuEndX+30,keDuEndY+10,textPaint);
            }else if (keDuEndX==cX){
                //中间刻度
                textPaint.setColor(Color.parseColor("#FF9800"));
                textPaint.setTextSize(30);
                canvas.drawText(textValue+"",keDuEndX,keDuEndY+30,textPaint);
            }else {
                //右侧刻度
                textPaint.setColor(Color.parseColor("#FF9800"));
                textPaint.setTextSize(30);
                canvas.drawText(textValue+"",keDuEndX-30,keDuEndY+10,textPaint);
            }

        }

        float scaleAngle=240/scaleNumber;//每个刻度的角度值
        for (int k=0;k<scaleNumber;k++){

            float keDuStartX = cX + RADIUS * (float) Math.cos((150+(scaleAngle*k)) / 180 * Math.PI);
            float keDuStartY = cY + RADIUS * (float) Math.sin((150+(scaleAngle*k)) / 180 * Math.PI);
            float keDuEndX = cX + (RADIUS-30) * (float) Math.cos((150+(scaleAngle*k)) / 180 * Math.PI);
            float keDuEndY = cY + (RADIUS-30) * (float) Math.sin((150+(scaleAngle*k)) / 180 * Math.PI);

            //绘制细刻度线
            linePaint.setStrokeWidth(2);
            canvas.drawLine(keDuStartX,keDuStartY,keDuEndX,keDuEndY,linePaint);
        }

        //绘制指针
        float dataStep=240/dataMax;
        float pointerStartX = cX;
        float pointerStartY = cY;
        float pointerEndX = cX + (RADIUS-40) * (float) Math.cos((150+(data*dataStep)) / 180 * Math.PI);
        float pointerEndY = cY + (RADIUS-40) * (float) Math.sin((150+(data*dataStep)) / 180 * Math.PI);
        linePaint.setStrokeWidth(6);
        linePaint.setColor(Color.RED);
        canvas.drawLine(pointerStartX,pointerStartY,pointerEndX,pointerEndY,linePaint);
        fullPaint.setColor(Color.RED);
        canvas.drawCircle(cX,cY,20,fullPaint);
        //绘制数据值
        int nowData= (int) data;
        textPaint.setColor(Color.WHITE);
        textPaint.setTextSize(40);
        canvas.drawText(nowData+unit,cX,cY+(RADIUS/2f),textPaint);

    }

    /**
     * 初始化轴数据
     * @param dataMax
     * @param sectionNumber
     * @param scaleNumber
     */
    public void initConstant(int dataMax,int sectionNumber,int scaleNumber){
        this.dataMax=dataMax;
        this.sectionNumber=sectionNumber;
        this.scaleNumber=scaleNumber;
        postInvalidate();
    }

    /**
     * 设置数据
     * @param dataValue 数据
     * @param unit 单位
     */
    public void setData(int dataValue,String unit){
        data=dataValue;
        this.unit=unit;
        postInvalidate();
    }

    /**
     * 刷屏幕特效
     * @param data
     */
    public void refreshUi(int data){
        refreshData=data;
        postInvalidate();
    }


}

使用方法

  1. 在xml中引用
<LinearLayout
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:ignore="MissingConstraints">

        <com.example.chartdemo.MeterChartView
            android:id="@+id/left"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="600px"/>

        <com.example.chartdemo.MeterChartView
            android:id="@+id/right"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="600px"/>

    </LinearLayout>
  1. 初始化参数与数据
package com.example.chartdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.example.chartdemo.util.ActionCallback;
import com.example.chartdemo.util.BarHeartbeat;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        initMeter();
        
    }

    private MeterChartView left;
    private MeterChartView right;
    private void initMeter() {

        left=(MeterChartView) findViewById(R.id.left);
        left.initConstant(240,6,24);

        right=(MeterChartView) findViewById(R.id.right);
        right.initConstant(8000,8,40);

        reSetLeftMeter(0,240);
        reSetRightMeter(0,80);
        
    }

    /**
     * 重置仪表
     * @param realData 真实数据值
     * @param maxData 模拟旋转最大值
     */
    public void reSetLeftMeter(int realData, int maxData){
        new BarHeartbeat().start(4, realData, maxData, new ActionCallback() {
            @Override
            public void toDo(Object o) {
                left.setData((int)o,"km/h");
            }
        });

        new BarHeartbeat().start(4, 0, maxData, new ActionCallback() {
            @Override
            public void toDo(Object o) {
                left.refreshUi((int)o);
            }
        });
    }

    /**
     * 重置仪表
     * @param realData 真实数据值
     * @param maxData 模拟旋转最大值
     */
    private void reSetRightMeter(int realData, int maxData){
        new BarHeartbeat().start(10, realData, maxData, new ActionCallback() {
            @Override
            public void toDo(Object o) {
                right.setData((int)o*100,"r/min");
            }
        });

        new BarHeartbeat().start(10, 0, maxData, new ActionCallback() {
            @Override
            public void toDo(Object o) {
                right.refreshUi((int)o*100);
            }
        });
    }

}

辅助工具类

package com.example.chartdemo.util;

import android.os.Looper;
import android.os.Message;

import java.util.ArrayList;
import java.util.List;

/**
 * 心跳计时器
 */
public class BarHeartbeat {

    //事件标签
    public final int ACTION_TAG =0xFF;
    //要回调的接口
    private ActionCallback thisActionDo;

    //Handler事件监听器
    private android.os.Handler mHandler = new android.os.Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case ACTION_TAG:

                    thisActionDo.toDo(msg.obj);

                    if ((int)msg.obj==maxValue && forTag==true){
                        doHUi();
                    }
                    break;
            }

        }
    };


    int dataValue;
    int maxValue;
    int countDownTime;
    public void start(int countDownTime,int dataValue,int maxValue,ActionCallback actionDo){
        this.dataValue=dataValue;
        this.maxValue=maxValue;
        this.countDownTime=countDownTime;
        thisActionDo=actionDo;
        for (int a=0;a<maxValue;a++){
            Message msg = mHandler.obtainMessage();
            msg.what = ACTION_TAG;
            msg.obj=a+1;
            mHandler.sendMessageDelayed(msg,countDownTime*a);
        }
    };

    private boolean forTag=true;
    private void doHUi(){
        forTag=false;
        for (int k=0;k<=maxValue-dataValue;k++){
            Message msg2 = mHandler.obtainMessage();
            msg2.what = ACTION_TAG;
            msg2.obj=maxValue-k;
            mHandler.sendMessageDelayed(msg2,countDownTime*k);
        }
    }

}
//回调接口
public interface ActionCallback {
    void toDo(Object o);
}
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Android仪表盘控件是一种常用于显示和控制数据的UI元素。它可以在移动设备的屏幕上呈现出类似于汽车仪表盘的视图,用于展示各种指标、数据和信息。Android提供了多种方式来实现仪表盘控件,开发者可以根据需求选择合适的控件来构建自定义仪表盘。 常见的Android仪表盘控件包括进度条、圆形进度条等。进度条可用于表示某项任务的进度,用户可以通过触摸或拖动进度条来控制任务的进行。圆形进度条则更加直观地展示了进度的百分比,适用于较为复杂的进度控制场景。 仪表盘控件还可以用于显示各种指标和数据,比如速度、温度、电量等。开发者可以根据具体需求绘制仪表盘的刻度、指针和标签,通过改变刻度和指针的位置来展示相应的数值。这些仪表盘控件可以帮助用户更直观地了解数据的变化,提供更好的用户交互体验。 除了上述基本功能,Android仪表盘控件还可以通过添加动画效果自定义样式来提升用户体验。开发者可以为仪表盘控件添加缓慢的指针移动效果或渐变的色彩变化,增加仪表盘的视觉吸引力。同时,也可以根据应用的需求自定义仪表盘的外观,使其更加符合应用的整体风格。 总之,Android仪表盘控件是一种非常有用和常见的UI元素,可以用于展示和控制各种指标、数据和信息。开发者可以根据具体需求选择适合的控件,并通过添加动画和自定义样式来提升用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绝命三郎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值