自定义仪表盘PaneView

1、概述

最近学习自定义View,趁着周末做了一个仪表盘练练手,效果还可以,在此分享一下先上效果图(截图有点不清晰,凑合着看下吧)

项目在我的github上https://github.com/xsfelvis/PanelView 


有图才能有真相,下面简要说一下如何实现的


2、实现

【分析有哪些属性需要】

在values/attr文件中进行声明,这些属性都是可以在xml中进行使用的,实现定制的,比如unit单位属性

<resources>


    <declare-styleable name="PanelView">
        <attr name="arcColor" format="color"/>
        <attr name="arcWidth" format="dimension"/>
        <attr name="secArcWidth" format="dimension"/>
        <attr name="android:text"/>
        <attr name="tikeCount" format="integer"/>
        <attr name="pointerColor" format="color"/>
        <attr name="Unit" format="string"/>
        <attr name="android:textSize"/>
        <attr name="AcrStartColor" format="color"/>
        <attr name="AcrEndColor" format="color"/>
        <attr name="textColor" format="color"/>

    </declare-styleable>

</resources>

然后为了结构上更加工整,推荐单独写一个文件来处理这些属性,最好不要和自定义控件混在一起

这里在PanelViewAttr.java中做了处理

package PanelView;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;

import com.xsf.panelview.R;

import util.PxUtils;

/**
 * Created by hzxushangfei on 2016/1/23.
 */
public class PanelViewAttr {
    private int mArcColor;
    private int mPointerColor;
    private int mTikeCount;
    private int mTextSize;
    private String mText = "";
    private int arcwidth;
    private int mScendArcWidth;
    private String unit;//单位
    private int acrStartColor;
    private int acrEndColor;
    private int textColor;


    public PanelViewAttr(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PanelView, defStyleAttr, 0);
        mArcColor = ta.getColor(R.styleable.PanelView_arcColor, context.getResources().getColor(R.color.colorPrimaryDark));
        mPointerColor = ta.getColor(R.styleable.PanelView_pointerColor, context.getResources().getColor(R.color.PointerColor));
        mTikeCount = ta.getInt(R.styleable.PanelView_tikeCount, 12);
        mTextSize = ta.getDimensionPixelSize(PxUtils.spToPx(R.styleable.PanelView_android_textSize, context), 24);
        mText = ta.getString(R.styleable.PanelView_android_text);
        arcwidth = ta.getInt(R.styleable.PanelView_arcWidth, 3);
        mScendArcWidth = ta.getInt(R.styleable.PanelView_secArcWidth, 50);
        unit = ta.getString(R.styleable.PanelView_Unit);
        acrStartColor = ta.getColor(R.styleable.PanelView_AcrStartColor, context.getResources().getColor(R.color.GREEN));
        acrEndColor = ta.getColor(R.styleable.PanelView_AcrEndColor, context.getResources().getColor(R.color.RED));
        textColor = ta.getColor(R.styleable.PanelView_textColor, context.getResources().getColor(R.color.Yellow));
        ta.recycle();
    }


    public int getAcrEndColor() {
        return acrEndColor;
    }

    public int getAcrStartColor() {
        return acrStartColor;
    }

    public int getArcwidth() {
        return arcwidth;
    }

    public int getmArcColor() {
        return mArcColor;
    }

    public int getmPointerColor() {
        return mPointerColor;
    }

    public int getmTikeCount() {
        return mTikeCount;
    }

    public int getmTextSize() {
        return mTextSize;
    }

    public String getmText() {
        return mText;
    }

    public int getmScendArcWidth() {
        return mScendArcWidth;
    }

    public String getUnit() {
        return unit;
    }
    public int getTextColor() {
        return textColor;
    }

}

再然后在自定义view实现类 PanelView.java中获取

 panelViewattr = new PanelViewAttr(context, attrs, defStyleAttr);
通过get方法获取

 mArcColor = panelViewattr.getmArcColor();
        mPointerColor = panelViewattr.getmPointerColor();
        mTikeCount = panelViewattr.getmTikeCount();
        mTextSize = panelViewattr.getmTextSize();
        mTextColor = panelViewattr.getTextColor();
        mText = panelViewattr.getmText();
        mArcWidth = panelViewattr.getArcwidth();
        mScendArcWidth = panelViewattr.getmScendArcWidth();
        unit = panelViewattr.getUnit();
        acrStartColor = panelViewattr.getAcrStartColor();
        acrEndColor = panelViewattr.getAcrEndColor();

OK,这些都是小儿科,主要看onMeasure和OnDraw()方法

这里主要对exactly做了处理,其余的按照统一处理onMeasure()方法如下,推荐用一个方法来处理measure,不要把宽和高都写一遍,这样代码看起来会有点冗杂

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int realWidth = startMeasure(widthMeasureSpec);
        int realHeight = startMeasure(heightMeasureSpec);

        setMeasuredDimension(realWidth, realHeight);
    }

    private int startMeasure(int msSpec) {
        int result = 0;
        int mode = MeasureSpec.getMode(msSpec);
        int size = MeasureSpec.getSize(msSpec);
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = PxUtils.dpToPx(200, mContext);
        }
        //Log.d("xsf", "startMeasure " + result);
        return result;
    }

