Android自定义View之DashBoard(仪表盘)

简介: 前言Android自定义View是Android初中级开发工程师向高级工程师进阶所必须掌握的一块内容,其重要性不言而喻。接下来的一段时间,我会连续出几篇跟自定义View相关的文章,从易到难,跟大家一起学习Android自定义View。

前言

Android自定义View是Android初中级开发工程师向高级工程师进阶所必须掌握的一块内容,其重要性不言而喻。接下来的一段时间,我会连续出几篇跟自定义View相关的文章,从易到难,跟大家一起学习Android自定义View。本文讲一个Android很简单的View——DashBoard(仪表盘),以这个例子带大家去学习自定义View的基本绘制,让大家学会自定义View,并最终掌握。
注:本文的Demo在文章的最后

必须要掌握的几个点

在开始我们的绘制DashBoard之前,有几个点是必须要掌握的,这些是绘制的基础,也是前提。

Paint

自定义View的过程就是一个绘制的过程,而绘制就好像我们画画一样,而画画就必须要会画笔,Paint就是我们的画笔。

  • Paint 类的几个最常用的方法。具体是:
    • Paint.setStyle(Style style) 设置绘制模式
    • Paint.setColor(int color) 设置颜色
    • Paint.setStrokeWidth(float width) 设置线条宽度
    • Paint.setTextSize(float textSize) 设置文字大小
    • Paint.setAntiAlias(boolean aa) 设置抗锯齿开关

这里重点讲一下Paint.setStyle(Style style)方法,这个方法设置的是绘制的 Style 。Style 具体来说有三种: FILL, STROKE 和 FILL_AND_STROKE 。FILL 是填充模式,STROKE 是画线模式(即勾边模式),FILL_AND_STROKE 是两种模式一并使用:既画线又填充。它的默认值是 FILL,填充模式。只有当Style是STROKE 和 FILL_AND_STROKE时,Paint.setStrokeWidth(float width)才有意义,你全是填充的就不涉及什么线条宽度了。

canvas

Paint是画笔,可画画光有画笔还不行,还必须得有画布,Canvas就是画布。Canvas这个类是绘制最重要的类,没有之一,几乎所有绘制的方法都出自于这个类。

坐标系

方法先不讲,先讲一下坐标系,在Android 里,每个View 都有一个自己的坐标系,彼此之间是不影响的。这个坐标系的原点是 View 左上角的那个点;水平方向是 x 轴,右正左负;竖直方向是 y 轴,下正上负(注意,是下正上负,不是上正下负,和上学时候学的坐标系方向不一样也就是下面这个样子。

img_1dfc51926d03c86a9c4277d3a20541cc.jpe
坐标系

这个坐标非常重要,因为我们所有的绘制都是在这个坐标系的基础上开展的,而关于坐标系还有这么个两个方法要特别注意:

  • Canvas.rotate(float degrees)//旋转坐标系,正角度顺时针,负角度逆时针
  • Canvas.translate(float dx, float dy)
    注意:以上两个方法的操作的对象是坐标系,跟View本身没有关系,之所以使用是为了让我们更好、更方便地绘制View。
方法

Canvas最重要也最常用的方法都是drawXXX()方法,方法太多了,我不可能一一列举,写几个最常用的,余下的请自行google

  • drawCircle(float centerX, float centerY, float radius, Paint paint) 画圆
    基本看参数名字就能猜出来是啥意思了,前面讲了坐标系的概念,前两个参数就是圆心的X、Y坐标了,第三个是半径大小,最后一个是画笔。
  • drawRect(float left, float top, float right, float bottom, Paint paint) 画矩形 (参数啥意思基本都能猜出来,不多讲,不行还是google)
  • drawOval(float left, float top, float right, float bottom, Paint paint) 画椭圆
  • drawLine(float startX, float startY, float stopX, float stopY, Paint paint) 画线
  • drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 绘制弧形或扇形
    left, top, right, bottom 描述的是这个弧形所在的椭圆;startAngle 是弧形的起始角度(x 轴的正向,即正右的方向,是 0 度的位置;顺时针为正角度,逆时针为负角度),sweepAngle 是弧形划过的角度;useCenter 表示是否连接到圆心,如果不连接到圆心,就是弧形,如果连接到圆心,就是扇形。
  • drawPath(Path path, Paint paint) 画自定义图形
    这里Path对象要讲一下,Path.addXxx()——添加子图形,例如Path.addCircle(float x, float y, float radius, Direction dir) 添加圆;Path.xxxTo() ——画线(直线或曲线)lineTo(float x, float y) / rLineTo(float x, float y) 画直线.

小结

以上就是我们开始绘制DashBoard之前还需要掌握的基础,因为都用得到。Paint是画笔,主要就是设置画笔相关的属性,颜色、大小、风格等等;canvas是画布,坐标系的概念必须清楚,重要的几个方法也必须知道。

DashBoard

先上个图


img_6317f6e44510808a4efa3f18884a4487.jpe
DashBoard

看图其实很简单,基本上就分为三步,第一份画弧;第二步画刻度;第三步画指针。

画弧线

画弧线之前,我简单讲一下自定义View的流程,创建一个DashBoard的类型继承View,重写构造方法和onDraw(Canvas canvas)

public class DashBoard extends View {
 public DashBoard(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();//初始化Paint
    }
  protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
   }
}

