Android自定义View分享——一个水平的进度条

来源:互联网 发布:隔音窗户 知乎 编辑:程序博客网 时间:2024/06/05 14:53

写在前面

笔者近来在学习Android自定义View,收集了一些不算复杂但又“长得”还可以的自定义View效果实现,这些View的逻辑不算复杂,大多都只用到了Paint、Canvas类的一些常用的API。在后续的博客里面,将分享几个不同的效果,本文作为第一篇,先来一个很简单的——一个水平进度条(跟系统的那个不同)。

本文适合什么样的人

如果你接触自定义View不久,看懂了View绘制基本流程,知道onMeasured()、onLayout()、onDraw(),知道Paint、Canvas类及其API很强大但没用过,正准备大显身手来几个自定义View却又不知道该“画”一个怎么样自定义View比较合适,苦恼于网上各种开源项目太高端,看不懂,那么本文及后续的博客很适合你,来让我们一起拿起铅笔(Paint)、白纸(Canvas)来当一回艺术家呗。

效果展现

看这就是我们要的效果,朝两边滚动的进度条。
TwoSideProgressBar图片展示

设计图

作为一个艺术家(码农),当然要先有设计图,才能“画”出一个靠谱的自定义View,你要认真看我的设计图,尽管文章最后面会给出完整代码地址,不过如果你认真看设计图,完全可以自己画哦~~

第一个情况设计图

第一个情况设计图
好了根据设计图能看到:

  • 我们的View以控件的中间为分界线,分别向左右两边滚动。
  • 每个bar长度都是一样的,除了左右两边的第一个。另外相邻bar之间的间距也是一样。
  • 我们需要关注三个参数,第一个bar的宽度,其他普通bar的宽度,两个bar之间的间隔。

我们只要绘制一边,另一边是对称的,所以绘制的逻辑是:

  1. 从中间开始,先绘制第一个较特殊的bar,然后间隔barSpace距离,绘制第二个宽度为barWidth的bar,重复,直到你的绘制位置超出控间的右边界。
  2. 绘制完成,同样操作对左边也绘制一次,注意传入API里面的关于坐标相关的参数。此时完成一次绘制,注意了只是一次,我们需要将firstBarWidth变大一些,然后调用invalidate()方法重绘,这样才能显示出动态的效果。

代码片段如下所示:

//第一个"bar"宽度小于普通"bar",注意我的坐标系原点在控件中间if(firstBarWidth<=barWidth){    //绘制右边第一个“条”    //index是绘制索引    int index = 0;    canvas.drawLine(index, 0, firstBarWidth, 0, barPaint);    index+=firstBarWidth+barSpace;    //循环绘制右边其它普通的“条”    while (index <= measuredWidth/2){        canvas.drawLine(index, 0, index+barWidth, 0, barPaint);        index+=barWidth+barSpace;    }    //绘制左边第一个“条”    index = 0;    canvas.drawLine(-firstBarWidth, 0, index, 0, barPaint);    index-=(firstBarWidth+barSpace);    //循环绘制左边其它普通的“条”    while (index >= -measuredWidth/2){        canvas.drawLine(index-barWidth, 0, index, 0, barPaint);        index-=(barWidth+barSpace);    }    firstBarWidth+=barWidth/10;}

第二个情况设计图

为什么会有两种情况,你是否想过,当firstBarWidth(第一个bar的宽度)达到普通的bar宽度时,该怎么办?直接将变量重置为0,从头画过?绝对不是,仔细想想你会发现,如果你这样做的话,会让滚动条有一个被“抽”一下的感觉,这种思考是错误的,那么正确的是怎样,来看第二个设计图
第一个bar宽度达到普通宽度时的情况
当第一个bar达到了普通bar的宽度,我们就要执行另外一个绘制逻辑:

  1. 从绘制起点开始,先间隔一小段的距离(长度为firstBarDistance),才开始绘制第一个bar。
  2. 然后间隔barWidth距离,绘制第二个,重复,直到绘制起点超过控件边界。这样就完成了一次绘制,类似于第一个情况,为了体现动态效果,我们需要将firstBarDistance变大一些,然后调用invalidate()方法重绘,这样才能显示出动态的效果。
  3. 问题在于,firstBarDistance达到什么程度才好,其实当他的值等于barSpace时,就是第二个情况的临界点了,在下一刻,就回到了第一个情况的起点,此时只要重置所有计算参数就可以。

代码片段如下所示:

//第一个"bar"宽度达到普通的"bar"宽度时//循环绘制右边的“条”float index = firstBarDistance;while (index <= measuredWidth/2){    canvas.drawLine(index, 0, index+barWidth, 0, barPaint);    index+=barWidth+barSpace;}//循环绘制左边的“条”index = -firstBarDistance;while (index >= -measuredWidth/2){    canvas.drawLine(index-barWidth, 0, index, 0, barPaint);    index-=(barWidth+barSpace);}firstBarDistance+=barWidth/10;//到达临界点,全部重置if(firstBarDistance > barSpace) {    firstBarDistance = barWidth/10;    firstBarWidth = barWidth/10;}

注意一下

针对我们这个View的特点,在你正式绘图之前,我提一下:

  • 在onDraw()里面,在所有的绘图操作执行之前,调用一下这个方法:
canvas.translate(measuredWidth/2, getMeasuredHeight()/2);

让坐标系移到中间,可以简化一些计算。

  • 提前看看canvas.drawLine()方法,在画线时不要传错参数了哦。
  • invalidate()方法可以引发控间重绘,如果你想控制重绘频率,postInvalidateDelayed();方法是个不错的替代品。
  • 没错,除了一些数学计算之外,我们就仅仅用到了Paint对象基本创建,Canvas类的drawLine()方法、translate()方法,就是这么简单啦。

项目源码:
https://github.com/JaffarOu/SimpleCustomView

下期预告

下篇文章我将分享这样子的效果,一个圆形的温度显示器
这里写图片描述

0 0