onMeasure之后决定了大小,重头戏在onDraw,直接看onraw方法

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float percent = mPercent / 100f;

        //最外面线条
        drawOutAcr(canvas);
        //绘制刻度
        drawerNum(canvas);
        //绘制粗圆弧
        drawInArc(canvas, percent);
        //绘制中间小圆和圆环
        drawInPoint(canvas);
        //绘制指针
        drawerPointer(canvas, percent);
        //绘制矩形和文字
        drawerRecAndText(canvas, percent);
        
    }

这样写是不是显得逻辑非常清楚,不要把细节都放在ondraw方法中,用一个个函数封装是最好不过的了

首先 drawOutAcr(canvas)

画最外面的弧

private void drawOutAcr(Canvas canvas) {
        //最外面线条
        rectF1 = new RectF(mArcWidth, mArcWidth, getWidth() - mArcWidth, getHeight() - mArcWidth);
        canvas.drawArc(rectF1, START_ARC, DURING_ARC, false, paintOuter_Arc);
    }

主要使用到canvas.drawArc方法,没啥好说的

执行完你会看到一个弧



然后是画刻度drawerNum(canvas);

这个函数开始画刻度和数字

   private void drawerNum(Canvas canvas) {
        canvas.save(); //记录画布状态
        canvas.rotate(-(180 - START_ARC + 90), getWidth() / 2, getHeight() / 2);
        float rAngle = DURING_ARC / mTikeCount;
        for (int i = 0; i < mTikeCount + 1; i++) {
            canvas.save(); //记录画布状态
            canvas.rotate(rAngle * i, getWidth() / 2, getHeight() / 2);
            canvas.drawLine(getWidth() / 2, mArcWidth, getWidth() / 2, 20, paintOuter_Arc);//画刻度线
            canvas.drawText("" + i * 10, getWidth() / 2 - mArcWidth * 2, 40, paintouter_Num);//画刻度
            canvas.restore();
        }
        canvas.restore();
    }

要点:canvas.save 和canvas.restore来记录和恢复画布的状态

结合canvas.ratate旋转画布,画布旋转固定角度,画笔此时不需要变化,大大方便,这个技巧需要掌握

此时画出


然后执行drawInArc(canvas, percent);画粗圆弧

 private void drawInArc(Canvas canvas, float percent) {
        rectF2 = new RectF(mArcWidth + OFFSET, mArcWidth + OFFSET, getWidth() - mArcWidth - OFFSET, getHeight() - mArcWidth - OFFSET);

        canvas.drawArc(rectF2, START_ARC, DURING_ARC, false, paintInerArc);

        rectF3 = new RectF(mArcWidth + OFFSET, mArcWidth + OFFSET, getWidth() - mArcWidth - OFFSET, getHeight() - mArcWidth - OFFSET);
        shader = new LinearGradient(mArcWidth + OFFSET, mArcWidth + OFFSET,
                getWidth() - mArcWidth - OFFSET, getHeight() - mArcWidth - OFFSET, acrStartColor, acrEndColor, Shader.TileMode.REPEAT);
        paintInerArc_tranform.setShader(shader);
        canvas.drawArc(rectF3, START_ARC, <span style="color:#000099;">percent * DURING_ARC</span>, false, paintInerArc_tranform);
    }

这个涉及到了粗圆弧颜色状态的变化,首先画一个白色的圆弧,画笔设置粗一点,为白色

然后使用shader来变色,在相同的圆弧上根据百分比再覆盖一层颜色,我们画弧是从150度(START_ARC)为开始位置,持续了240度(DURING_ARC)

此时状态为


然后开始画圆环和圆点这个比较简单,注意下圆环使用空心画笔,圆点使用实心画笔即可

private void drawInPoint(Canvas canvas) {
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMinRingRadius, paintOuter_Arc);//中心小圆环
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMinCircleRadius, paint_centerPoint_Pointer);//中心圆点
    }


圆弧颜色变化解决了,下面来解决指针跟随百分比变化

percent在0-1之间,实现-120- +120变化(DURING_ARC=240),那么转化成数学关系就是,变化角度angle = DURING_ARC*(percent-0.5),ok表达式搞定就好搞了

  private void drawerPointer(Canvas canvas, float percent) {
        canvas.save();
        float angel = DURING_ARC * (percent - 0.5f);
        canvas.rotate(angel, getWidth() / 2, getHeight() / 2);//指针与外弧边缘持平
        paint_centerPoint_Pointer.setStrokeWidth(mArcWidth);
        canvas.drawLine(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2 - mArcWidth * 2 - OFFSET - mScendArcWidth, paint_centerPoint_Pointer);
        canvas.restore();
    }

