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哈

目录
相关文章
|
24天前
|
存储 Shell Android开发
基于Android P,自定义Android开机动画的方法
本文详细介绍了基于Android P系统自定义开机动画的步骤,包括动画文件结构、脚本编写、ZIP打包方法以及如何将自定义动画集成到AOSP源码中。
42 2
基于Android P,自定义Android开机动画的方法
|
22天前
|
供应链 物联网 区块链
未来触手可及:探索新兴技术的趋势与应用安卓开发中的自定义视图:从基础到进阶
【8月更文挑战第30天】随着科技的飞速发展,新兴技术如区块链、物联网和虚拟现实正在重塑我们的世界。本文将深入探讨这些技术的发展趋势和应用场景,带你领略未来的可能性。
|
23天前
|
测试技术 Android开发 Python
探索软件测试的艺术:从基础到高级安卓应用开发中的自定义视图
【8月更文挑战第29天】在软件开发的世界中,测试是不可或缺的一环。它如同艺术一般,需要精细的技巧和深厚的知识。本文旨在通过浅显易懂的语言,引领读者从软件测试的基础出发,逐步深入到更复杂的测试策略和工具的使用,最终达到能够独立进行高效测试的水平。我们将一起探索如何通过不同的测试方法来确保软件的质量和性能,就像艺术家通过不同的色彩和笔触来完成一幅画作一样。
|
1天前
|
Android开发 开发者
安卓开发中的自定义视图:从入门到精通
【9月更文挑战第19天】在安卓开发的广阔天地中,自定义视图是一块充满魔力的土地。它不仅仅是代码的堆砌,更是艺术与科技的完美结合。通过掌握自定义视图,开发者能够打破常规,创造出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战应用,一步步展示如何用代码绘出心中的蓝图。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往创意和效率的大门。让我们一起探索自定义视图的秘密,将你的应用打造成一件艺术品吧!
18 10
|
6天前
|
XML 编解码 Android开发
安卓开发中的自定义视图控件
【9月更文挑战第14天】在安卓开发中,自定义视图控件是一种高级技巧,它可以让开发者根据项目需求创建出独特的用户界面元素。本文将通过一个简单示例,引导你了解如何在安卓项目中实现自定义视图控件,包括创建自定义控件类、处理绘制逻辑以及响应用户交互。无论你是初学者还是有经验的开发者,这篇文章都会为你提供有价值的见解和技巧。
14 3
|
7天前
|
前端开发 Android开发 开发者
安卓应用开发中的自定义视图基础
【9月更文挑战第13天】在安卓开发的广阔天地中,自定义视图是一块神奇的画布,它允许开发者将想象力转化为用户界面的创新元素。本文将带你一探究竟,了解如何从零开始构建自定义视图,包括绘图基础、触摸事件处理,以及性能优化的实用技巧。无论你是想提升应用的视觉吸引力,还是追求更流畅的交互体验,这里都有你需要的金钥匙。
|
10天前
|
缓存 搜索推荐 Android开发
安卓应用开发中的自定义View组件实践
【9月更文挑战第10天】在安卓开发领域,自定义View是提升用户体验和实现界面个性化的重要手段。本文将通过一个实际案例,展示如何在安卓项目中创建和使用自定义View组件,包括设计思路、实现步骤以及可能遇到的问题和解决方案。文章不仅提供了代码示例,还深入探讨了自定义View的性能优化技巧,旨在帮助开发者更好地掌握这一技能。
|
12天前
|
Android开发
Android中SurfaceView的双缓冲机制和普通View叠加问题解决办法
本文介绍了 Android 平台上的 SurfaceView,这是一种高效的图形渲染控件,尤其适用于视频播放、游戏和图形动画等场景。文章详细解释了其双缓冲机制,该机制通过前后缓冲区交换来减少图像闪烁,提升视觉体验。然而,SurfaceView 与普通 View 叠加时可能存在 Z-Order 不一致、同步问题及混合渲染难题。文中提供了使用 TextureView、调整 Z-Order 和创建自定义组合控件等多种解决方案。
46 9
|
16天前
|
Android开发 容器
Android经典实战之如何获取View和ViewGroup的中心点
本文介绍了在Android中如何获取`View`和`ViewGroup`的中心点坐标,包括计算相对坐标和屏幕上的绝对坐标,并提供了示例代码。特别注意在视图未完成测量时可能出现的宽高为0的问题及解决方案。
25 7
|
22天前
|
XML 搜索推荐 Android开发
安卓开发中的自定义View组件实践
【8月更文挑战第30天】探索Android世界,自定义View是提升应用界面的关键。本文以简洁的语言带你了解如何创建自定义View,从基础到高级技巧,一步步打造个性化的UI组件。