千古八方的博客

  • 程序开发
    • Android
    • Spring Boot
  • 数据私有化
    • NAS
    • 私有云
    • 内网穿透
    • 服务器
  • 留言
  • 搞事
进学
为学无间断,如流水行云,日进而不已也
  1. 首页
  2. Android
  3. 正文

Android widget - 完全可配的炫酷仪表盘

2022年6月20日 196点热度 1人点赞 0条评论
目录
Widget部件介绍
使用说明及Demo
GaugeView.java 源代码
widget 链接集合
Widget部件介绍

项目里需要一个完全可自定义的仪表盘,其中需要分三个警示断来提示,这里以温度为例,0°C以下用蓝色刻度,70°C以下用绿色刻度,70°C以上红色刻度。

由于需要展示十几个这种图标,并且更新频率接近实时,所以对性能要求比较高。

改了几版后达到要求,最终效果图如下:

 

为了保持移植到其他项目的便利性,未使用xml的属性值来定义。   并且只有一个文件,复制粘贴简单;大部分参数都设有默认值,并且支持所有的参数可以通过配置来修改。

使用说明及Demo

GaugeView gaugeView = findViewById(R.id.gaugu);
        
GaugeView.Config cfg = new GaugeView.Config();
cfg.labelText = "温度"; // 所有项均可在此配置修改
gaugeView.config(cfg); 

// 中间值格式,最大值,最小值,刻度线(dialStep的值,必须被最大值减最小值整除),第一个警戒线,第二个警戒线
gaugeView.init("%.1f°C", 100f, -30f, 10, 10, 70);
gaugeView.updateVal(30f);

 

GaugeView.java 源代码

package org.tcshare.widgets;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import androidx.annotation.Nullable;

import java.text.DecimalFormat;

/**
 * @author 爱T鱼
 * @date 2021年1月5日
 */
public class GaugeView extends View {
    protected int bgRound1 = Color.parseColor("#99122a73");
    protected int bgRound2 = Color.parseColor("#66122a73");
    protected int bgRound3 = Color.parseColor("#22142f80");
    protected int bgRound4 = Color.parseColor("#0e2267");
    protected int circle1 = Color.parseColor("#0d1d4c");
    protected int bgRadius1;
    protected int bgRadius2;
    protected int bgRadius3;
    protected int bgRadius4;
    protected int circleRadius1;
    protected int circleRadius2;

    // 背景圆画笔
    protected Paint roundPaint;
    protected int mWidth;
    protected int mHeight;
    protected int mCx;
    protected int mCy;
    protected int radius;
    // 刻度画笔
    protected Paint pointerPaint;
    protected float textSizeDial = 14; // 刻度字体大小
    protected Paint.FontMetrics pointerFontMetrics;
    // 标题画笔
    protected Paint labelPaint;
    // 长刻度线半径
    protected int radiusDial;
    protected float maxVal = 150f;
    protected float minVal = -50f;

    protected int lowDialLimit = 20; // 低段温度警戒线
    protected int highDialLimit = 80; // 中段警戒线
    protected int colorDialLower = Color.parseColor("#00faff"); // 低中高三个色段表盘刻度
    protected int colorDialMiddle = Color.GREEN;
    protected int colorDialHigh = Color.RED;

    protected int lengthLDial = 16;// 长刻度线的长度
    protected int lengthSDial = 8;// 短刻度线的长度
    protected float strokeLDial = 3;
    protected float strokeSDial = 1;
    protected float circleRadius1StrokeWidth = 2f;
    protected float circleRadius2StrokeWidth = 4f;
    protected float pointerLineDegree = 135; // 多少角度
    protected float arcStartDegree = pointerLineDegree; // 弧线起始角度
    protected Paint valPaint;

    protected Paint.FontMetrics labelFontMetrics;
    protected Paint.FontMetrics valFontMetrics;
    protected String valText = "";
    protected float realVal; // 真实的值
    protected float valTextSize = 40f;// 值字体大小
    protected int valTextColor = Color.WHITE;// 值字体颜色
    protected String labelText = "";
    protected float labelTextSize = 20f;// 标签字体
    protected int labelTextColor = Color.WHITE;// 标签字体颜色
    protected Paint arcPaint;
    protected RectF mArcRect = new RectF(), mCircleArcRect1 = new RectF(), mCircleArcRect2 = new RectF(); // 指针(半扇形)及描边线
    protected float arcPaintStrokeWidth = 2f; // 描边宽度
    protected int arcColor = Color.parseColor("#ff8e00");
    protected int[] colors = new int[]{Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, arcColor};
    protected int[] colorsArcLine = new int[]{arcColor, arcColor, Color.TRANSPARENT};
    protected Shader arcShader;
    protected Shader arcLineShader;
    protected float sweepAngle = 0;
    protected String formatter = "%.0f";
    protected int drawDialNum = 100;
    protected int dialStep = 10; // 分度
    protected float valRange;
    protected float stepDegree;
    protected float sRoateDegree;
    protected DecimalFormat decimalFormat = new DecimalFormat(".0");
    private Bitmap pLineCache; // 绘制点线缓存,该步骤耗时长

