写在前头,最近看cocoaChina上面整理出了很多有名的博客,自己做iOS两年了,也想好好做出点东西来而不是每天只知道挣那点工资,所以买了服务器新建博客了,拿以前仿写的组件来练习写写博客。这个组件是有一天晚上睡前听QQ音乐做定时关闭时看见的,觉得效果不错而且能用上的地方也应该不少,所以萌生了仿写一个的想法,第二天便开始动手。文章尽量都是跟大家一起交流分析思路,我觉得仿东西最重要的是观察和分析,看代码时间也有好久了,我也重新看了下代码才开始写的,如有写错的地方望见谅~~

Jasper(ZR)原创文章,转载请注明出处。

QQ.png
f15b95b46e912d12.gif

首先分析功能和效果,以此为入手点:

  1. slider可以通过点击目标点来设置时间。
  2. 可以通过拖拽来设置,拖拽手松后自动释放到最近的点。
  3. 在滑动的过程中会对经过的点做提高文字和加粗文字的动画效果。

再分析UI组成:

  1. 有正常的横线和节点以及touchPad组成。
  2. 有标题,标题的个数可以自定义。

先做头文件的定义及Slider的构造函数定义,构造函数应包含frame和title(数组),这样就确定了构造返回来的Slider的具体大小,位置和各个节点的标题。delegate函数暂时只写一个返回当前选择index的函数,因为是初次实现功能的仿写,先不做更完善的重构。属性应设置有横线view、横线粗细度、第一节点和最后节点距边缘的间距、标题数组、touchPad按钮、当前选择的index。综上分析,头文件代码如下:

再写.m具体实现,这里横线采用UIView,touchPad采用UIImageView,目的是view可以自定义粗细和背景颜色,这里把view做成1像素高,把背景颜色当做是横线的颜色,这样横线就做成了。touchPad采用imageView的原因是如果不设置图片可以直接设置背景色和阴影也能做出有扁平的效果,如果有美术的出图,也可以更方便的设置上去。构造函数代码如下:

构造函数里面主要根据传入的frame和标题设置横线的大小和节点标题,其中把间距和线宽作为全局属性是因为后面也常用到这两个变量。再更完善的时候其实也可以把线的颜色和节点颜色等等这些Style上的属性单独提出来,方便复用的时候自定义更多的样式。

这里的思路是先add一个横线view,之后遍历节点标题数组,把每个节点标题取出来构造小节点view放到横线上,并且add到节点数组中,然后对应构造UILabel设置标题,放到节点正上方,也add到标题数组中。这里仅仅只是设置节点跟标题的Y坐标,保证高度对了就行,待最后添加完全部的节点和标题之后,调用一次resetPoint函数来计算修改每个节点的X坐标,在resetPoint中再根据当前的point调用resetTitle来计算当前节点的标题X坐标。

有人要问为什么这里不每add一个节点就即使计算X坐标,这里1是因为初次仅为实现功能,代码设计的时候没考虑那么周到和完整,很多也都是边写边修正的,2是这个resetPoint函数在其之后的滑动动画中也离不开,所以就复用这个函数提高复用度了。

至此UI元素上已经都完全满足最开始分析出来的需求,一个不能滑动的,静止的Slider已经制作出来了,下一步是完成功能上的需求,主要分为滑动、判定选中节点、动画。滑动和点击使用touch事件来写。

这里touch事件的思路是这样的,分别实现点击开始、拖动、结束、取消这4个函数,Slider的操作模式有两种,1是点击目标节点,2是滑动到目标节点。这里是1还是2通过touchView来判断,如果点击开始的时候touchView是之前定义的touchPad,那么就说明是开始滑动,即使是点了一下touchPad,也算是滑动(因为不是点在节点上),执行滑动处理函数,具体滑动怎么处理一会再讲;如果touchView不是touchPad,那么就说明是点击节点操作,这时候应该执行点击处理函数,把touchPad移动到选中的节点上。

那么滑动处理和点击处理是怎么做的呢,下面就来讲解这两块:

计算因为横线上的节点是用UIView去做的,仅仅是显示出节点的作用,并不具备点击的功能,也没考虑去用btn做,觉得麻烦。所以这里点击目标节点是通过touch点击的距离来判定的,具体思路为,touch相对于横线的X坐标再减去左间距,就是点击处相对于第一个节点的距离,我这里把他叫做跨度,然后用它去除以每个节点间线的长度,就得到了当前点击的X坐标越过了几个节点。假设先把他当做当前选中的节点。因为跨度不可能正好等于节点的X坐标,都会或多或少些,所以我们用 跨度 – 节点数 * 节点间距 = 剩余长度 来得到跨度的余数,节点是有一个判定范围的,这个范围我定义成节点间距的一半又三分之一,也就是说余数大于节点间距的一半又多了长度的三分之一,那就说明跨过了当前选中点而且更接近下一个点,那么真正的选中点就应该是之前假设的跨过整数点 + 1,如果余数比节点间距一半减去三分之一,那就是真正的选中点更接近于当前假设的选中点,则假设选中点无需+1,最后,有了当前的选中点,就可以resetTitle,主要是设置frame坐标和是否加粗。PS:这里面现在写博客的时候感觉当时写麻烦了,也是因为第一次为了实现功能,很多都是边写边改的,之后一直没有重构完善,现在看来判定可以写的更简单些,设定一个节点判定范围,比如余数 大于 整个节点间距 – 判定范围,就是更接近下一个点,如果余数 小于 判定范围,就是更接近假设点。

上一段讲了怎么做点击节点的处理,这一段讲滑动处理,在touchMove里面主要有两个执行函数,1是拖动touchPad,主要是通过计算center和max、min函数限制拖动范围来实现拖动效果,2是执行setMoveTitleAnimation来在滑动过程中动画的改变标题效果。拖动touchPad没什么好说的,我代码尽可能详细注释了,这里主要说说怎么动画改变标题效果,其实这块当初没太考虑效率,我做东西一般两遍,第一次追求快速实现功能,第二次优化重构,技术有限啦,所以目前是这么个套路。动画改变标题其实是touchMove的循环调用,每次都计算判定,跟上一段讲的跨度原理一样,每move一次都执行一次,然后给当前选中的节点标题改变样式。

最后,在应该的地方做delegate的调用和动画的调用,大体就完成了。详细.m文件如下:

写在最后,因为是第一个练手文章,掌握不好讲解的详细程度,讲个少了怕看不懂,讲的多了以一个Slider而言有点还觉得过于详细。代码写的也不是特别好,大家见谅,更重要的是想跟大家交流分析思路的方法~~~这个博文写都有点文字性多,如有不懂的地方希望大家画下图就理解了。