版本記錄
| 版本號(hào) | 時(shí)間 |
|---|---|
| V1.0 | 2018.08.26 |
前言
如果你細(xì)看了我前面寫(xiě)的有關(guān)動(dòng)畫(huà)的部分,就知道前面介紹了
CoreAnimation、序列幀以及LOTAnimation等很多動(dòng)畫(huà)方式,接下來(lái)幾篇我們就以動(dòng)畫(huà)示例為線索,進(jìn)行動(dòng)畫(huà)的講解。相關(guān)代碼已經(jīng)上傳至GitHub - 刀客傳奇。感興趣的可以看我寫(xiě)的前面幾篇。
1. 動(dòng)畫(huà)示例(一) —— 一種外擴(kuò)的簡(jiǎn)單動(dòng)畫(huà)
2. 動(dòng)畫(huà)示例(二) —— 一種抖動(dòng)的簡(jiǎn)單動(dòng)畫(huà)
3. 動(dòng)畫(huà)示例(三) —— 仿頭條一種LOTAnimation動(dòng)畫(huà)
4. 動(dòng)畫(huà)示例(四) —— QuartzCore之CAEmitterLayer下雪??動(dòng)畫(huà)
5. 動(dòng)畫(huà)示例(五) —— QuartzCore之CAEmitterLayer煙花動(dòng)畫(huà)
6. 動(dòng)畫(huà)示例(六) —— QuartzCore之CAEmitterLayer、CAReplicatorLayer和CAGradientLayer簡(jiǎn)單動(dòng)畫(huà)
7. 動(dòng)畫(huà)示例(七) —— 基于CAShapeLayer圖像加載過(guò)程的簡(jiǎn)單動(dòng)畫(huà)(一)
8. 動(dòng)畫(huà)示例(八) —— UIViewController間轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的實(shí)現(xiàn) (一)
9. 動(dòng)畫(huà)示例(九) —— 一種復(fù)雜加載動(dòng)畫(huà)的實(shí)現(xiàn) (一)
開(kāi)始
首先我們看一個(gè)效果

每個(gè)好一點(diǎn)的iOS應(yīng)用程序都有自定義元素,自定義UI,自定義動(dòng)畫(huà)等。自定義,自定義,自定義!
如果您希望自己的應(yīng)用能夠脫穎而出,那么您必須花時(shí)間添加一些獨(dú)特的功能,這些功能將為您的應(yīng)用提供WOW因素。
在本教程中,您將構(gòu)建一個(gè)自定義text field,在其被輕敲時(shí)可以生成一個(gè)可愛(ài)的小彈性反彈動(dòng)畫(huà)。
在此過(guò)程中,您將使用許多有趣的API:
- 1)
CAShapeLayer - 2)
CADisplayLink - 3)
UIView spring animations - 4)
IBInspectable
下面我們就開(kāi)始了。
新建立一個(gè)工程,該項(xiàng)目基于Single View Application iOS\Application\Single View Application。 它目前在容器視圖中有兩個(gè)text field和一個(gè)按鈕。

你的目標(biāo)是在獲得焦點(diǎn)時(shí)給予彈性彈跳。 你說(shuō),你是如何實(shí)現(xiàn)這一目標(biāo)的?