    public GaugeView(Context context) {
        super(context);
        init();
    }


    public GaugeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

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

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


    protected void init() {
        // 初始化圆背景画笔
        roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        // 扫描扇
        arcPaint = new Paint();
        arcPaint.setAntiAlias(true);


        // 刻度
        pointerPaint = new Paint();
        pointerPaint.setAntiAlias(true);
        pointerPaint.setTextSize(textSizeDial);
        pointerPaint.setTextAlign(Paint.Align.CENTER);
        pointerFontMetrics = pointerPaint.getFontMetrics();

        // 标题
        labelPaint = new Paint();
        labelPaint.setAntiAlias(true);
        labelPaint.setTextAlign(Paint.Align.CENTER);
        labelPaint.setFakeBoldText(true);
        labelPaint.setTextSize(labelTextSize);
        labelPaint.setColor(labelTextColor);
        labelFontMetrics = labelPaint.getFontMetrics();

        // 值
        valPaint = new Paint();
        valPaint.setAntiAlias(true);
        valPaint.setTextAlign(Paint.Align.CENTER);
//        valPaint.setFakeBoldText(true);
        valPaint.setTextSize(valTextSize);
        valPaint.setColor(valTextColor);
        valFontMetrics = valPaint.getFontMetrics();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawBG(canvas);
        drawArc(canvas);
        drawPointerLine(canvas);
        drawValText(canvas);
        drawLabelText(canvas);

    }

    private void drawArc(Canvas canvas) {
        canvas.save();
        arcPaint.setStyle(Paint.Style.FILL);
        arcPaint.setShader(arcShader);
        canvas.drawArc(mArcRect, arcStartDegree, sweepAngle, true, arcPaint);

        arcPaint.setStyle(Paint.Style.STROKE);
        arcPaint.setColor(arcColor);
        arcPaint.setShader(null);
        arcPaint.setStrokeWidth(arcPaintStrokeWidth);
        canvas.drawArc(mArcRect, arcStartDegree, sweepAngle, false, arcPaint);

        arcPaint.setStrokeWidth(arcPaintStrokeWidth);
        canvas.drawArc(mCircleArcRect1, arcStartDegree, sweepAngle, false, arcPaint);
        canvas.drawArc(mCircleArcRect2, arcStartDegree, sweepAngle, false, arcPaint);

        arcPaint.setStrokeWidth(arcPaintStrokeWidth);
//        arcPaint.setShader(arcLineShader);
        canvas.translate(mCx, mCy);
        canvas.rotate(arcStartDegree + sweepAngle);
        canvas.drawLine(radiusDial, 0, circleRadius2, 0, arcPaint);
        canvas.restore();
    }

    private void drawLabelText(Canvas canvas) {
        canvas.save();
        canvas.translate(mCx, mCy + (bgRadius3 + circleRadius1) / 2f);
        int textBaseLine = (int) (0 + (labelFontMetrics.bottom - labelFontMetrics.top) / 2 - labelFontMetrics.bottom);
        canvas.drawText(labelText, 0, textBaseLine, labelPaint);
        canvas.restore();
    }

    private void drawValText(Canvas canvas) {
        if (valText != null) {
            canvas.save();
            canvas.translate(mCx, mCy);
            int textBaseLine = (int) (0 + (valFontMetrics.bottom - valFontMetrics.top) / 2 - valFontMetrics.bottom);
            canvas.drawText(valText, 0, textBaseLine, valPaint);
            canvas.restore();
        }
    }

