layout: post
title: 創(chuàng)建自定義動畫按鈕
description: 獨家翻譯
image: assets/images/pic02.jpg
被追波設計的啟發(fā),我們將要去創(chuàng)造一個自定義的動畫按鈕。我希望通過這個教程,當說到去編碼動效,你能至少理解一些重要的步驟。
理解動效
首先,在我們打開Xcode,跟變換(transform),圖層(CALayer)的子類們等等玩耍之前,我們需要弄懂這個我們將要去構造實際動畫模型是什么,這是非常重要的。盯著這個模型看一會兒,然后思考你將提供說明來簡單的描述發(fā)生了什么。EZGIF提供一些非常好用,用于gif圖的工具,例如,改變動畫速度,然后在慢速度中查看動效。
下面的慢速度的圖可能有幫助:
讓我們開始打破動效圖層,然后命名他們來獲得一個更方便的引用:

好了,現(xiàn)在我們已經(jīng)標記了這些圖層,我們可以用幀分解來描述每一層的發(fā)生情況。下面是一個動效步驟的簡單描述:
1.星圖層和環(huán)圖層幾乎同步增長。
2.星圖層和環(huán)圖層停止增長,然后用比第一步更快的速度開始縮小到星圖層的中心。
3.填充圓從星圖層中心開始增長。
4.星圖層增長到初始大小,并且填充色改變了。
5.填充圓增長到步驟二里的大小,然后縮小到初始大小。
創(chuàng)建路徑
在iOS中CGPaths代表的路徑并在代碼中創(chuàng)建他們是一個痛苦而且耗時的任務。幸運的是的這里有個工具叫PaintCode使這個任務變得很容易。
所有你需要做的就是創(chuàng)建基于軟件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中手動創(chuàng)建簡單的圖形,例如圓圖層:
let circle = UIBezierPath(ovalInRect: inFrame)
現(xiàn)在我們可以創(chuàng)建自定義的按鈕了。
連接圖層
Xcode6 帶來一個叫做活動視圖(live views)的新科技,這使得處理自定義布局代碼更容易,并且不用運行或者構建視圖就能提供立即的可視化的反饋。所以讓我們使用活動視圖!為你的類使用活動視圖非常簡單:
1.放置關鍵詞@IBDesignable 在類聲明之上
2.放置關鍵詞@IBInspectable 在一個你想在故事版的屬性檢查器上改變的變量上。
3.重寫 layoutSubviews().這是一個你將添加子視圖和子圖層的地方。
為了我們的星星按鈕,我們將創(chuàng)建一個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()
}
}
轉(zhuǎn)到故事版,創(chuàng)建一個按鈕,把它設置成自定義,去掉上面默認的文本,然后設置成我們的StarButton類。
在屬性檢查器上設置可視化屬性。
然后看!
動效
創(chuàng)建動畫總是一點點嘗試和失敗。當然,即使你有動畫背景,你也不會流暢的構建動效。為了節(jié)約時間,我會直接跳向代碼。為了創(chuàng)建動效,我已經(jīng)把這個動效分成五個步驟。我們要處理的主要屬性是用來轉(zhuǎn)換大小的CATransform3D,用來改變阿爾法值的opacity,用來改變顏色的fillColor.
我們假定我們用這些按鈕來點贊,然后這樣命名函數(shù)。
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
}
當?shù)谝徊糠值膭有ЫY束,第二部分的動效會觸發(fā)
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社區(qū)是一個真正的明星,我們慶祝一下!
按照下面這個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!
就是這個!我希望你能理解多一點關于創(chuàng)建動效。去我的Github找到完整的工程。當我添加一些新圖形的時候,我會升級代碼,所以注意這個倉庫!如果你喜歡這個項目,請分享并且粉一下這個git倉庫!