根据百分比变化位置的指针也画出来了

最后画画文字和单位

    private void drawerRecAndText(Canvas canvas, float percent) {
        float length = 0;
        paint_text.setTextSize(mTextSize);

        length = paint_text.measureText(mText);
        canvas.drawText(mText, getWidth() / 2 - length / 2, (float) (getHeight() / 2 * (1 + Math.sqrt(2) / 3)), paint_text);
        
        paint_text.setTextSize(mTextSize * 1.5f);
        speed = StringUtil.floatFormat(120 * percent) +<span style="color:#336666;"> unit</span>;
        length = paint_text.measureText(speed);

        canvas.drawText(speed, getWidth() / 2 - length / 2, (float) (getHeight() / 2 * (1 + Math.sqrt(2) / 2)), paint_text);
    }

为了使文字画在中间需要使用path里面的measureText的方法,然后为了文字和速度字体有层次感,这里设置为文字的1.5倍大小


ok很简答吧!


















  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
### 回答1: Qt是一种跨平台的C++开发框架,它提供了丰富的GUI组件库,使得开发者可以轻松地设计和创建各种控件和界面。自定义控件从一定程度上方便了程序的编写,让程序更加直观,使用得当的自定义控件可以让程序增添不少精彩细节。仪表盘是一种比较常见的自定义控件,它可以用来显示实时数据,例如速度,油量,温度等等。下面来简单介绍一下用Qt自定义一个仪表盘的一些步骤。 首先要构思好仪表盘的外观和功能,比如可以考虑盘的大小,采用什么颜色,显示哪些数据等等。 其次,需要用Qt中提供的基础控件(如QPainter、QPoint、QRect)来构建自定义控件的各个部分,包括盘表、指针、刻度等,然后为这些部分设置合适的属性(如颜色、位置、宽度等)。 接着,需要实现控件的数据传递和刷新。一般情况下,会使用定时器或者事件触发来更新控件显示的数据。考虑到仪表盘是一种实时显示数据的控件,所以在设计数据刷新时需要保证刷新频率足够高,否则会出现卡顿、显示延迟等问题。 最后,为了方便其他的开发者使用该自定义控件,可以将其打包成独立的Qt插件,或者直接将自定义控件的源代码公开发布。 以上就是简单的Qt自定义控件仪表盘的一些步骤,开发者可以根据自己的需求进行相应更改和优化。总之自定义控件并不是一件简单的事情,但是如果能够掌握好最基本的知识,就能够创造出更加精美、实用的自定义控件。 ### 回答2: Qt自定义控件仪表盘可以用于需要展示数据的界面设计。通过自定义仪表盘,可以实现不同样式和功能的展示,并且能够满足不同场景下的需求。 在Qt中,仪表盘的设计可以通过绘图、圆弧、指针和动画的实现,使得界面更加直观、美观,也更加容易被用户理解和操作。可以通过Qt提供的QPainter绘图工具绘制圆弧,也可以通过QTimer控制指针的动画效果。 另外,仪表盘也是可以与其他控件进行绑定的。通过信号与槽的机制,可以将仪表盘的数值与其他控件进行绑定,实现更加复杂的界面功能。 需要特别注意的是,仪表盘的设计需要考虑到不同屏幕分辨率的适配性。通过使用Qt提供的屏幕适配机制,可以实现不同分辨率下的仪表盘展示效果。 总之,Qt自定义控件仪表盘是一个非常实用、优雅的设计元素,可以帮助开发者快速开发出漂亮的界面,并且提高用户体验。 ### 回答3: Qt是一个强大的C++跨平台框架,自带了很多常用的UI控件,但是有时候我们也需要自定义一些控件来满足我们的需求。今天我们来介绍一下如何使用Qt自定义一个仪表盘控件。 首先我们需要继承QWidget类,命名为Dashboard。然后我们需要在Dashboard的构造函数中初始化一些常量,比如外部圆弧的宽度、内部圆弧的半径等等。接着我们需要重写paintEvent函数,绘制仪表盘的外部圆弧、刻度、指针等等,具体绘制方式可以根据需求灵活设置。 为了使仪表盘可以在Qt Designer中拖拽使用,我们需要在Dashboard类中加入QIB_DESIGNER_EXPORT_WIDGETS宏。最后在.pro文件中添加如下代码: ``` QT += designer TARGET = Dashboard TEMPLATE = lib HEADERS += Dashboard.h SOURCES += Dashboard.cpp QIB_DESIGNER_EXPORT_WIDGETS(Dashboard) ``` 这样我们就可以在Qt Designer中使用Dashboard自定义控件了。当然,使用自定义控件也需要相应的信号槽机制来进行交互操作。 总的来说,自定义控件可以更好地满足我们的需求,同时也可以提高界面的美观度。在自定义控件的过程中,我们要考虑到控件的可扩展性、易用性以及代码的可维护性等方面,并在不断地改进和优化中,不断提高自己的技术水平。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值