    private void drawPointerLine(Canvas c) {
        if(pLineCache == null) {
            pLineCache = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
            Canvas pointerLineCacheCanvas = new Canvas(pLineCache);
//        pointerLineCacheCanvas.save();
            pointerLineCacheCanvas.translate(mCx, mCy);
            pointerLineCacheCanvas.rotate(pointerLineDegree);

            for (int i = 0; i <= valRange; i++) {

                if (i < lowDialLimit) {
                    pointerPaint.setColor(colorDialLower);
                } else if (i <= highDialLimit) {
                    pointerPaint.setColor(colorDialMiddle);
                } else {
                    pointerPaint.setColor(colorDialHigh);
                }

                if (i % dialStep == 0) {     //长表针
                    pointerPaint.setStrokeWidth(strokeLDial);
                    pointerLineCacheCanvas.drawLine(radiusDial, 0, radiusDial - lengthLDial, 0, pointerPaint);

//                String text = String.format("%.0f", minVal + i); // format 性能跟不上
                    drawPointerText(pointerLineCacheCanvas, decimalFormat.format(minVal + i), i);
                } else {    //短表针
                    pointerPaint.setStrokeWidth(strokeSDial);
                    int offset = (lengthLDial - lengthSDial) / 2;
                    pointerLineCacheCanvas.drawLine(radiusDial - offset, 0, radiusDial - lengthSDial, 0, pointerPaint);
                }
                pointerLineCacheCanvas.rotate(sRoateDegree);
            }
//        pointerLineCacheCanvas.restore();
        }
        c.drawBitmap(pLineCache,0,0, null);
    }

    private void drawPointerText(Canvas canvas, String text, int i) {
        canvas.save();

        int currentCenterX = (int) (radiusDial - lengthLDial - strokeLDial - pointerPaint.measureText(String.valueOf(text)) / 2);
        canvas.translate(currentCenterX, 0);
        canvas.rotate(360 - pointerLineDegree - i * sRoateDegree);

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

        canvas.drawText(text, 0, textBaseLine, pointerPaint);
        canvas.restore();
    }


    private void drawBG(Canvas canvas) {
        canvas.save();
        roundPaint.setStyle(Paint.Style.FILL);
        roundPaint.setColor(bgRound1);
        roundPaint.setStrokeWidth(0);
        canvas.drawCircle(mCx, mCy, bgRadius1, roundPaint);
        roundPaint.setColor(bgRound2);
        canvas.drawCircle(mCx, mCy, bgRadius2, roundPaint);
        roundPaint.setColor(bgRound3);
        canvas.drawCircle(mCx, mCy, bgRadius3, roundPaint);
        roundPaint.setColor(bgRound4);
        canvas.drawCircle(mCx, mCy, bgRadius4, roundPaint);
        roundPaint.setColor(circle1);
        roundPaint.setStyle(Paint.Style.STROKE);
        roundPaint.setStrokeWidth(circleRadius1StrokeWidth);
        canvas.drawCircle(mCx, mCy, circleRadius1, roundPaint);
        roundPaint.setStrokeWidth(circleRadius2StrokeWidth);
        canvas.drawCircle(mCx, mCy, circleRadius2, roundPaint);
        canvas.restore();
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 获取控件区域宽高
        int minimumWidth = getSuggestedMinimumWidth();
        int minimumHeight = getSuggestedMinimumHeight();
        mWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
        mHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
        // 获取x/y轴中心点
        mCx = mWidth / 2;
        mCy = mHeight / 2;
        radius = Math.min(mWidth, mHeight) / 2;
        bgRadius1 = radius;
        bgRadius2 = (int) (radius * 0.95f);
        bgRadius3 = (int) (radius * 0.88f);
        bgRadius4 = (int) (radius * 0.6f);
        circleRadius1 = (int) (radius * 0.64f);
        circleRadius2 = (int) (radius * 0.62f);

        // 长刻度线
        radiusDial = bgRadius3;

        mArcRect.set(mCx - radiusDial, mCy - radiusDial, mCx + radiusDial, mCy + radiusDial);
        mCircleArcRect1.set(mCx - circleRadius1, mCy - circleRadius1, mCx + circleRadius1, mCy + circleRadius1);
        mCircleArcRect2.set(mCx - circleRadius2, mCy - circleRadius2, mCx + circleRadius2, mCy + circleRadius2);
        arcShader = new RadialGradient(mCx, mCy, bgRadius2, colors, null, Shader.TileMode.REPEAT);
        arcLineShader = new LinearGradient(radiusDial, 0, circleRadius2, 0, colorsArcLine, null, Shader.TileMode.REPEAT);
        pLineCache = null; // 重建缓存画布, 绘制点线耗时长

    }


