本文是筆者學(xué)習(xí)iOS動(dòng)畫的一些小總結(jié)茄厘;
View Animation
動(dòng)畫其實(shí)就是UIView
基本屬性(animatable
)的操作油猫,我們寫動(dòng)畫的時(shí)候稠茂,其實(shí)不需要關(guān)心其中的數(shù)學(xué)計(jì)算,只需要熟悉API的特性即可情妖。
我們可以看到UIView
中的屬性中注明為animatable
的睬关,那么這個(gè)屬性就是我們寫動(dòng)畫可以用到的屬性。還有一個(gè)alpha
屬性同樣是animatable
毡证。
本文中的動(dòng)畫方法都是UIKit
為我們封裝好的一些方法电爹,也就是UIView
提供的類方法,比如常見的animate(withDuration:animations:)
,因?yàn)槭?code>UIKit層提供的料睛,所以這些動(dòng)畫的方法都相對(duì)簡(jiǎn)單好用丐箩,沒有涉及到 Core Animation
提供的復(fù)雜API。
animate
UIView
的一個(gè)extension里都是animate
的類方法恤煞,如下圖所示:
normal
UIView
的animate
類方法是最簡(jiǎn)單的動(dòng)畫方法屎勘,相信大家一定都用過。
UIView.animate(withDuration: 0.5, delay: 0.4,
options: [.repeat, .autoreverse, .curveEaseIn],
animations: {
self.password.center.x += self.view.bounds.width
},
completion: nil
)
這里想了好久不知道如何去解釋居扒,索性就不去解釋了概漱,反正iOSer能看懂就行??
這里的options
倒可以解釋下, 這個(gè)屬性可以告訴UIKit
如何去執(zhí)行animations
里的操作,是UIViewAnimationOptions
類型。常見的有
repeat // 重復(fù)的執(zhí)行`animations`里的操作
curveEaseInOut //類似汽車行駛先加速之后到達(dá)目的地時(shí)減速的效果執(zhí)行動(dòng)畫
curveEaseIn // 汽車加速
curveEaseOut // 汽車減速
curveLinear // 汽車勻速
spring
spring類型的animate類方法相對(duì)于normal類型的增加了彈簧的效果苔货,使動(dòng)畫更加符合現(xiàn)實(shí)中的動(dòng)畫效果犀概。
UIView.animate(withDuration: 1.5, delay: 0.0, usingSpringWithDamping: 0.2,
initialSpringVelocity: 0.0, options: [],
animations: {
self.loginButton.bounds.size.width += 80.0
},
completion: nil
)
方法中usingSpringWithDamping
取值為0-1立哑,數(shù)值越大,彈簧效果越不明顯姻灶。
transition
之前的normal和spring都是基于UIView
中animatable
屬性的動(dòng)畫铛绰,如果想要給添加view或者移除view做動(dòng)畫的話,就需要使用transition類型的animate類方法來實(shí)現(xiàn)产喉。
UIView.transition(with: animationContainerView,
duration: 0.33,
options: [.curveEaseOut, .transitionFlipFromBottom],
animations: {
self.animationContainerView.addSubview(newView)
},
completion: nil
)
上述代碼給animationContainerView制造了一個(gè)動(dòng)畫捂掰,當(dāng)有subview添加到animationContainerView中時(shí),動(dòng)畫就會(huì)執(zhí)行曾沈。方法中的options同樣的告訴UIKit
如何去執(zhí)行animations
里的操作,是UIViewAnimationOptions
類型这嚣。常見的有
.transitionFlipFromLeft
.transitionFlipFromRight
.transitionCurlUp
.transitionCurlDown
.transitionCrossDissolve
.transitionFlipFromTop
.transitionFlipFromBottom
當(dāng)我們想替換view時(shí),可以使用下面的方法
UIView.transition(from: oldView, to: newView, duration: 0.33,
options: .transitionFlipFromTop, completion: nil)
UIKit幫了我們太多??
keyframe
之前的三種類型的動(dòng)畫都是單一效果的塞俱,現(xiàn)實(shí)中的動(dòng)畫大多是有許多步驟的姐帚,此時(shí)如果我們使用completion來連接下一個(gè)動(dòng)畫,代碼會(huì)被嵌套很多層障涯。所以我們可以將每個(gè)單獨(dú)的步驟拆分成一個(gè)個(gè)的Keyframes罐旗,然后用Keyframe動(dòng)畫將各個(gè)單獨(dú)的Keyframes連接起來生成一個(gè)完整的動(dòng)畫。
UIView.animateKeyframes(withDuration: 1.5, delay: 0.0, animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations: {
self.planeImage.center.x += 80.0
self.planeImage.center.y -= 10.0
})
UIView.addKeyframe(withRelativeStartTime: 0.1, relativeDuration: 0.4, animations: {
self.planeImage.transform = CGAffineTransform(rotationAngle: -.pi / 8)
})
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25, animations: {
self.planeImage.center.x += 100.0
self.planeImage.center.y -= 50
self.planeImage.alpha = 0.0
})
UIView.addKeyframe(withRelativeStartTime: 0.51, relativeDuration: 0.01, animations: {
self.planeImage.transform = .identity
self.planeImage.center = CGPoint(x: 0.0, y: originalCenter.y)
})
UIView.addKeyframe(withRelativeStartTime: 0.55, relativeDuration: 0.45, animations: {
self.planeImage.alpha = 1.0
self.planeImage.center = originalCenter
})
}, completion: nil)
上例寫了一個(gè)飛機(jī)起飛到著陸的動(dòng)畫唯蝶,addKeyframe
里的withRelativeStartTime
和relativeDuration
都是相對(duì)于animateKeyframes
中withDuration
來計(jì)算的九秀。
auxiliary views
// cube animation
func cubeTransition(label: UILabel, text: String, direction: AnimationDirection) {
let auxLabel = UILabel(frame: label.frame)
auxLabel.text = text
auxLabel.font = label.font
auxLabel.textAlignment = label.textAlignment
auxLabel.textColor = label.textColor
auxLabel.backgroundColor = label.backgroundColor
let auxLabelOffset = CGFloat(direction.rawValue) * label.frame.size.height / 2.0
auxLabel.transform = CGAffineTransform(scaleX: 1.0, y: 0.1)
.concatenating(CGAffineTransform(translationX: 0.0, y: auxLabelOffset))
label.superview?.addSubview(auxLabel)
UIView.animate(withDuration: 0.5, delay: 0.0, options: [.curveEaseOut], animations: {
auxLabel.transform = .identity
label.transform = CGAffineTransform(scaleX: 1.0, y: 0.1)
.concatenating(
CGAffineTransform(translationX: 0.0, y: -auxLabelOffset)
)
}, completion: { _ in
label.text = auxLabel.text
label.transform = .identity
auxLabel.removeFromSuperview()
})
}
上述代碼寫了一個(gè)label立方體翻轉(zhuǎn)的效果,但其實(shí)不是真的3D效果粘我,這里使用到了一個(gè)輔助的label鼓蜒,對(duì)兩個(gè)label同時(shí)動(dòng)畫,最后移除這個(gè)輔助label征字,從而達(dá)到了立方體翻轉(zhuǎn)的效果都弹。
Auto Layout
之前的動(dòng)畫都是設(shè)置 animatable
的屬性,UIKit幫助我們實(shí)現(xiàn)對(duì)應(yīng)的動(dòng)畫效果柔纵。但是現(xiàn)在我們大多更多的使用Auto Layout來進(jìn)行布局界面缔杉,此時(shí)上面的幾種動(dòng)畫方法同樣也是有效的锤躁,只是此時(shí)我們操作的是約束搁料。
UIViewPropertyAnimator 更新于2017-11-11
UIViewPropertyAnimator 是iOS10新出的一個(gè)動(dòng)畫相關(guān)的類,通過這個(gè)類我們可以簡(jiǎn)單的創(chuàng)建更加易于控制的view animation系羞,也就是說此時(shí)我們?cè)谝恍﹫?chǎng)景中可以不去創(chuàng)建layer animation郭计,通過UIViewPropertyAnimator也可以完成需要。
UIViewPropertyAnimator和之前的UIView.animate系列方法是相輔相成的椒振,也就是說有些簡(jiǎn)單的情況昭伸,我們只需要使用UIView.animate系列方法就可以了,沒有必要去使用UIViewPropertyAnimator澎迎。
一個(gè)簡(jiǎn)單的UIViewPropertyAnimator創(chuàng)建的動(dòng)畫如下:
let scale = UIViewPropertyAnimator(duration: 0.33, curve: .easeIn)
// addAnimations 也可以添加之前的上文所說的keyFrame animation等
scale.addAnimations {
view.alpha = 1.0
}
scale.addAnimations({
view.transform = CGAffineTransform.identity
}, delayFactor: 0.33) // 這里的delayFactor是一個(gè)相對(duì)比例(0-1)庐杨,是相對(duì)于動(dòng)畫剩下的時(shí)間选调,這樣就保證了延遲的時(shí)間不會(huì)超過總時(shí)間
scale.addCompletion {_ in
print("ready")
}
各種不同的animator 可以封裝在單獨(dú)的一個(gè)類,這樣不同的view如果想要?jiǎng)?chuàng)建相同的動(dòng)畫直接調(diào)用方法然后start即可灵份。
animation timing
UIViewPropertyAnimator默認(rèn)提供了以下的curve選擇:
public enum UIViewAnimationCurve : Int {
case easeInOut // slow at beginning and end
case easeIn // slow at beginning
case easeOut // slow at end
case linear
}
其實(shí)這些curve都是控制兩個(gè)control point形成的貝塞爾曲線仁堪,具體可以去這個(gè)網(wǎng)站實(shí)際體驗(yàn)下curve;
UIViewPropertyAnimator提供了自定義這兩個(gè)control point的方法填渠,可以讓我們自定義timing 效果弦聂。
public convenience init(duration: TimeInterval, controlPoint1 point1: CGPoint, controlPoint2 point2: CGPoint, animations: (() -> Swift.Void)? = nil)
除了使用自定義control point的方式來自定義timing,我們還可以通過下面這個(gè)方法來為animator提供自定義的timing方式:
public init(duration: TimeInterval, timingParameters parameters: UITimingCurveProvider)
UITimingCurveProvider是一個(gè)協(xié)議氛什,UIKit提供了兩個(gè)已經(jīng)遵守該協(xié)議的類莺葫,我們可以方便的拿來使用。
UICubicTimingParameters and UISpringTimingParameters. 例如下面這個(gè)spring timing:
let spring = UISpringTimingParameters(dampingRatio: 0.55)
let animator = UIViewPropertyAnimator(duration: 1.0, timingParameters: spring)
這樣這個(gè)animator的動(dòng)畫運(yùn)行時(shí)就是彈簧效果了枪眉。
animation state
因?yàn)閁IViewPropertyAnimator可以創(chuàng)建##易于控制##的動(dòng)畫捺檬,所以UIViewPropertyAnimator可以讓我們知道現(xiàn)在動(dòng)畫的狀態(tài),以下是三個(gè)主要的狀態(tài)屬性:
1.isRunning(Bool): animator 的動(dòng)畫是否正在運(yùn)行贸铜,默認(rèn)是false欺冀,當(dāng)調(diào)用`startAnimation`后變?yōu)閠rue;
2.isReversed(Bool): animator 的動(dòng)畫執(zhí)行順序萨脑,默認(rèn)是false隐轩,當(dāng)設(shè)置為true后,動(dòng)畫會(huì)反方向執(zhí)行渤早,此時(shí)才改變沒有效果职车,也就是只能一次性設(shè)置;
3.state: animator 動(dòng)畫的狀態(tài)鹊杖,具體見下圖:
fractionComplete可以接受一個(gè)animator的完成百分比悴灵,達(dá)到一種可交互的動(dòng)畫,類似3Dtouch 的重按動(dòng)畫效果骂蓖;
ViewController Transition Animation
同樣的正是因?yàn)閁IViewPropertyAnimator可以讓我們控制動(dòng)畫的一系列狀態(tài)积瞒,我們也可以使用它來實(shí)現(xiàn)控制器的可交互切換動(dòng)畫,下面方法是iOS10中UIViewControllerAnimatedTransitioning
協(xié)議中新增的一個(gè)方法登下,允許提供一個(gè)animator:
func interruptibleAnimator(using transitionContext:
UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating
總的來說茫孔,UIViewPropertyAnimator是一個(gè)中間產(chǎn)物,為我們提供了多一種的選擇被芳,不及UIView.animate 來的簡(jiǎn)單缰贝,不及Layer Animation的全面。
最后
未完待續(xù)第二彈