初始化Paint

 private void initPaint(){
        mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
        mPaint.setStyle(Paint.Style.STROKE);//画线模式
        mPaint.setStrokeWidth(Utils.px2dp(2));//线宽度
        mPaint.setColor(Color.BLACK);
    }

做好以上初始化工作,我们就开始第一步画弧线。调用Canvas.drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)这个方法上面有介绍过,这里就不详细讲了,前四个参数很好设置,我们定义圆心在View的中心位置,即(getWidth()/2,geHeight()/2),半径150dp,那么前四个参数就有了,第六个参数sweepAngle是划过的度数,这个我们定义为240度,第七个是否连接中心,false,我们不需要连接中心,最后一个放自己的Paint就行了,现在的关键就是第五个参数,开始角度的计算,下面我出张图帮助大家计算一下
img_e4b200a11b17aaf01d5b8ab7165285a1.jpe
startAngle角度分析

图中画得应该比较清楚了,不过多解释,直接上代码

 private void drawArc(Canvas canvas){
        rectF = new RectF(getWidth() / 2 - RADIUS, getHeight() / 2 - RADIUS,
                getWidth() / 2 + RADIUS, getHeight() / 2 + RADIUS);
        canvas.drawArc(rectF,90+(360-SWEEPANGLE)/2,SWEEPANGLE,false,mPaint);
    }

效果图


img_c7d851c20d55bd889d0551289f50e92a.jpe
效果图

画刻度

关于画刻度,其实就是画线吗,那画线的方法拿过来看一下,Canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint) ,需要线的起始点和结束点的坐标,如果我要画21个刻度,那需要21个点的刻度都算一遍,我去,谁可能这么干啊。放心,我们当然不会这么干了,下面提供两种方式。

第一种方式

思路:坐标系的旋转+平移
之前在讲Canvas里提过,每一个Android的View都对应着有一个坐标系,坐标原点在View的左上角(可以看一下上面的那张图),现在如果我们把坐标原点平移到圆心的位置,并且再顺时针旋转30°,那么当前的坐标系就是下面这样的

img_9185f86137936b48c394a2fa39e31458.jpe
平移后的坐标系

那么在我们平移旋转以后,在画 右下角第一个刻度的时候,就相当于这个刻度的起始点和结束点的 纵坐标都是0,因为我们把原点移动到了圆心,而 结束点的横坐标就是圆的半径(RADIUS),而 起始点的横坐标就是(半径-刻度线的长度)。下面我还是用一张图来解释一下
img_064f154d5674f28bbc6d9b7fd2df190d.jpe

这样第一个刻度线,我们就画出来了,现在假定我们要画21个刻度线,21刻度线对应 20个间隔,总角度是240度,每个刻度线间隔角度就是240/20即12度,所以其他的刻度线就可以让 坐标系每次逆时针旋转12度画一次,用代码表达一下会更清晰。