    /**
     * @param formatter 中间值格式化
     * @param maxVal 最大值
     * @param minVal 最小值
     * @param dialStep      必须能被最大范围整除 (maxVal - minVal) / dialStep = 0
     * @param lowDialLimit 警戒线1
     * @param highDialLimit 警戒线2
     */
    public void init(String formatter, float maxVal, float minVal, int dialStep, int lowDialLimit, int highDialLimit) {
        this.dialStep = dialStep;
        float drawDegree = (360 - (180 - pointerLineDegree) * 2);
        this.maxVal = maxVal;
        this.minVal = minVal;
        this.valRange = maxVal - minVal;
        this.formatter = formatter;
        this.drawDialNum = (int) Math.ceil(valRange / dialStep);
        this.stepDegree = drawDegree / drawDialNum;
        this.lowDialLimit = (int) (lowDialLimit - minVal);
        this.highDialLimit = (int) (highDialLimit - minVal);
        this.sRoateDegree = stepDegree / dialStep; // 短刻度旋转角度
        invalidate();
    }

    public void updateVal(float val) {
        realVal = Math.max(Math.min(val, maxVal), minVal);
        valText = String.format(formatter, realVal);
        sweepAngle = (realVal - minVal) / valRange * (360 - (180 - pointerLineDegree) * 2);


        invalidate();
    }

    private int resolveMeasured(int measureSpec, int desired) {
        int specSize = MeasureSpec.getSize(measureSpec);
        int result;
        switch (MeasureSpec.getMode(measureSpec)) {
            case MeasureSpec.UNSPECIFIED:
                result = desired;
                break;
            case MeasureSpec.AT_MOST:
                result = Math.max(specSize, desired);
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            default:
                result = specSize;
                break;
        }

        return result;
    }
    private float dp2px(float dp){
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,this.getContext().getResources().getDisplayMetrics());
    }
    public void config(GaugeView.Config cfg) {
        this.bgRound1 = cfg.bgRound1;
        this.bgRound2 = cfg.bgRound2;
        this.bgRound3 = cfg.bgRound3;
        this.bgRound4 = cfg.bgRound4;
        this.circle1 = cfg.circle1;
        this.textSizeDial = dp2px(cfg.textSizeDial); // 刻度字体大小
        this.maxVal = cfg.maxVal;
        this.minVal = cfg.minVal;

        this.lowDialLimit = cfg.lowDialLimit; // 低段温度警戒线
        this.highDialLimit = cfg.highDialLimit; // 中段警戒线
        this.colorDialLower = cfg.colorDialLower; // 低中高三个色段表盘刻度
        this.colorDialMiddle = cfg.colorDialMiddle;
        this.colorDialHigh = cfg.colorDialHigh;

        this.lengthLDial = cfg.lengthLDial;// 长刻度线的长度
        this.lengthSDial = cfg.lengthSDial;// 短刻度线的长度
        this.strokeLDial = cfg.strokeLDial;
        this.strokeSDial = cfg.strokeSDial;
        this.circleRadius1StrokeWidth = cfg.circleRadius1StrokeWidth;
        this.circleRadius2StrokeWidth = cfg.circleRadius2StrokeWidth;
        this.pointerLineDegree = cfg.pointerLineDegree; // 多少角度
        this.arcStartDegree = pointerLineDegree; // 弧线起始角度
        this.valTextSize = dp2px(cfg.valTextSize);// 值字体大小
        this.valTextColor = cfg.valTextColor;// 值字体颜色
        this.labelText = cfg.labelText;
        this.labelTextSize = dp2px(cfg.labelTextSize);// 标签字体
        this.labelTextColor = cfg.labelTextColor;// 标签字体颜色
        this.arcPaintStrokeWidth = cfg.arcPaintStrokeWidth; // 描边宽度
        this.arcColor = cfg.arcColor;
        this.colors = cfg.colors != null ? cfg.colors : new int[]{Color.TRANSPARENT, Color.TRANSPARENT, Color.TRANSPARENT, cfg.arcColor};
        this.colorsArcLine = cfg.colorsArcLine != null ? cfg.colorsArcLine : new int[]{cfg.arcColor, cfg.arcColor, Color.TRANSPARENT};
        ;
        this.formatter = cfg.formatter;
        this.drawDialNum = cfg.drawDialNum;
        this.dialStep = cfg.dialStep; // 分度
        this.decimalFormat= cfg.decimalFormat;
        init();
        invalidate();
    }

    public static class Config {
        public int bgRound1 = Color.parseColor("#99122a73");
        public int bgRound2 = Color.parseColor("#66122a73");
        public int bgRound3 = Color.parseColor("#22142f80");
        public int bgRound4 = Color.parseColor("#0e2267");
        public int circle1 = Color.parseColor("#0d1d4c");
        public float textSizeDial = 12; // 刻度字体大小
        public float maxVal = 150f;
        public float minVal = -50f;

        public int lowDialLimit = 20; // 低段温度警戒线
        public int highDialLimit = 80; // 中段警戒线
        public int colorDialLower = Color.parseColor("#00faff"); // 低中高三个色段表盘刻度
        public int colorDialMiddle = Color.GREEN;
        public int colorDialHigh = Color.RED;

        public int lengthLDial = 16;// 长刻度线的长度
        public int lengthSDial = 8;// 短刻度线的长度
        public float strokeLDial = 3;
        public float strokeSDial = 1;
        public float circleRadius1StrokeWidth = 2f;
        public float circleRadius2StrokeWidth = 4f;
        public float pointerLineDegree = 135; // 多少角度
        public float valTextSize = 30f;// 值字体大小
        public int valTextColor = Color.WHITE;// 值字体颜色
        public String labelText = "";
        public float labelTextSize = 14f;// 标签字体
        public int labelTextColor = Color.WHITE;// 标签字体颜色
        public float arcPaintStrokeWidth = 2f; // 描边宽度
        public int arcColor = Color.parseColor("#f6a800");
        public int[] colors;
        public int[] colorsArcLine;
        public String formatter = "%.0f";
        public int drawDialNum = 100;
        public int dialStep = 10; // 分度
        public DecimalFormat decimalFormat= new DecimalFormat(".0");

    }

}

 