技術(shù)很簡(jiǎn)單:您將使用四個(gè)控制點(diǎn)視圖和一個(gè)CAShapeLayer,然后使用UIView spring animations為控制點(diǎn)設(shè)置動(dòng)畫(huà)。 當(dāng)它們正在動(dòng)畫(huà)時(shí),你會(huì)在它們的位置周?chē)匦吕L制形狀。
如果這聽(tīng)起來(lái)有點(diǎn)復(fù)雜,請(qǐng)不要擔(dān)心! 它比你想象的容易。
Create a Base Elastic View - 創(chuàng)建一個(gè)彈性視圖
首先,您將創(chuàng)建基本彈性視圖,你將它作為子視圖嵌入到UITextfield中,你將為這個(gè)視圖設(shè)置動(dòng)畫(huà),讓你的控件彈性反彈。
右鍵單擊項(xiàng)目導(dǎo)航器中的ElasticUI組,然后選擇New File ...,然后選擇iOS / Source / Cocoa Touch Class模板。 點(diǎn)擊Next。
調(diào)用類ElasticView,在子類的字段中輸入UIView,并確保語(yǔ)言為Swift。 單擊“下一步”,然后單擊Create以選擇存儲(chǔ)與此新類關(guān)聯(lián)的文件的默認(rèn)位置。
首先,您需要?jiǎng)?chuàng)建四個(gè)控制點(diǎn)視圖和一個(gè)CAShapeLayer。 添加以下代碼,最終得到以下類定義:
import UIKit
class ElasticView: UIView {
private let topControlPointView = UIView()
private let leftControlPointView = UIView()
private let bottomControlPointView = UIView()
private let rightControlPointView = UIView()
private let elasticShape = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
setupComponents()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupComponents()
}
private func setupComponents() {
}
}
視圖和圖層可以立即創(chuàng)建。setUpComponents()是從所有初始化路徑調(diào)用的設(shè)置方法。 你現(xiàn)在要實(shí)現(xiàn)它。
在setupComponents()中添加以下內(nèi)容:
elasticShape.fillColor = backgroundColor?.CGColor
elasticShape.path = UIBezierPath(rect: self.bounds).CGPath
layer.addSublayer(elasticShape)
在這里,您要配置形狀圖層,將其填充顏色設(shè)置為與ElasticView的背景顏色相同,并將路徑填充為與視圖邊界相同的大小。 最后,將其添加到圖層層次結(jié)構(gòu)中。
接下來(lái),在`setupComponents()``的末尾添加以下代碼:
for controlPoint in [topControlPointView, leftControlPointView,
bottomControlPointView, rightControlPointView] {
addSubview(controlPoint)
controlPoint.frame = CGRect(x: 0.0, y: 0.0, width: 5.0, height: 5.0)
controlPoint.backgroundColor = UIColor.blueColor()
}
這會(huì)將所有四個(gè)控制點(diǎn)添加到您的視圖中。 為了幫助調(diào)試,這也將控制點(diǎn)的背景更改為藍(lán)色,以便在模擬器中很容易看到。 您將在本教程結(jié)束時(shí)刪除它。
您需要將控制點(diǎn)定位在頂部中心,底部中心,左中心和右側(cè)中心。 這使得當(dāng)您做遠(yuǎn)離視圖的動(dòng)畫(huà)時(shí),可以使用它們的定位在CAShapeLayer中繪制新路徑。
你需要經(jīng)常這樣做,所以創(chuàng)建一個(gè)新功能來(lái)做到這一點(diǎn)。 將以下內(nèi)容添加到ElasticView.swift:
private func positionControlPoints(){
topControlPointView.center = CGPoint(x: bounds.midX, y: 0.0)
leftControlPointView.center = CGPoint(x: 0.0, y: bounds.midY)
bottomControlPointView.center = CGPoint(x:bounds.midX, y: bounds.maxY)
rightControlPointView.center = CGPoint(x: bounds.maxX, y: bounds.midY)
}
該函數(shù)將每個(gè)控制點(diǎn)移動(dòng)到視圖邊緣上的正確位置。
現(xiàn)在從setupComponents()的末尾調(diào)用new函數(shù):
positionControlPoints()
在深入了解動(dòng)畫(huà)之前,您將添加一個(gè)視圖,因此您可以看到ElasticView的工作原理。 為此,您將為sb添加新視圖。
打開(kāi)Main.storyboard,將新的UIView拖到視圖控制器的視圖中,并將其Custom Class設(shè)置為ElasticView。 不要擔(dān)心它的位置,只要它在屏幕上,你就能看到發(fā)生了什么。

Build并運(yùn)行你的項(xiàng)目

看那個(gè)! 四個(gè)小藍(lán)方塊 - 這些是您在setupComponents中添加的控制點(diǎn)視圖。
現(xiàn)在,您將使用它們?cè)?code>CAShapeLayer上創(chuàng)建路徑以獲得彈性外觀。
Drawing Shapes with UIBezierPath - 使用UIBezierPath繪制形狀
在深入研究下一系列步驟之前,請(qǐng)考慮如何在2D中繪制內(nèi)容 - 您依賴于繪制線,特別是直線和曲線。 在繪制任何內(nèi)容之前,如果要繪制直線,則需要指定起始位置和結(jié)束位置;如果要繪制更復(fù)雜的內(nèi)容,則需要指定多個(gè)位置。
這些點(diǎn)是CGPoints,您可以在當(dāng)前坐標(biāo)系中指定x和y。
當(dāng)您想要繪制基于矢量的形狀(如正方形,多邊形和復(fù)雜的曲線形狀)時(shí),它會(huì)變得更復(fù)雜一些。
為了模擬彈性效果,您將繪制一個(gè)看起來(lái)像矩形的二次Bézier曲線(quadratic Bézier),但它將為矩形的每一邊都有控制點(diǎn),這樣就可以創(chuàng)建一條曲線來(lái)創(chuàng)建彈性效果。
Bézier曲線以PierreBézier命名,他是法國(guó)工程師,曾在CAD / CAM系統(tǒng)中表示曲線。 看一下二次Bézier曲線的樣子:

藍(lán)色圓圈是您的控制點(diǎn),它們是您之前創(chuàng)建的四個(gè)視圖,紅色圓點(diǎn)是矩形的角。
注意:Apple有一個(gè)針對(duì)UIBezierPath的深入的類參考文檔。 如果您想深入了解如何創(chuàng)建路徑,那么值得一試。
現(xiàn)在是時(shí)候?qū)⒗碚摳吨T實(shí)踐了! 將以下方法添加到ElasticView.swift:
private func bezierPathForControlPoints()->CGPathRef {
// 1
let path = UIBezierPath()
// 2
let top = topControlPointView.layer.presentationLayer().position
let left = leftControlPointView.layer.presentationLayer().position
let bottom = bottomControlPointView.layer.presentationLayer().position
let right = rightControlPointView.layer.presentationLayer().position
let width = frame.size.width
let height = frame.size.height
// 3
path.moveToPoint(CGPointMake(0, 0))
path.addQuadCurveToPoint(CGPointMake(width, 0), controlPoint: top)
path.addQuadCurveToPoint(CGPointMake(width, height), controlPoint:right)
path.addQuadCurveToPoint(CGPointMake(0, height), controlPoint:bottom)
path.addQuadCurveToPoint(CGPointMake(0, 0), controlPoint: left)
// 4
return path.CGPath
}
這個(gè)方法做了很多事,這里進(jìn)行詳細(xì)拆解:
- 1)創(chuàng)建一個(gè)
UIBezierPath來(lái)保持你的形狀。 - 2)將控制點(diǎn)位置提取為四個(gè)常量。 您使用
presentationLayer的原因是在動(dòng)畫(huà)期間獲取視圖的“實(shí)時(shí)”位置。 - 3)通過(guò)使用控制點(diǎn)從矩形的角到角添加曲線來(lái)創(chuàng)建路徑
- 4)將路徑作為
CGPathRef返回,因?yàn)檫@是形狀層所期望的。
在為控制點(diǎn)設(shè)置動(dòng)畫(huà)時(shí),需要調(diào)用此方法,因?yàn)樗梢宰屇粩嘀匦吕L制新形狀。 你是怎樣做的?

CADisplayLink對(duì)象是一個(gè)計(jì)時(shí)器,允許您的應(yīng)用程序?qū)⒒顒?dòng)與顯示器的刷新率同步。 您可以添加目標(biāo)和在屏幕內(nèi)容更新時(shí)調(diào)用的操作。
這是重新繪制路徑和更新形狀圖層的絕佳機(jī)會(huì)。
首先,每次需要更新時(shí)添加一個(gè)方法來(lái)調(diào)用:
func updateLoop() {
elasticShape.path = bezierPathForControlPoints()
}
然后,通過(guò)將以下變量添加到ElasticView.swift來(lái)創(chuàng)建顯示鏈接:
private lazy var displayLink : CADisplayLink = {
let displayLink = CADisplayLink(target: self, selector: Selector("updateLoop"))
displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
return displayLink
}()
這是一個(gè)懶加載變量,意味著在訪問(wèn)它之前不會(huì)創(chuàng)建它。 每次屏幕更新時(shí),它都會(huì)調(diào)用updateLoop()函數(shù)。
您將需要方法來(lái)啟動(dòng)和停止鏈接,因此添加以下內(nèi)容:
private func startUpdateLoop() {
displayLink.paused = false
}
private func stopUpdateLoop() {
displayLink.paused = true
}
每當(dāng)控制點(diǎn)移動(dòng)時(shí),你已準(zhǔn)備好繪制一條新路徑。 現(xiàn)在,你必須移動(dòng)他們!
UIView Spring Animations - UIView彈性動(dòng)畫(huà)
Apple非常擅長(zhǎng)在每個(gè)iOS版本中添加新功能,spring animations的添加可以輕松提升應(yīng)用程序的WOW因素。
它允許您使用自定義阻尼和速度(amping and velocity)為元素設(shè)置動(dòng)畫(huà),使其更加特殊和有彈性!
將以下方法添加到ElasticView.swift以使這些控制點(diǎn)移動(dòng):
func animateControlPoints() {
//1
let overshootAmount : CGFloat = 10.0
// 2
UIView.animateWithDuration(0.25, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 1.5,
options: nil, animations: {
// 3
self.topControlPointView.center.y -= overshootAmount
self.leftControlPointView.center.x -= overshootAmount
self.bottomControlPointView.center.y += overshootAmount
self.rightControlPointView.center.x += overshootAmount
},
completion: { _ in
// 4
UIView.animateWithDuration(0.45, delay: 0.0, usingSpringWithDamping: 0.15, initialSpringVelocity: 5.5,
options: nil, animations: {
// 5
self.positionControlPoints()
},
completion: { _ in
// 6
self.stopUpdateLoop()
})
})
}
以下是逐步細(xì)分:
- 1)
overshootAmount是控制點(diǎn)移動(dòng)的量。 - 2)在
spring animation中包含即將到來(lái)的UI更改,持續(xù)時(shí)間為五分之一秒。如果您不熟悉spring animation但擅長(zhǎng)物理,請(qǐng)查看UIView類參考,了解阻尼和速度變量的詳細(xì)說(shuō)明。對(duì)于我們其余的只要知道這些變量控制著動(dòng)畫(huà)如何反彈。使用數(shù)字來(lái)尋找感覺(jué)正確的配置是很正常的。 - 3)向上,向左,向下或向右移動(dòng)控制點(diǎn) - 這將是動(dòng)畫(huà)。
- 4)創(chuàng)建另一個(gè)彈簧動(dòng)畫(huà)以反彈所有內(nèi)容。
- 5)重置控制點(diǎn)位置 - 這也將是動(dòng)畫(huà)。
- 6)一旦停止移動(dòng),停止
display link。
到目前為止,您尚未調(diào)用animateControlPoints。自定義控件的主要目的是在點(diǎn)擊它時(shí)進(jìn)行動(dòng)畫(huà)處理,因此調(diào)用上述方法的最佳位置是在touchBegan中。
添加以下內(nèi)容:
override func touchesBegan(touches: Set, withEvent event: UIEvent) {
startUpdateLoop()
animateControlPoints()
}
Build并運(yùn)行,然后點(diǎn)擊View。

Refactoring and Polishing
現(xiàn)在你已經(jīng)看到了很酷的動(dòng)畫(huà),但你還有一些工作要做,以使你的ElasticView更抽象。
清除的第一個(gè)障礙是overshootAmount。 目前,它的硬編碼值為10,但以編程方式和Interface Builder更改其值非常棒。

Xcode 6.0的一個(gè)新功能是@IBInspectable,這是一種通過(guò)界面構(gòu)建器設(shè)置自定義屬性的好方法。
您將通過(guò)添加overshootAmount作為@IBInspectable屬性來(lái)利用這個(gè)非常棒的新功能,這樣您創(chuàng)建的每個(gè)ElasticView都可以具有不同的值。
將以下變量添加到ElasticView:
@IBInspectable var overshootAmount : CGFloat = 10
通過(guò)替換此行來(lái)引用animateControlPoints()中的屬性:
let overshootAmount : CGFloat = 10.0
以及下面這行
let overshootAmount = self.overshootAmount
轉(zhuǎn)到Main.storyboard,單擊ElasticView并選擇Attributes Inspector選項(xiàng)卡。

您會(huì)注意到一個(gè)新選項(xiàng)卡,其中顯示了您的視圖名稱以及一個(gè)名為Overshoot A的input field
對(duì)于使用@IBInspectable聲明的每個(gè)變量,您將在Interface Builder中看到一個(gè)新的輸入字段,您可以在其中編輯其值。
要查看此操作,請(qǐng)復(fù)制ElasticView,以便最終得到兩個(gè)視圖,并將新視圖放在當(dāng)前視圖上方,就像這樣。

在新視圖中將原始視圖中的Overshoot Amount值更改為20和40。
Build并運(yùn)行。 點(diǎn)按兩個(gè)視圖即可查看差異。 如您所見(jiàn),動(dòng)畫(huà)略有不同,并且取決于您在界面構(gòu)建器中輸入的數(shù)量。

嘗試將值更改為-40而不是40,并查看會(huì)發(fā)生什么。 您可以看到控制點(diǎn)向內(nèi)動(dòng)畫(huà),但背景似乎沒(méi)有變化。

你一定可以解決這個(gè)問(wèn)題吧!
我會(huì)給你一個(gè)線索:你需要在setupComponents方法中改變一些東西。 自己嘗試一下,但是如果你遇到困難,請(qǐng)看看下面的解決方案。
// You have to change the background color of your view after the elasticShape is created, otherwise the view and layer have the same color
backgroundColor = UIColor.clearColor()
clipsToBounds = false
干得好,你終于完成了你的ElasticView。
現(xiàn)在您已經(jīng)擁有了ElasticView,您可以將其嵌入到不同的控件中,例如text fields或按鈕。
Making an Elastic UITextfield - 做一個(gè)有彈性的UITextfield
現(xiàn)在您已經(jīng)構(gòu)建了彈性視圖的核心功能,接下來(lái)的任務(wù)是將其嵌入到自定義text fields中。
右鍵單擊項(xiàng)目導(dǎo)航器中的ElasticUI組,然后選擇New File…。 選擇iOS / Source / Cocoa Touch Class模板,然后單擊Next。
調(diào)用類ElasticTextField,在子類字段中輸入UITextfield并確保語(yǔ)言為Swift。 單擊Next,然后單擊Create。
打開(kāi)ElasticTextField.swift并用以下內(nèi)容替換其內(nèi)容:
import UIKit
class ElasticTextField: UITextField {
// 1
var elasticView : ElasticView!
// 2
@IBInspectable var overshootAmount: CGFloat = 10 {
didSet {
elasticView.overshootAmount = overshootAmount
}
}
// 3
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
// 4
func setupView() {
// A
clipsToBounds = false
borderStyle = .None
// B
elasticView = ElasticView(frame: bounds)
elasticView.backgroundColor = backgroundColor
addSubview(elasticView)
// C
backgroundColor = UIColor.clearColor()
// D
elasticView.userInteractionEnabled = false
}
// 5
override func touchesBegan(touches: Set, withEvent event: UIEvent) {
elasticView.touchesBegan(touches, withEvent: event)
}
}
那里有很多事情發(fā)生!這是一步一步細(xì)分:
- 1)這是保存
ElasticView的A屬性。 - 2)一個(gè)名為
overshootAmount的IBInspectable變量,因此您可以通過(guò)Interface Builder更改控件的彈性。它會(huì)覆蓋didSet并只設(shè)置彈性視圖的overshootAmount。 - 3)該類的標(biāo)準(zhǔn)初始值設(shè)定項(xiàng),因此調(diào)用設(shè)置方法。
- 4)這是您設(shè)置
text field的位置。讓我們進(jìn)一步細(xì)分:- A)將
clipsToBounds更改為false。這使彈性視圖超出其父級(jí)邊界,并將UITextField的邊框樣式更改為.None以展平控件。 - B)創(chuàng)建并添加
ElasticView作為控件的子視圖。 - C)更改控件的
backgroundColor以使其clear;你這樣做是因?yàn)槟阆M?code>ElasticView決定顏色。 - D)最后,這會(huì)將
ElasticView的userInteractionEnabled設(shè)置為false。否則,它會(huì)從您的控制中竊取。 - E)覆蓋
touchesBegan并將其轉(zhuǎn)發(fā)到您的ElasticView,以便它可以設(shè)置動(dòng)畫(huà)。
- A)將
轉(zhuǎn)到Main.storyboard,選擇UITextfield的兩個(gè)實(shí)例,并在Identity Inspector中將它們的類從UITextField更改為ElasticTextField。
此外,請(qǐng)確保刪除為測(cè)試目的而添加的兩個(gè)ElasticView實(shí)例。

Build并運(yùn)行,點(diǎn)按您的textfield,注意它實(shí)際上是如何工作的:

原因是當(dāng)您在代碼中創(chuàng)建ElasticView時(shí),它會(huì)獲得清晰的背景顏色,并將其傳遞給形狀圖層。
要更正此問(wèn)題,只要在視圖上設(shè)置新的背景顏色,就需要一種方式將顏色傳遞到shape layer。
Forwarding the Background Color - 傳遞背景色
因?yàn)橐褂?code>elasticShape作為視圖的主要背景,所以必須覆蓋ElasticView中的backgroundColor。
將以下代碼添加到ElasticView.swift:
override var backgroundColor: UIColor? {
willSet {
if let newValue = newValue {
elasticShape.fillColor = newValue.CGColor
super.backgroundColor = UIColor.clearColor()
}
}
}
在設(shè)置值之前,將調(diào)用willSet。 檢查是否已傳遞值,然后將elasticShape的fillColor設(shè)置為用戶選擇的顏色。 然后,您調(diào)用super并將其背景顏色設(shè)置為clear。
Build并運(yùn)行,你應(yīng)該有一個(gè)可愛(ài)的彈性控制。

Final Tweaks - 最后的調(diào)整
請(qǐng)注意UITextField的占位符文本與左邊緣的接近程度。 它有點(diǎn)舒服,你不覺(jué)得嗎? 你想自己解決這個(gè)問(wèn)題嗎?
看下面的解決方案。
// Add some padding to the text and editing bounds
override func textRectForBounds(bounds: CGRect) -> CGRect {
return CGRectInset(bounds, 10, 5)
}
override func editingRectForBounds(bounds: CGRect) -> CGRect {
return CGRectInset(bounds, 10, 5)
}
Removing Debug Information - 刪除調(diào)試信息
打開(kāi)ElasticView.swift并從setupComponents中刪除以下內(nèi)容。
controlPoint.backgroundColor = UIColor.blueColor()
到目前為止就全部完成了。
源碼
1. ElasticView.swift
import UIKit
class ElasticView: UIView {
private let topControlPointView = UIView()
private let leftControlPointView = UIView()
private let bottomControlPointView = UIView()
private let rightControlPointView = UIView()
private let elasticShape = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
setupComponents()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupComponents()
}
private func setupComponents() {
elasticShape.fillColor = backgroundColor?.CGColor
elasticShape.path = UIBezierPath(rect: self.bounds).CGPath
layer.addSublayer(elasticShape)
for controlPoint in [topControlPointView, leftControlPointView,
bottomControlPointView, rightControlPointView] {
addSubview(controlPoint)
controlPoint.frame = CGRect(x: 0.0, y: 0.0, width: 5.0, height: 5.0)
}
positionControlPoints()
// You have to change the background color of your view after the elasticShape is created, otherwise the view and layer have the same color
backgroundColor = UIColor.clearColor()
clipsToBounds = false
}
private func positionControlPoints(){
topControlPointView.center = CGPoint(x: bounds.midX, y: 0.0)
leftControlPointView.center = CGPoint(x: 0.0, y: bounds.midY)
bottomControlPointView.center = CGPoint(x:bounds.midX, y: bounds.maxY)
rightControlPointView.center = CGPoint(x: bounds.maxX, y: bounds.midY)
}
private func bezierPathForControlPoints()->CGPathRef {
// 1
let path = UIBezierPath()
// 2
let top = topControlPointView.layer.presentationLayer().position
let left = leftControlPointView.layer.presentationLayer().position
let bottom = bottomControlPointView.layer.presentationLayer().position
let right = rightControlPointView.layer.presentationLayer().position
let width = frame.size.width
let height = frame.size.height
// 3
path.moveToPoint(CGPointMake(0, 0))
path.addQuadCurveToPoint(CGPointMake(width, 0), controlPoint: top)
path.addQuadCurveToPoint(CGPointMake(width, height), controlPoint:right)
path.addQuadCurveToPoint(CGPointMake(0, height), controlPoint:bottom)
path.addQuadCurveToPoint(CGPointMake(0, 0), controlPoint: left)
// 4
return path.CGPath
}
private lazy var displayLink : CADisplayLink = {
let displayLink = CADisplayLink(target: self, selector: Selector("updateLoop"))
displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
return displayLink
}()
func updateLoop() {
elasticShape.path = bezierPathForControlPoints()
}
private func startUpdateLoop() {
displayLink.paused = false
}
private func stopUpdateLoop() {
displayLink.paused = true
}
@IBInspectable var overshootAmount : CGFloat = 10
func animateControlPoints() {
//1
let overshootAmount = self.overshootAmount
// 2
UIView.animateWithDuration(0.25, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 1.5,
options: nil, animations: {
// 3
self.topControlPointView.center.y -= overshootAmount
self.leftControlPointView.center.x -= overshootAmount
self.bottomControlPointView.center.y += overshootAmount
self.rightControlPointView.center.x += overshootAmount
},
completion: { _ in
// 4
UIView.animateWithDuration(0.45, delay: 0.0, usingSpringWithDamping: 0.15, initialSpringVelocity: 5.5,
options: nil, animations: {
// 5
self.positionControlPoints()
},
completion: { _ in
// 6
self.stopUpdateLoop()
})
})
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
startUpdateLoop()
animateControlPoints()
}
override var backgroundColor: UIColor? {
willSet {
if let newValue = newValue {
elasticShape.fillColor = newValue.CGColor
super.backgroundColor = UIColor.clearColor()
}
}
}
}
2. ElasticTextField.swift
import UIKit
class ElasticTextField: UITextField {
// 1
var elasticView : ElasticView!
// 2
@IBInspectable var overshootAmount: CGFloat = 10 {
didSet {
elasticView.overshootAmount = overshootAmount
}
}
// 3
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
// 4
func setupView() {
// A
clipsToBounds = false
borderStyle = .None
// B
elasticView = ElasticView(frame: bounds)
elasticView.backgroundColor = backgroundColor
addSubview(elasticView)
// C
backgroundColor = UIColor.clearColor()
// D
elasticView.userInteractionEnabled = false
}
// 5
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
elasticView.touchesBegan(touches, withEvent: event)
}
// Add some padding to the text and editing bounds
override func textRectForBounds(bounds: CGRect) -> CGRect {
return CGRectInset(bounds, 10, 5)
}
override func editingRectForBounds(bounds: CGRect) -> CGRect {
return CGRectInset(bounds, 10, 5)
}
}
后記
本篇主要講述了一種彈性動(dòng)畫(huà)的實(shí)現(xiàn),感興趣的給個(gè)贊或者關(guān)注~~~