private void drawDegree(Canvas canvas){
        canvas.translate(getWidth()/2,getHeight()/2);
        canvas.rotate(30);
        for (int i=0;i<20;i++){
            //Utils.px2dp(10)是刻度线的长度,为10dp
            canvas.drawLine(RADIUS-Utils.px2dp(10),0,RADIUS,0,mPaint);
            canvas.rotate(-SWEEPANGLE/20);//逆时针选择 负值是逆时针
        }
        //最后一根线
        canvas.drawLine(RADIUS-Utils.px2dp(10),0,RADIUS,0,mPaint);
        canvas.rotate(240-30);//旋转回去的角度
        canvas.translate(-getWidth()/2,-getHeight()/2);

    }

画完以后的效果图

img_c7638eab8a2c20b046babf2fb679300a.jpe

这个图我故意没有缩放,细心的你可能已经发现了第一个和最后一个刻度明显有点不自然,我再放大一下
img_6e0e69de5b81dbcbff85c7887dc33e67.jpe

这下很明显了吧,感觉好像是空了一半的宽度没有画在弧线上。这是为什么呢? 还得再上个图
img_2d58fc389e75f0fe9ced1896d4724a92.jpe

看图我们可以知道,我们在画刻度的时候,Paint就是我们的画笔,默认是宽度的,我们画刻度的纵坐标是0,但是实际画的时候是把画笔的 中间位置放在0的坐标上,这就导致了好像空了一半Paint出来,知道了什么原因其实解决起来也很简单,就是把起始点和结束点的纵坐标 相应的提高 半个画笔的高度,直接看代码吧

 private void drawDegree(Canvas canvas){
        canvas.translate(getWidth()/2,getHeight()/2);
        canvas.rotate(30);
        for (int i=0;i<20;i++){
            //纵坐标下正上负,向上提高,加负值,即-mPaint.getStrokeWidth()/2
            canvas.drawLine(RADIUS-Utils.px2dp(10),-mPaint.getStrokeWidth()/2,RADIUS,-mPaint.getStrokeWidth()/2,mPaint);
            canvas.rotate(-SWEEPANGLE/20);
        }
        //最后一个点,因坐标系已经旋转了240度,向上提高,加整值,即mPaint.getStrokeWidth()/2
        canvas.drawLine(RADIUS-Utils.px2dp(10),mPaint.getStrokeWidth()/2,RADIUS,mPaint.getStrokeWidth()/2,mPaint);
        canvas.rotate(240-30);//旋转回去的角度
        canvas.translate(-getWidth()/2,-getHeight()/2);
    }

看了注释,你会发现第一个点和最后一个点的提高方式不同,这也是为什么上面”相应的“三个字我要加粗了,再看一眼修改后的效果图
img_78b09c5ae65dcd016271d50c41edbaeb.jpe
微信图片_20181024141740.jpg
第二种方式

思路:使用PathMeasure测量弧线长度,利用PathDashPathEffect来画刻度
简单讲一下PathMeasure和PathDashPathEffect

  • PathMeasure:用来测量路径的长度,public PathMeasure(Path path, boolean forceClosed),通过PathMeasure.getLength()
  • PathDashPathEffect:Paint.setPathEffect(PathEffect effect)给图形的轮廓设置效果的,PathDashPathEffect是PathEffect的一个子类,它的构造方法 PathDashPathEffect(Path shape, float advance, float phase, PathDashPathEffect.Style style) 中, shape 参数是用来绘制的 Path ; advance 是两个相邻的 shape 段之间的间隔,不过注意,这个间隔是两个 shape 段的起点的间隔,而不是前一个的终点和后一个的起点的距离; phase 和 DashPathEffect 中一样,是虚线的偏移;最后一个参数 style,是用来指定拐弯改变的时候 shape 的转换方式。style 的类型为 PathDashPathEffect.Style ,是一个 enum ,具体有三个值:TRANSLATE:位移,ROTATE:旋转,MORPH:变体
    知道了这两个方法,我们就可以先用PathMeasure拿到弧线的长度,除以20获得每个间隔的长度,然后通过Paint.setPathEffect(new PathDashPathEffect())方法来画刻度就行了,直接上代码
