Swift开源项目:以Emoji表情为基础的Rating控件TTGEmojiRate的实现

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
查看查看211 回复回复2 收藏收藏 分享淘帖 转播转播 分享分享 微信
查看: 211|回复: 2
收起左侧

Swift开源项目:以Emoji表情为基础的Rating控件TTGEmojiRate的实现

[复制链接]
SwiftRobot 发表于 2016-7-13 23:29:18 | 显示全部楼层 |阅读模式
快来登录
获取优质的苹果资讯内容
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
订阅梳理好了的知识点专辑
本帖最后由 SwiftRobot 于 2016-7-13 23:35 编辑

1、前言

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

Github: http://github.com/zekunyan/TTGEmojiRate

Swift开源项目:TTGEmojiRate的实现 1

Swift开源项目:以Emoji表情为基础的Rating控件TTGEmojiRate的实现 - 敏捷大拇指 - Swift开源项目:TTGEmojiRate的实现 1

TTGEmojiRate Example




2、分析

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

Swift开源项目:TTGEmojiRate的实现 2

Swift开源项目:以Emoji表情为基础的Rating控件TTGEmojiRate的实现 - 敏捷大拇指 - Swift开源项目:TTGEmojiRate的实现 2

Rating Version A - Hoang Nguyen

可以看出来,主要的特点如下:

  • 可以上下拖动,改变Emoji表情嘴的弧度。
  • 拖动的时候Rate的值也会随之变化,从0到5,并且跟表情的“喜怒”相对应。
  • 颜色也会变化,从绿色到蓝色再到红色,也对应表情的“喜怒”。


实际实现的时候,增加了眼睛元素,并且增强了自定义,如颜色的变化范围、线条的粗细等都可以设定,基本的思路还是不变的。




3、实现



3.1、思路

开始写代码之前,先理理思路。

拖动的时候,直接影响的应该是Rate值,然后在Rate值改变的时候刷新整个控件,刷新的时候重绘。重绘的时候,嘴、眼睛的弧度,颜色的值都要根据Rate值重新计算,如下图:

Swift开源项目:TTGEmojiRate的实现 3

Swift开源项目:以Emoji表情为基础的Rating控件TTGEmojiRate的实现 - 敏捷大拇指 - Swift开源项目:TTGEmojiRate的实现 3




3.2、拖动改变Rate值

这个还是很容易实现的,直接重写UIView的touch相关的三个方法,在里面记录拖动在Y轴上的变化值,然后映射到Rate值上就可以了。

先声明一个CGPoint属性,用来保存手指按下时的点位置:

[Swift] 纯文本查看 复制代码
private var touchPoint: CGPoint? = nil


在手指移动的时候,在touchesMoved方法里面计算当前点跟上一次触摸点的Y轴上的差值,然后映射到Rate值上。

[Swift] 纯文本查看 复制代码
public override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    // 获取当前触摸点
    let currentPoint = touches.first?.locationInView(self)
    // 改变Rate值
    rateValue = rateValue + Float((currentPoint!.y - touchPoint!.y) / CGRectGetHeight(self.bounds) * rateDragSensitivity)
    // 保存当前触摸点
    touchPoint = currentPoint
}


注意:

  • 计算Y轴差值的时候,除以了当前控件的高度,这是为了保证Rate值按比例增减。
  • 增加了一个rateDragSensitivity属性,用来调节改变Rate值的“灵敏度”。




3.3、UIBezierPath - 贝塞尔曲线

控件的主要内容都是用UIBezierPath绘制出来的。网上关于UIBezierPath的讲解很多,在这里就不详细说了。

简单来说,UIBezierPath用来绘制矢量路径,是一种参数曲线,在使用的时候,只需要先设定好锚点、控制点,系统就可以根据贝塞尔曲线的算法,绘制出对应的线,并且保证锚点和对应的控制点的连线与曲线相切。

全球最大的Swift开发者社区敏捷大拇指 Swifthumb.com上有《开源:破解绘制贝塞尔曲线定位控制点难题 Swift+OC》可以看看,这里还有一个演示绘制贝塞尔曲线过程的网站:Bézier curve



3.4、脸

脸是最简单的,就是一个圆,直接用一个方法就可以:

[Swift] 纯文本查看 复制代码
private func drawFaceWithRect(rect: CGRect) {
    let facePath = UIBezierPath(ovalInRect: rect)
    rateColor.setStroke()
    facePath.lineWidth = rateLineWidth // 线粗细
    facePath.stroke()
}


实际实现的时候可以加上Margin,防止线画到View的边界之外。



3.5、嘴、眼睛

先看看UIBezierPath提供的可以用来绘制曲线的方法:

[Swift] 纯文本查看 复制代码
addCurveToPoint(_:controlPoint1:controlPoint2:)




[Swift] 纯文本查看 复制代码
addQuadCurveToPoint(_:controlPoint:)


