























前段时间在Dribbble上发现了一个Rating控件的演示动画,控件以Emoji表情为基础,结合了上下滑动手势,正好最近正在深入学习iOS动画、绘图相关的知识,就尝试着用UIBezierPath实现了出来。本文就是TTGEmojiRate的实现过程。
Github: https://github.com/zekunyan/TTGEmojiRate

先看看原本的效果:Rating Version A - Hoang Nguyen

可以看出来,主要的特点如下:
实际实现的时候,增加了眼睛元素,并且增强了自定义,如颜色的变化范围、线条的粗细等都可以设定,基本的思路还是不变的。
开始写代码之前,先理理思路。
拖动的时候,直接影响的应该是Rate值,然后在Rate值改变的时候刷新整个控件,刷新的时候重绘。重绘的时候,嘴、眼睛的弧度,颜色的值都要根据Rate值重新计算,如下图:

这个还是很容易实现的,直接重写UIView的touch相关的三个方法,在里面记录拖动在Y轴上的变化值,然后映射到Rate值上就可以了。
先声明一个CGPoint属性,用来保存手指按下时的点位置:
1 | private var touchPoint: CGPoint? = nil |
在手指移动的时候,在touchesMoved方法里面计算当前点跟上一次触摸点的Y轴上的差值,然后映射到Rate值上。
1 | public override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) { |
注意:
rateDragSensitivity属性,用来调节改变Rate值的“灵敏度”控件的主要内容都是用UIBezierPath绘制出来的。网上关于UIBezierPath的讲解很多,在这里就不详细说了。
简单来说,UIBezierPath用来绘制矢量路径,是一种参数曲线,在使用的时候,只需要先设定好锚点、控制点,系统就可以根据贝塞尔曲线的算法,绘制出对应的线,并且保证锚点和对应的控制点的连线与曲线相切。
这里有一个演示绘制贝塞尔曲线过程的网站:Bézier curve
脸是最简单的,就是一个圆,直接用一个方法就可以:
1 | private func drawFaceWithRect(rect: CGRect) { |
实际实现的时候可以加上Margin,防止线画到View的边界之外。
先看看UIBezierPath提供的可以用来绘制曲线的方法:addCurveToPoint(_:controlPoint1:controlPoint2:)和addQuadCurveToPoint(_:controlPoint:),如下图:

直观上来讲,嘴、眼睛的绘制跟addQuadCurveToPoint方法绘制的效果基本一致,但是这样的效果没法调整,因为只能控制唯一的一个控制点,所以还是要用addCurveToPoint方法,对称的绘制两条曲线,拼接起来,如下图:

这样的话,就可以通过调整两个控制点,来控制嘴、眼睛的弯曲宽度、形状。
以绘制嘴为例:
1 | private func drawMouthWithRect(rect: CGRect) { |
说明:
rateMouthWidth为嘴的宽度与整个控件宽度的比值,即相对值。rateMouthVerticalPosition为嘴的左右两个端点的Y轴坐标值,也为相对值。rateLipWidth为中心点的两个控制点的距离与嘴宽度的比值,也是相对值。眼睛的绘制跟嘴原理一致,就不再说明。
Dribbble的演示中,控件的线条颜色也是会变化的,从红色到蓝色再到绿色,是连续变化的。这个时候用常见的RGB色彩模式是不好控制的,效果也不好。
所以这个时候要用HSB色彩模式
HSB 色彩模式是基于人眼的一种颜色模式。是普及型设计软件中常见的色彩模式,其中H代表色相;S代表饱和度;B代表亮度。- 百度百科

对应到UIColor类,就是下面两个方法:
1 |
|
实现的时候,为了增加可定制性,控件颜色的变化范围是可以设置的,用以下属性保存:
1 | public var rateColorRange: (from: UIColor, to: UIColor) |
刷新时,就可以根据当前的Rate值,重新计算颜色的HSB和alpha值:
1 | let rate: CGFloat = CGFloat(rateValue / 5) |
说明:
xxxFrom、xxxDelta分别指HSB和alpha的起始值与变化范围,在设置rateColorRange时计算保存下来。这样,颜色就能做到跟Rate值做连续的线性变化。
实现控件的时候,对外暴露了很多属性,如线的宽度rateLineWidth、嘴的宽度rateMouthWidth等。为了对这些属性做校验,并且在设置后刷新控件,就要用到didSet。
didSet在Swift里面,跟类的属性是一一绑定的,在对属性赋值后会被调用。
控件的大部分属性都做了校验、刷新,如下:
1 |
|
为了能在XIB、StoryBoard里面使用、编辑控件,就要用到@IBDesignable和@IBInspectable这两个关键字。
在类的前面加上@IBDesignable关键字,使IB可以预览控件:
1 | @IBDesignable |
在属性前面加上@IBInspectable,就可以在IB里面编辑属性,实时预览:
1 | @IBInspectable public var rateLineWidth: CGFloat = 14 { |
详细的使用可以参考NSHipster上的文章:IBInspectable / IBDesignable
最后,在IB里面就是下面这样:
By the way =。=
属性名字太长,在IB里面显示不完整,咋办。。。
拖动改变Rate值的时候,肯定要有回调,如下定义:
1 | public var rateValueChangeCallback: ((newRateValue: Float) -> Void)? = nil |
在rateValue的didSet里面回调:
1 | @IBInspectable public var rateValue: Float = 2.5 { |
看似简单的一个Rating控件,从构思到实现,再到完善,一点一点朝着完美去做,收获不少~
最后,Dribbble是个好地方,贝塞尔曲线好强大,XCode 7.1写Swift还是有点卡=。=
以上。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。