前言
项目开发中有这样一个需求:
通过滑动SeekBar来控制打赏金额,金额设置5个档次。
看到设计稿的时候,除了自定义View想不到什么更好的方法了。自定义View一直是我的短板,内心总会有点抗拒,总感觉自己做不出来。最近一段时间,也是有意无意的练习自定义View。最后决定还是用自定义View来实现这次的需求。
根据设计图,我分为3个步骤:
- 画弧线。
- 画圆球。
- 触摸事件的处理,并提供对外接口。
画弧线
圆弧
起初的想法是根据自定义View的宽高,创建一个正方形,然后画一个内切圆,截取其中的一段圆弧。大致就是这样的:
假设这样的圆存在,那么对于View的宽高有一个要求:
假定宽为a,高为b,x为半径。取一个直角三角形,勾股定理能得出这样的公式:
1 | (a/2)*(a/2) + (x-b)*(x-b) = x*x; |
化简得到:
1 | x = b/2 + (a*a) / (8*b) |
假设若成立,那么x-b>0
,得到
1 | a > 2b |
即对View的宽高有一个这样的硬性要求,才能符合预期。
另外,后续还需要画圆球,圆球的圆心肯定是需要在弧线上的,那么我每次画圆球时,圆心的坐标都需要通过三角函数来获得,会显得比较麻烦。考虑再三便放弃这种做法了。
抛物线
晚上下班回家,跟室友讨论了一波这个问题,我室友当场来了个:抛物线
。后面一想:卧槽?确实可以啊。直接确定3个点,就能确定抛物线的公式了,这样对于后续画圆球,求圆心的坐标也非常简单。于是打算第二天来做一波。
第二天。
网上一查,没查到android关于画抛物线的方法。我的内心:
于是便也放弃了这种做法。
贝塞尔
网上查阅资料的时候,关于弧线说的最多的就是利用贝塞尔曲线。
关于贝塞尔曲线的更多介绍可以参考这篇文章。
二阶贝塞尔曲线符合我的情况,于是选取的二阶贝塞尔曲线。
贝塞尔曲线的关键代码如下:
1 | path.moveTo(pointF1.x, pointF1.y); |
pointF1是起始点,pointF2是控制点,pointF3是终止点。
采用贝塞尔曲线实现的效果还不错,只是控制点是不在曲线上的,曲线无法与View的顶部相切,但也无伤大雅了。
画圆球
画圆球就比较简单了,计算出圆心坐标直接画圆即可。
1 | // 二阶贝塞尔曲线公式计算坐标 |
二阶贝塞尔曲线的公式是这样的:
我们把当前的横坐标currentX对于View宽度的比例作为t。
触摸事件处理&对外接口
触摸事件无非就是重写onTouchEvent
,这里我只对横坐标做了处理,只要横着滑,就能滑动圆球了,并没有处理纵坐标。
- ACTION_DOWN:判断点下的坐标是否在圆球返回内(我设置了20的误差),若不在圆球内,则直接返回false,那么后续就不会接收ACTION_MOVE等其他事件了。若在则返回true,接收后续的ACTION_MOVE等事件。
- ACTION_MOVE:获取x坐标,然后设置currentX,重绘。对外的接口也是在这里调用。
- default:当手指移出或者离开View时,利用Scroller使圆球平滑滑到距离最近的档次。
下面上完整代码:
1 | public class CustomArcSeekBar extends View { |
使用
在代码中使用,非常简单,在布局文件中引用:
1 | <com.lastwarmth.viewstudy.CustomArcSeekBar |
代码中:
1 | final TextView textView = (TextView) findViewById(R.id.seek_bar_progress); |
最终效果:
算是达到我的预期。
小结
- 自定义View在绘制矩形、圆等图形时,使用的坐标是要相对于View本身的,而不是相对于屏幕的坐标。
- 使用贝塞尔曲线能获得不错的曲线效果。
- Paint在画完1个东西之后记得reset,然后重新设置值,不然画笔的属性一样,得到的效果就不对。