,如下图:

Swift开源项目:TTGEmojiRate的实现 4

Swift开源项目:以Emoji表情为基础的Rating控件TTGEmojiRate的实现 - 敏捷大拇指 - Swift开源项目:TTGEmojiRate的实现 4


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

Swift开源项目:TTGEmojiRate的实现 5

Swift开源项目:以Emoji表情为基础的Rating控件TTGEmojiRate的实现 - 敏捷大拇指 - Swift开源项目:TTGEmojiRate的实现 5

Curve拼接

这样的话,就可以通过调整两个控制点,来控制嘴、眼睛的弯曲宽度、形状。

以绘制嘴为例:

[Swift] 纯文本查看 复制代码
private func drawMouthWithRect(rect: CGRect) {
    let width = CGRectGetWidth(rect)
    let height = CGRectGetWidth(rect)

    // 左端点
    let leftPoint = CGPointMake(
        width * (1 - rateMouthWidth) / 2,
        height * (1 - rateMouthVerticalPosition))

    // 右端点
    let rightPoint = CGPointMake(
        width - leftPoint.x,
        leftPoint.y)

    // 中间点 - Y值根据当前的Rate值计算,0.3为系数
    let centerPoint = CGPointMake(
        width / 2,
        leftPoint.y + height * 0.3 * (CGFloat(rateValue) - 2.5) / 5)

    // 控制点跟中间点在X轴上的距离
    let halfLipWidth = width * rateMouthWidth * rateLipWidth / 2

    // 创建贝塞尔曲线
    let mouthPath = UIBezierPath()

    // 移动到起始点
    mouthPath.moveToPoint(leftPoint)

    // 添加左半边曲线路径
    mouthPath.addCurveToPoint(
        centerPoint,
        controlPoint1: leftPoint,
        controlPoint2: CGPointMake(centerPoint.x - halfLipWidth, centerPoint.y))

    // 添加右半边曲线路径
    mouthPath.addCurveToPoint(
        rightPoint,
        controlPoint1: CGPointMake(centerPoint.x + halfLipWidth, centerPoint.y),
        controlPoint2: rightPoint)

    // 设定样式
    mouthPath.lineCapStyle = CGLineCap.Round;
    rateColor.setStroke()
    mouthPath.lineWidth = rateLineWidth
    mouthPath.stroke()
}


说明:

  • 所有的距离、坐标都是根据当前控件的大小计算出来的。
  • rateMouthWidth为嘴的宽度与整个控件宽度的比值,即相对值。
  • rateMouthVerticalPosition为嘴的左右两个端点的Y轴坐标值,也为相对值。
  • rateLipWidth为中心点的两个控制点的距离与嘴宽度的比值,也是相对值。


眼睛的绘制跟嘴原理一致,就不再说明。



3.6、颜色的渐变

Dribbble的演示中,控件的线条颜色也是会变化的,从红色到蓝色再到绿色,是连续变化的。这个时候用常见的RGB色彩模式是不好控制的,效果也不好。

所以这个时候要用HSB色彩模式

HSB 色彩模式是基于人眼的一种颜色模式。是普及型设计软件中常见的色彩模式,其中H代表色相;S代表饱和度;B代表亮度。


Swift开源项目:TTGEmojiRate的实现 6

Swift开源项目:以Emoji表情为基础的Rating控件TTGEmojiRate的实现 - 敏捷大拇指 - Swift开源项目:TTGEmojiRate的实现 6

HSB色彩模式

对应到UIColor类,就是下面两个方法:

[Swift] 纯文本查看 复制代码
// 创建UIColor
init(hue hue: CGFloat, saturation saturation: CGFloat, brightness brightness: CGFloat, alpha alpha: CGFloat)
// 获取HSB值,注意参数
func getHue(_ hue: UnsafeMutablePointer<CGFloat>, saturation saturation: UnsafeMutablePointer<CGFloat>, brightness brightness: UnsafeMutablePointer<CGFloat>, alpha alpha: UnsafeMutablePointer<CGFloat>) -> Bool


实现的时候,为了增加可定制性,控件颜色的变化范围是可以设置的,用以下属性保存:

[Swift] 纯文本查看 复制代码
public var rateColorRange: (from: UIColor, to: UIColor)


刷新时,就可以根据当前的Rate值,重新计算颜色的HSB和alpha值:

[Swift] 纯文本查看 复制代码
let rate: CGFloat = CGFloat(rateValue / 5) // Rate值归一化

self.rateColor = UIColor.init(
    hue: hueFrom + hueDelta * rate, // 色相
    saturation: saturationFrom + saturationDelta * rate, // 饱和度
    brightness: brightnessFrom + brightnessDelta * rate, // 亮度
    alpha: alphaFrom + alphaDelta * rate // 透明度
)


