Swift开源项目: TTGEmojiRate的实现
(文章转载自:http://tutuge.me/2015/10/25/ttgemojirate-lib/)
Swift开源项目: TTGEmojiRate的实现
前言
前段时间在Dribbble上发现了一个Rating控件的演示动画,控件以Emoji表情为基础,结合了上下滑动手势,正好最近正在深入学习iOS动画、绘图相关的知识,就尝试着用UIBezierPath实现了出来。本文就是TTGEmojiRate的实现过程。
Github: https://github.com/zekunyan/TTGEmojiRate
分析
先看看原本的效果:Rating Version A - Hoang Nguyen
可以看出来,主要的特点如下:
- 可以上下拖动,改变Emoji表情嘴的弧度。
- 拖动的时候Rate的值也会随之变化,从0到5,并且跟表情的“喜怒”相对应。
- 颜色也会变化,从绿色到蓝色再到红色,也对应表情的“喜怒”。
实际实现的时候,增加了眼睛元素,并且增强了自定义,如颜色的变化范围、线条的粗细等都可以设定,基本的思路还是不变的。
实现
思路
开始写代码之前,先理理思路。
拖动的时候,直接影响的应该是Rate值,然后在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?) {
|
注意:
- 计算Y轴差值的时候,除以了当前控件的高度,这是为了保证Rate值按比例增减。
- 增加了一个
rateDragSensitivity属性,用来调节改变Rate值的“灵敏度”
UIBezierPath - 贝塞尔曲线
控件的主要内容都是用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
|
// 创建UIColor
|
实现的时候,为了增加可定制性,控件颜色的变化范围是可以设置的,用以下属性保存:
1
|
public var rateColorRange: (from: UIColor, to: UIColor)
|
刷新时,就可以根据当前的Rate值,重新计算颜色的HSB和alpha值:
1
|
let rate: CGFloat = CGFloat(rateValue / 5) // Rate值归一化
|
说明:
- 所有的颜色参数都是根据Rate值做线性增减。
xxxFrom、xxxDelta分别指HSB和alpha的起始值与变化范围,在设置rateColorRange时计算保存下来。
这样,颜色就能做到跟Rate值做连续的线性变化。
善于使用didSet
实现控件的时候,对外暴露了很多属性,如线的宽度rateLineWidth、嘴的宽度rateMouthWidth等。为了对这些属性做校验,并且在设置后刷新控件,就要用到didSet。
didSet在Swift里面,跟类的属性是一一绑定的,在对属性赋值后会被调用。
控件的大部分属性都做了校验、刷新,如下:
1
|
/// Mouth width. From 0.2 to 0.7.
|
@IBDesignable、@IBInspectable
为了能在XIB、StoryBoard里面使用、编辑控件,就要用到@IBDesignable和@IBInspectable这两个关键字。
在类的前面加上@IBDesignable关键字,使IB可以预览控件:
1
|
@IBDesignable
|
在属性前面加上@IBInspectable,就可以在IB里面编辑属性,实时预览:
1
|
@IBInspectable public var rateLineWidth: CGFloat = 14 {
|
详细的使用可以参考NSHipster上的文章:IBInspectable / IBDesignable
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还是有点卡=。=
以上。







浙公网安备 33010602011771号