widget 链接集合

  • Android widget - 完全可配的炫酷仪表盘
  • Android widget - 支持实际距离的雷达扫描控件
  • Android widget - 直播右下角点击刷礼物特效
标签: Android Widgets
最后更新:2022年6月22日

千古八方

物格而后知至,知至而后意诚,意诚而后心正

点赞
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

千古八方

物格而后知至,知至而后意诚,意诚而后心正

最新 热点 随机
最新 热点 随机
火狐下远离内容农场的方法! 中国城市生活成本查询 Debian11 系统备份与灾备盘制作 Debian logwatch 自定义配置文件,根据规则忽略掉某些日志内容 Ampache 5.0 报错 The root Ampache folder has changed to ./public socat 正反向传输文件
Android widget - 完全可配的炫酷仪表盘 自建 NAS,系统安全防护配置(三) 备份篇 ubuntu 更改boot分区格式,重建引导 你到底需不需要一台NAS ? Android 实现背景图片快速高斯模糊 Nextcloud 局域网上传速度慢
分类
  • Android
  • Linux
  • NAS
  • Spring Boot
  • Windows
  • 内网穿透
  • 回忆
  • 基于NEAT的瞎几把寻思算法笔记
  • 散篇
  • 普通人的致富之路在哪里
  • 服务器
  • 未分类
  • 私有云
标签聚合
服务器 我开源的APP linux Android系统编译 内网穿透 Spring Boot 私有云 Nextcloud Windows NPS ubuntu Android Utils 群晖NAS TrueNAS 散篇 安全维护 Android Widgets NAS
工具/友链

资源导航 – NAS、私有云存储
在线工具 – MD5、SHA、BASE64、URL编解码
在线工具 – 贷款计算器
在线工具 – IPv4/IPv6地址查询

千古八方 在 B站

千古八方 在 知乎

我的微信小程序:
微信小程序-城市生活成本查询
城市生活成本查询
库房物资流水记账簿
最近评论
Avatar photo
千古八方 发布于 1 个月前(12月28日) 重置命令是Nextcloud提供的,和哪个平台没关系。 如果报错,一般是路径或权限的问题导致的。 ...
Avatar photo
aron 发布于 1 个月前(12月24日) 博主你好 我的nextcloud管理员密码过期了 用了occ命令重置但是报错,能请教下是什么原因...
Avatar photo
千古八方 发布于 2 个月前(12月15日) 贝塞尔这个类在这里 https://gitee.com/aityu/AndroidUtils/blo...
Avatar photo
disco 发布于 2 个月前(12月05日) 有demo链接吗?贝塞尔曲线这个关键类没有写出来~
Avatar photo
HJM 发布于 3 个月前(10月20日) 必须点赞!很有效!

COPYRIGHT © 2022 千古八方的博客. ALL RIGHTS RESERVED.

京ICP备14020471号