说明:

  • 所有的颜色参数都是根据Rate值做线性增减。
  • xxxFrom、xxxDelta分别指HSB和alpha的起始值与变化范围,在设置rateColorRange时计算保存下来。


这样,颜色就能做到跟Rate值做连续的线性变化。



3.7、善于使用didSet

实现控件的时候,对外暴露了很多属性,如线的宽度rateLineWidth、嘴的宽度rateMouthWidth等。为了对这些属性做校验,并且在设置后刷新控件,就要用到didSet。

didSet在Swift里面,跟类的属性是一一绑定的,在对属性赋值后会被调用。

控件的大部分属性都做了校验、刷新,如下:

[Swift] 纯文本查看 复制代码
/// Mouth width. From 0.2 to 0.7.
@IBInspectable public var rateMouthWidth: CGFloat = 0.6 {
    didSet {
        // 判断上限
        if rateMouthWidth > 0.7 {
            rateMouthWidth = 0.7
        }
        // 判断下限
        if rateMouthWidth < 0.2 {
            rateMouthWidth = 0.2
        }
        // 刷新、重绘
        self.setNeedsDisplay()
    }
}




3.8、@IBDesignable、@IBInspectable

为了能在XIB、StoryBoard里面使用、编辑控件,就要用到@IBDesignable和@IBInspectable这两个关键字。

在类的前面加上@IBDesignable关键字,使IB可以预览控件:

[Swift] 纯文本查看 复制代码
@IBDesignable
public class EmojiRateView: UIView {
    // ...
}


在属性前面加上@IBInspectable,就可以在IB里面编辑属性,实时预览:

[Swift] 纯文本查看 复制代码
@IBInspectable public var rateLineWidth: CGFloat = 14 {
    // ...
}


详细的使用可以参考NSHipster上的文章:IBInspectable / IBDesignable

最后,在IB里面就是下面这样:

Swift开源项目:TTGEmojiRate的实现 7

Swift开源项目:以Emoji表情为基础的Rating控件TTGEmojiRate的实现 - 敏捷大拇指 - Swift开源项目:TTGEmojiRate的实现 7


By the way =。=

属性名字太长,在IB里面显示不完整,咋办。。。



3.9、回调

拖动改变Rate值的时候,肯定要有回调,如下定义:

[Swift] 纯文本查看 复制代码
public var rateValueChangeCallback: ((newRateValue: Float) -> Void)? = nil


在rateValue的didSet里面回调:

[Swift] 纯文本查看 复制代码
@IBInspectable public var rateValue: Float = 2.5 {
    didSet {
        // ...
        // 回调
        self.rateValueChangeCallback?(newRateValue: rateValue)
    }
}





4、总结

看似简单的一个Rating控件,从构思到实现,再到完善,一点一点朝着完美去做,收获不少~

最后,Dribbble是个好地方,贝塞尔曲线好强大,XCode 7.1写Swift还是有点卡=。=

以上。




5、相关内容参考

开源:破解绘制贝塞尔曲线定位控制点难题 Swift+OC

Swift开源项目:以Emoji表情为基础的Rating控件TTGEmojiRate的实现

维基百科 - 贝塞尔曲线

Drawing Shapes Using Bézier Paths

HSB色彩模式

IBInspectable / IBDesignable




作者:土土哥


都看到这里了,就把这篇资料推荐给您的好朋友吧,让他们也感受一下。

回帖是一种美德,也是对楼主发帖的尊重和支持。您的赞赏是我前进的方向。

*声明:敏捷大拇指是全球最大的Swift开发者社区、苹果粉丝家园、智能移动门户,所载内容仅限于传递更多最新信息,并不意味赞同其观点或证实其描述;内容仅供参考,并非绝对正确的建议。本站不对上述信息的真实性、合法性、完整性做出保证;转载请注明来源并加上本站链接,敏捷大拇指将保留所有法律权益。如有疑问或建议,邮件至marketing@swifthumb.com

*联系:微信公众平台:“swifthumb” / 腾讯微博:@swifthumb / 新浪微博:@swifthumb / 官方QQ一群:343549891(满) / 官方QQ二群:245285613 ,需要报上用户名才会被同意进群,请先注册敏捷大拇指

评分

参与人数 1金钱 +10 贡献 +10 专家分 +10 收起 理由
Anewczs + 10 + 10 + 10 32个赞!专家给力!

查看全部评分

本帖被以下淘专辑推荐:

swifter 发表于 2016-8-30 01:37:58 | 显示全部楼层
也是tutuge这哥们写的哈
瑶池 发表于 2016-7-14 16:00:38 | 显示全部楼层
直觉告诉我这个控件碉堡了
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

淘帖专辑
我要发帖

分享扩散

都看到这里了,就把这资料推荐给您的好朋友吧,让他们也感受一下。
您的每一位朋友访问此永久链接后,您都将获得相应的金钱积分奖励