layout: post
title: 创建自定义动画按钮
description: 独家翻译
image: assets/images/pic02.jpg
被追波设计的启发,我们将要去创造一个自定义的动画按钮。我希望通过这个教程,当说到去编码动效,你能至少理解一些重要的步骤。
理解动效
首先,在我们打开Xcode,跟变换(transform),图层(CALayer)的子类们等等玩耍之前,我们需要弄懂这个我们将要去构造实际动画模型是什么,这是非常重要的。盯着这个模型看一会儿,然后思考你将提供说明来简单的描述发生了什么。EZGIF提供一些非常好用,用于gif图的工具,例如,改变动画速度,然后在慢速度中查看动效。
下面的慢速度的图可能有帮助:
让我们开始打破动效图层,然后命名他们来获得一个更方便的引用:
好了,现在我们已经标记了这些图层,我们可以用帧分解来描述每一层的发生情况。下面是一个动效步骤的简单描述:
1.星图层和环图层几乎同步增长。
2.星图层和环图层停止增长,然后用比第一步更快的速度开始缩小到星图层的中心。
3.填充圆从星图层中心开始增长。
4.星图层增长到初始大小,并且填充色改变了。
5.填充圆增长到步骤二里的大小,然后缩小到初始大小。
创建路径
在iOS中CGPaths代表的路径并在代码中创建他们是一个痛苦而且耗时的任务。幸运的是的这里有个工具叫PaintCode使这个任务变得很容易。
所有你需要做的就是创建基于软件Adobe lllustrator或者Sketch的矢量图形,导出为SVG文件,导入PaintCode,然后PaintCode会提供你这个图形OC和Swift的代码。
这下面的代码代表星图层:
var star = UIBezierPath()
star.moveToPoint(CGPointMake(112.79, 119))
star.addCurveToPoint(CGPointMake(107.75, 122.6),controlPoint1: CGPointMake(113.41, 122.8), controlPoint2: CGPointMake(111.14, 124.42))
star.addLineToPoint(CGPointMake(96.53, 116.58))
star.addCurveToPoint(CGPointMake(84.14, 116.47), controlPoint1: CGPointMake(93.14, 114.76), controlPoint2: CGPointMake(87.56, 114.71))
star.addLineToPoint(CGPointMake(72.82, 122.3))
star.addCurveToPoint(CGPointMake(67.84, 118.62), controlPoint1: CGPointMake(69.4, 124.06), controlPoint2: CGPointMake(67.15, 122.41))
star.addLineToPoint(CGPointMake(70.1, 106.09))
star.addCurveToPoint(CGPointMake(66.37, 94.27), controlPoint1: CGPointMake(70.78, 102.3), controlPoint2: CGPointMake(69.1, 96.98))
star.addLineToPoint(CGPointMake(57.33, 85.31))
star.addCurveToPoint(CGPointMake(59.29, 79.43), controlPoint1: CGPointMake(54.6, 82.6), controlPoint2: CGPointMake(55.48, 79.95))
star.addLineToPoint(CGPointMake(71.91, 77.71))
star.addCurveToPoint(CGPointMake(81.99, 70.51), controlPoint1: CGPointMake(75.72, 77.19), controlPoint2: CGPointMake(80.26, 73.95))
star.addLineToPoint(CGPointMake(87.72, 59.14))
star.addCurveToPoint(CGPointMake(93.92, 59.2), controlPoint1: CGPointMake(89.46, 55.71), controlPoint2: CGPointMake(92.25, 55.73))
star.addLineToPoint(CGPointMake(99.46, 70.66))
star.addCurveToPoint(CGPointMake(109.42, 78.03), controlPoint1: CGPointMake(101.13, 74.13), controlPoint2: CGPointMake(105.62, 77.44))
star.addLineToPoint(CGPointMake(122, 79.96))
star.addCurveToPoint(CGPointMake(123.87, 85.87), controlPoint1: CGPointMake(125.81, 80.55), controlPoint2: CGPointMake(126.64, 83.21))
star.addLineToPoint(CGPointMake(114.67, 94.68))
star.addCurveToPoint(CGPointMake(110.75, 106.43), controlPoint1: CGPointMake(111.89, 97.34), controlPoint2: CGPointMake(110.13, 102.63))
star.addLineToPoint(CGPointMake(112.79, 119))
我们可以在iOS中手动创建简单的图形,例如圆图层:
let circle = UIBezierPath(ovalInRect: inFrame)
现在我们可以创建自定义的按钮了。
连接图层
Xcode6 带来一个叫做活动视图(live views)的新科技,这使得处理自定义布局代码更容易,并且不用运行或者构建视图就能提供立即的可视化的反馈。所以让我们使用活动视图!为你的类使用活动视图非常简单:
1.放置关键词@IBDesignable 在类声明之上
2.放置关键词@IBInspectable 在一个你想在故事版的属性检查器上改变的变量上。
3.重写 layoutSubviews().这是一个你将添加子视图和子图层的地方。
为了我们的星星按钮,我们将创建一个UIButton 的子类并且遵循下面的步骤:
@IBDesignable
class StarButton: UIButton
{
private var starShape: CAShapeLayer!
private var outerRingShape: CAShapeLayer!
private var fillRingShape: CAShapeLayer!
@IBInspectable
var lineWidth: CGFloat = 1 {
didSet {
updateLayerProperties()
}
}
@IBInspectable
var favoriteColor: UIColor = UIColor(hex:"eecd34") {
didSet {
updateLayerProperties()
}
}
@IBInspectable
var notFavoriteColor: UIColor = UIColor(hex:"9e9b9b") {
didSet {
updateLayerProperties()
}
}
@IBInspectable
var starFavoriteColor: UIColor = UIColor(hex:"9e9b9b") {
didSet {
updateLayerProperties()
}
}
var isFavorite: Bool = false {
didSet {
return self.isFavorite ? favorite() : notFavorite()
}
}
private func updateLayerProperties()
{
if fillRingShape != nil
{
fillRingShape.fillColor = favoriteColor.CGColor
}
if outerRingShape != nil
{
outerRingShape.lineWidth = lineWidth
outerRingShape.strokeColor = notFavoriteColor.CGColor
}
if starShape != nil
{
starShape.fillColor = isFavorite ? starFavoriteColor.CGColor : notFavoriteColor.CGColor
}
}
override func layoutSubviews()
{
super.layoutSubviews()
updateLayerProperties()
}
}
转到故事版,创建一个按钮,把它设置成自定义,去掉上面默认的文本,然后设置成我们的StarButton类。
在属性检查器上设置可视化属性。
然后看!
动效
创建动画总是一点点尝试和失败。当然,即使你有动画背景,你也不会流畅的构建动效。为了节约时间,我会直接跳向代码。为了创建动效,我已经把这个动效分成五个步骤。我们要处理的主要属性是用来转换大小的CATransform3D,用来改变阿尔法值的opacity,用来改变颜色的fillColor.
我们假定我们用这些按钮来点赞,然后这样命名函数。
private func favorite()
{
// 1. Star grows
var starGoUp = CATransform3DIdentity
starGoUp = CATransform3DScale(starGoUp, 1.5, 1.5, 1.5)
// 2. Star stop growing and starts shrinking
var starGoDown = CATransform3DIdentity
starGoDown = CATransform3DScale(starGoDown, 0.01, 0.01, 0.01)
// Configure a keyframe animation with both transforms (grow and shrink)
let starKeyFrames = CAKeyframeAnimation(keyPath: "transform")
starKeyFrames.values = [
NSValue(CATransform3D:CATransform3DIdentity),
NSValue(CATransform3D:starGoUp),
NSValue(CATransform3D:starGoDown)
]
starKeyFrames.keyTimes = [0.0,0.4,0.6]
starKeyFrames.duration = 0.4
starKeyFrames.beginTime = CACurrentMediaTime() + 0.05
starKeyFrames.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
// This is VERY important when you're working with relative time, remove and odd things will happen
starKeyFrames.fillMode = kCAFillModeBackwards
starKeyFrames.setValue(favoriteKey, forKey: starKey)
// Let the notification tell us when it's over
starKeyFrames.delegate = self
starShape.addAnimation(starKeyFrames, forKey: favoriteKey)
starShape.transform = starGoDown
// 1. Ring grows
var grayGoUp = CATransform3DIdentity
grayGoUp = CATransform3DScale(grayGoUp, 1.5, 1.5, 1.5)
// 2. Ring stop growing and starts shrinking
var grayGoDown = CATransform3DIdentity
grayGoDown = CATransform3DScale(grayGoDown, 0.01, 0.01, 0.01)
let outerCircleAnimation = CAKeyframeAnimation(keyPath: "transform")
outerCircleAnimation.values = [
NSValue(CATransform3D:CATransform3DIdentity),
NSValue(CATransform3D:grayGoUp),
NSValue(CATransform3D:grayGoDown)
]
outerCircleAnimation.keyTimes = [0.0,0.4,0.6]
outerCircleAnimation.duration = 0.4
outerCircleAnimation.beginTime = CACurrentMediaTime() + 0.01
outerCircleAnimation.fillMode = kCAFillModeBackwards
outerCircleAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
outerRingShape.addAnimation(outerCircleAnimation, forKey: "Gray circle Animation")
outerRingShape.transform = grayGoDown
// 3. Fill Circle grows from Star's center.
var favoriteFillGrow = CATransform3DIdentity
favoriteFillGrow = CATransform3DScale(favoriteFillGrow, 1.5, 1.5, 1.5)
// 5. Fill Circle grows until reach the size of step 2 and shrink back to the initial size.
let fillCircleAnimation = CAKeyframeAnimation(keyPath: "transform")
fillCircleAnimation.values = [
NSValue(CATransform3D:fillRingShape.transform),
NSValue(CATransform3D:favoriteFillGrow),
NSValue(CATransform3D:CATransform3DIdentity)
]
fillCircleAnimation.keyTimes = [0.0,0.4,0.6]
fillCircleAnimation.duration = 0.4
fillCircleAnimation.beginTime = CACurrentMediaTime() + 0.22
fillCircleAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
fillCircleAnimation.fillMode = kCAFillModeBackwards
let favoriteFillOpacity = CABasicAnimation(keyPath: "opacity")
favoriteFillOpacity.toValue = 1
favoriteFillOpacity.duration = 1
favoriteFillOpacity.beginTime = CACurrentMediaTime()
favoriteFillOpacity.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
favoriteFillOpacity.fillMode = kCAFillModeBackwards
fillRingShape.addAnimation(favoriteFillOpacity, forKey: "Show fill circle")
fillRingShape.addAnimation(fillCircleAnimation, forKey: "fill circle Animation")
fillRingShape.transform = CATransform3DIdentity
}
当第一部分的动效结束,第二部分的动效会触发
private func endFavorite()
{
// just a helper to run this piece of code with default actions disabled
executeWithoutActions {
self.starShape.fillColor = self.starFavoriteColor.CGColor
self.starShape.opacity = 1
self.fillRingShape.opacity = 1
self.outerRingShape.transform = CATransform3DIdentity
self.outerRingShape.opacity = 0
}
// 4. Star grows to it's initial size, and the filling color is changed.
let starAnimations = CAAnimationGroup()
var starGoUp = CATransform3DIdentity
starGoUp = CATransform3DScale(starGoUp, 2, 2, 2)
let starKeyFrames = CAKeyframeAnimation(keyPath: "transform")
starKeyFrames.values = [
NSValue(CATransform3D: starShape.transform),
NSValue(CATransform3D:starGoUp),
NSValue(CATransform3D:CATransform3DIdentity)
]
starKeyFrames.keyTimes = [0.0,0.4,0.6]
starKeyFrames.duration = 0.2
starKeyFrames.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
starShape.addAnimation(starKeyFrames, forKey: nil)
starShape.transform = CATransform3DIdentity
}
对于取消收藏,这个动效没有秘密:
private func notFavorite()
{
let starFillColor = CABasicAnimation(keyPath: "fillColor")
starFillColor.toValue = notFavoriteColor.CGColor
starFillColor.duration = 0.3
let starOpacity = CABasicAnimation(keyPath: "opacity")
starOpacity.toValue = 0.5
starOpacity.duration = 0.3
let starGroup = CAAnimationGroup()
starGroup.animations = [starFillColor, starOpacity]
starShape.addAnimation(starGroup, forKey: nil)
starShape.fillColor = notFavoriteColor.CGColor
starShape.opacity = 0.5
let fillCircle = CABasicAnimation(keyPath: "opacity")
fillCircle.toValue = 0
fillCircle.duration = 0.3
fillCircle.setValue(notFavoriteKey, forKey: starKey)
fillCircle.delegate = self
fillRingShape.addAnimation(fillCircle, forKey: nil)
fillRingShape.opacity = 0
let outerCircle = CABasicAnimation(keyPath: "opacity")
outerCircle.toValue = 0.5
outerCircle.duration = 0.3
outerRingShape.addAnimation(outerCircle, forKey: nil)
outerRingShape.opacity = 0.5
}
一个附赠品
因为Swift在iOS社区是一个真正的明星,我们庆祝一下!
按照下面这个CGPath修改这个星图层路径:
var swiftPath = UIBezierPath()
swiftPath.moveToPoint(CGPointMake(376.2, 283.2))
swiftPath.addCurveToPoint(CGPointMake(349.8, 238.4),controlPoint1: CGPointMake(367.4, 258.4), controlPoint2: CGPointMake(349.8, 238.4))
swiftPath.addCurveToPoint(CGPointMake(236.5, 0), controlPoint1: CGPointMake(349.8, 238.4), controlPoint2: CGPointMake(399.7, 105.6))
swiftPath.addCurveToPoint(CGPointMake(269, 180.8), controlPoint1: CGPointMake(303.7, 101.6), controlPoint2: CGPointMake(269, 180.8))
swiftPath.addCurveToPoint(CGPointMake(181.29, 117.07), controlPoint1: CGPointMake(269, 180.8), controlPoint2: CGPointMake(211.4, 140.8))
swiftPath.addCurveToPoint(CGPointMake(85, 33.6), controlPoint1: CGPointMake(151.18, 93.35), controlPoint2: CGPointMake(85, 33.6))
swiftPath.addCurveToPoint(CGPointMake(145, 117.07), controlPoint1: CGPointMake(85, 33.6), controlPoint2: CGPointMake(128.15, 96.31))
swiftPath.addCurveToPoint(CGPointMake(185.78, 163.66), controlPoint1: CGPointMake(161.85, 137.84), controlPoint2: CGPointMake(185.78, 163.66))
swiftPath.addCurveToPoint(CGPointMake(136.36, 129.42), controlPoint1: CGPointMake(185.78, 163.66), controlPoint2: CGPointMake(161.07, 147.39))
swiftPath.addCurveToPoint(CGPointMake(34.6, 50.4), controlPoint1: CGPointMake(111.65, 111.46), controlPoint2: CGPointMake(34.6, 50.4))
swiftPath.addCurveToPoint(CGPointMake(133.8, 169.2), controlPoint1: CGPointMake(34.6, 50.4), controlPoint2: CGPointMake(82.69, 119.24))
swiftPath.addCurveToPoint(CGPointMake(214.6, 244), controlPoint1: CGPointMake(184.91, 219.16), controlPoint2: CGPointMake(214.6, 244))
swiftPath.addCurveToPoint(CGPointMake(129.8, 264.8), controlPoint1: CGPointMake(214.6, 244), controlPoint2: CGPointMake(196.2, 263.2))
swiftPath.addCurveToPoint(CGPointMake(0, 221), controlPoint1: CGPointMake(63.4, 266.4), controlPoint2: CGPointMake(0, 221))
swiftPath.addCurveToPoint(CGPointMake(206.6, 339.2), controlPoint1: CGPointMake(0, 221), controlPoint2: CGPointMake(62.5, 339.2))
swiftPath.addCurveToPoint(CGPointMake(325, 304.8), controlPoint1: CGPointMake(270.6, 339.2), controlPoint2: CGPointMake(288.93, 304.8))
swiftPath.addCurveToPoint(CGPointMake(383.3, 339.2), controlPoint1: CGPointMake(361.07, 304.8), controlPoint2: CGPointMake(381.7, 340))
swiftPath.addCurveToPoint(CGPointMake(376.2, 283.2), controlPoint1: CGPointMake(384.9, 338.4), controlPoint2: CGPointMake(385, 308))
return swiftPath.CGPath
这就可以了!偶也!Swift!
就是这个!我希望你能理解多一点关于创建动效。去我的 Github找到完整的工程。当我添加一些新图形的时候,我会升级代码,所以注意这个仓库!如果你喜欢这个项目,请分享并且粉一下这个git仓库!