private void drawDegree2(Canvas canvas){
        //刻度的路径
        dash=new Path();
        //Path.Direction.CW顺时针方向 同时顺时针切线方向为X轴正向 
        dash.addRect(0,0,Utils.px2dp(2),Utils.px2dp(10), Path.Direction.CW);
        //弧线长度的路径
        Path length=new Path();
        length.addArc(rectF,90+(360-SWEEPANGLE)/2,SWEEPANGLE);
        //测量弧线长度
        pathMeasure=new PathMeasure(length,false);
        //这里(pathMeasure.getLength()-mPaint.getStrokeWidth())/20 弧线长度之所以减去Paint的宽度跟我第一种方式去掉宽度是一个意思
        mPaint.setPathEffect(new PathDashPathEffect(dash,
                (pathMeasure.getLength()-mPaint.getStrokeWidth())/20,0, PathDashPathEffect.Style.ROTATE));
        canvas.drawArc(rectF,90+(360-SWEEPANGLE)/2,SWEEPANGLE,false,mPaint);
        mPaint.setPathEffect(null);
    }

这里我就不细讲了,注释还是比较清楚,效果图跟第一种方式是一样的就不贴图,个人还是更加推荐第一种的画刻度方式。

画指针

画指针呢,就比较简单了,其实就是调用画线的方法,先把坐标系平移动原点位置,设置一个当前的角度currentAngle还有指针长度INDICATOR,唯一有一点难度的就是计算结束点的横纵坐标,需要用到三角函数的知识

  • 横坐标:Math.cos(Math.toRadians(currentAngle))*INDICATOR
  • 纵坐标:Math.sin(Math.toRadians(currentAngle))*INDICATOR
    很简单,上代码
private void drawIndicator(Canvas canvas){
        canvas.translate(getWidth()/2,getHeight()/2);
        canvas.drawLine(0,0,
                (float) Math.cos(Math.toRadians(currentAngle))*INDICATOR,
                (float)Math.sin(Math.toRadians(currentAngle))*INDICATOR,
                mPaint);
        canvas.translate(getWidth()/2,getHeight()/2);
    }

最后

Android自定义View是Android比较难的一块内容,本文主要通过绘制DashBoard来讲基本的绘制,Paint和Canvas的基本用法,接下来的一段时间内,我会继续出自定义View相关的内容,下一篇文章会讲绘制文字。
最后放上文章的demo DashBoard,觉得还不错的请给个star哈

目录
相关文章
|
1月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
29 1
|
2月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
107 0
|
1月前
|
XML 前端开发 Android开发
Android:UI:Drawable:View/ImageView与Drawable
通过本文的介绍,我们详细探讨了Android中Drawable、View和ImageView的使用方法及其相互关系。Drawable作为图像和图形的抽象表示,提供了丰富的子类和自定义能力,使得开发者能够灵活地实现各种UI效果。View和ImageView则通过使用Drawable实现了各种图像和图形的显示需求。希望本文能为您在Android开发中使用Drawable提供有价值的参考和指导。
42 2
|
1月前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
1月前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
41 5
|
2月前
|
缓存 数据处理 Android开发
在 Android 中使用 RxJava 更新 View
【10月更文挑战第20天】使用 RxJava 来更新 View 可以提供更优雅、更高效的解决方案。通过合理地运用操作符和订阅机制,我们能够轻松地处理异步数据并在主线程中进行 View 的更新。在实际应用中,需要根据具体情况进行灵活运用,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在 Android 中使用 RxJava 更新 View 的技巧和方法,为开发高质量的 Android 应用提供有力支持。
|
2月前
|
缓存 调度 Android开发
Android 在子线程更新 View
【10月更文挑战第21天】在 Android 开发中,虽然不能直接在子线程更新 View,但通过使用 Handler、AsyncTask 或 RxJava 等方法,可以实现子线程操作并在主线程更新 View 的目的。在实际应用中,需要根据具体情况选择合适的方法,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在子线程更新 View 的技巧和方法,为开发高质量的 Android 应用提供支持。
43 2
|
2月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
2月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
29 2
|
2月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。