隨便說兩句
最近在學(xué)習(xí)iOS中的動(dòng)畫形用,一直想把學(xué)習(xí)的東西記錄下來就轧,由于太懶了,一直沒有動(dòng)手來寫田度。正好最近喜歡用的一款app(談資)里面有一個(gè)自定義的轉(zhuǎn)場動(dòng)畫效果不錯(cuò)妒御,自己動(dòng)手實(shí)現(xiàn)了一下。這也是我第一次在簡書上面寫文章镇饺,作為一個(gè)剛做iOS不久的菜鳥乎莉,寫的不好的大家隨便看看。奸笤。我盡量用我自己的話來描述動(dòng)畫該如何完成惋啃,從我認(rèn)為好理解的角度來寫,因?yàn)楹芏噘Y料網(wǎng)上都有了监右,官方的教程或者是別的書里面的我就不貼了(或者有時(shí)間再貼)边灭。因?yàn)檫@篇文章介紹的是最基礎(chǔ)的自定義轉(zhuǎn)場動(dòng)畫,不包括交互什么的健盒,所以后面應(yīng)該還會(huì)有更進(jìn)一步的绒瘦。還有這篇沒有太多的理論,如果涉及到理論的話扣癣,東西可能太多了惰帽,后面我把翻譯的文章加上來,里面大部分就是理論的東西了父虑。如果能對一個(gè)人有幫助该酗,也不算白寫啦!
好了士嚎,廢話不多說垂涯,開始寫!
最終效果
這篇文章最終要完成的一個(gè)效果如下圖:
自定義轉(zhuǎn)場動(dòng)畫:
iOS7開始蘋果的API中提供了自定義的轉(zhuǎn)場動(dòng)畫航邢,在模態(tài)視圖present耕赘,dismiss,導(dǎo)航視圖pop膳殷,push還有TabbarController的標(biāo)簽視圖切換都可以自定義轉(zhuǎn)場的動(dòng)畫操骡,這篇文章介紹的是模態(tài)視圖的自定義轉(zhuǎn)場動(dòng)畫九火。
思路
自定義轉(zhuǎn)場動(dòng)畫思路
要完成一個(gè)自定義的轉(zhuǎn)場動(dòng)畫,思路其實(shí)很簡單册招,大的步驟主要是兩步:
- 需要一個(gè)遵循UIViewControllerTransitioningDelegate協(xié)議的對象來提供動(dòng)畫控制器(具體動(dòng)畫如何實(shí)現(xiàn)的類)岔激。
- 創(chuàng)建一個(gè)遵循UIViewControllerAnimatedTransitioning協(xié)議的動(dòng)畫控制器,來控制轉(zhuǎn)場的動(dòng)畫具體該如何進(jìn)行是掰。
動(dòng)畫實(shí)現(xiàn)思路
要實(shí)現(xiàn)像上圖中的效果虑鼎,我的思路是為即將出現(xiàn)的view(或者即將消失)的layer創(chuàng)建一個(gè)mask(CAShapeLayer),這個(gè)mask通過UIBezierPath來繪制键痛,比如當(dāng)我們點(diǎn)擊左邊的按鈕present出新的視圖時(shí)炫彩,mask最初的path是以按鈕中心為原點(diǎn),半徑為0的貝塞爾曲線絮短,而當(dāng)轉(zhuǎn)場結(jié)束江兢,mask的最終path是以按鈕中心為原點(diǎn),以按鈕中心到屏幕右上角距離為半徑的貝塞爾曲線丁频,使用CABasicAnimation以mask layer的path作為動(dòng)畫的keyPath杉允,就可以產(chǎn)生一個(gè)逐漸變大的圓形效果。具體的關(guān)于CALayer和CABasicAnimation由于不是這篇文章的重點(diǎn)席里,這里就不詳細(xì)說了叔磷。
具體實(shí)現(xiàn)
要實(shí)現(xiàn)上圖效果,首先創(chuàng)建三個(gè)控制器:
ViewController奖磁,LeftViewController世澜,RightViewController
我們讓ViewController遵循UIViewControllerTransitioningDelegate協(xié)議,
1.提供動(dòng)畫控制器
當(dāng)點(diǎn)擊了ViewController中的左邊的按鈕時(shí)署穗,present出LeftViewController寥裂,需要設(shè)置LeftViewController的transitioningDelegate為self,這就是思路1中說的案疲,即self對象(ViewController)將為自定義轉(zhuǎn)場來提供動(dòng)畫控制器封恰。
let leftVC = UIStoryboard(name: "Main",bundle:NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("leftVC")
leftVC.transitioningDelegate = self
self.presentViewController(leftVC, animated: true) {
}
ViewController作為LeftViewController的transitioningDelegate,需要(不是必須褐啡,UIViewControllerTransitioningDelegate中所有的方法都是可選的)提供控制轉(zhuǎn)場的動(dòng)畫控制器诺舔。創(chuàng)建一個(gè)Animator類繼承自NSObject,遵循UIViewControllerAnimatedTransitioning協(xié)議备畦,具體的Animator類的實(shí)現(xiàn)下一步會(huì)說低飒,下面的代碼是提供Animator對象,因?yàn)閷τ趐resent和dismiss都是提供的同一個(gè)animator對象懂盐,所以在Animator類中聲明了一個(gè)presenting屬性來判斷當(dāng)前的動(dòng)畫是present褥赊,還是dismiss
extension ViewController: UIViewControllerTransitioningDelegate {
//提供用來present模態(tài)視圖的轉(zhuǎn)場動(dòng)畫控制器
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
animator.presenting = true
return animator
}
//提供用來dismiss模態(tài)視圖的轉(zhuǎn)場動(dòng)畫控制器
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
animator.presenting = false
return animator
}
}
2.動(dòng)畫控制器Animator的實(shí)現(xiàn)
Animator類是我們創(chuàng)建用來控制動(dòng)畫的具體實(shí)現(xiàn)的類,也是自定義轉(zhuǎn)場動(dòng)畫中最關(guān)鍵的類莉恼,Animator類需要先遵循UIViewControllerAnimatedTransitioning協(xié)議拌喉,該協(xié)議中有兩個(gè)必須要實(shí)現(xiàn)的方法:
//動(dòng)畫的持續(xù)時(shí)間
public func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval
//動(dòng)畫的具體實(shí)現(xiàn)
public func animateTransition(transitionContext: UIViewControllerContextTransitioning)
下面是Animator類具體實(shí)現(xiàn)的代碼:
enum PresentSide {
case Left
case Right
}
class Animator: NSObject ,UIViewControllerAnimatedTransitioning{
//判斷present或dismiss
var presenting: Bool = true
//左右兩個(gè)按鈕
var side: PresentSide!
var animationCenter: CGPoint!
//下面這些變量都是通過代理方法提供的transitionContext來獲取的
//我將他們都聲明成屬性速那,為了后面使用方便點(diǎn)。
var transitionContext: UIViewControllerContextTransitioning!
var containView: UIView!
var fromView: UIView!
var toView: UIView!
var fromVC: UIViewController!
var toVC: UIViewController!
var maskLayer: CAShapeLayer!
let bounds: CGRect = UIScreen.mainScreen().bounds
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}
/*
這個(gè)方法提供的是動(dòng)畫的具體實(shí)現(xiàn)尿背,在這個(gè)方法中
我們可以獲取到所有跟轉(zhuǎn)場有關(guān)系的對象
然后可以對這些對象進(jìn)行動(dòng)畫端仰,比如對轉(zhuǎn)場后的view,可以進(jìn)行
平移田藐,縮放荔烧,旋轉(zhuǎn)等transform,也可以對view的layer加入各種動(dòng)畫
*/
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
/*通過transitionContext我們可以獲取到所有跟轉(zhuǎn)場有關(guān)系的對象汽久,包括
* 轉(zhuǎn)場視圖的容器:containView
* 轉(zhuǎn)場前的ViewController(fromVC)
* 轉(zhuǎn)場后的ViewController(toVC)
* 轉(zhuǎn)場前的視圖 fromView
* 轉(zhuǎn)場后的視圖 toView
*/
self.transitionContext = transitionContext
containView = transitionContext.containerView()
fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
//讓toView的frame變?yōu)檗D(zhuǎn)場結(jié)束后最終的frame
toView.frame = transitionContext.finalFrameForViewController(toVC)
//layer
maskLayer = CAShapeLayer()
//animation
let animation = CABasicAnimation(keyPath: "path")
animation.duration = transitionDuration(transitionContext)
animation.delegate = self
if presenting { //當(dāng)前是present模態(tài)視圖
/*containView作為轉(zhuǎn)場動(dòng)畫中視圖的容器
我們?nèi)绻獙δ膫€(gè)視圖進(jìn)行動(dòng)畫
就需要把這個(gè)視圖加入到containView中鹤竭。
當(dāng)在present模態(tài)視圖的時(shí)候,我們需要處理的是即將要展示的View的layer回窘,所以需要加入toView*/
containView?.addSubview(toView)
//path的初始值
animation.fromValue = UIBezierPath(arcCenter: animationCenter,
radius: 0,
startAngle: 0,
endAngle: CGFloat(M_PI*2),
clockwise: true).CGPath
//path的結(jié)束值
animation.toValue = UIBezierPath(arcCenter: animationCenter,
radius: radius(),
startAngle: 0.0,
endAngle: CGFloat(M_PI*2),
clockwise: true).CGPath
//動(dòng)畫結(jié)束之后诺擅,我們需要保持動(dòng)畫后的效果市袖,
//所以需要加入下面兩句啡直,如果不加,會(huì)有一些不想要的東西(可以自己試一下苍碟。酒觅。)
animation.fillMode = kCAFillModeForwards
animation.removedOnCompletion = false
toView.layer.mask = maskLayer
}else{//當(dāng)前是dismiss模態(tài)視圖
//這邊跟上面是一樣的,只是dismiss的時(shí)候微峰,屬性的初始值不同,作為mask的圓是從大變小的舷丹。
containView.addSubview(toView)
containView.addSubview(fromView)
animation.fromValue = UIBezierPath(arcCenter: animationCenter,
radius: radius(),
startAngle: 0.0,
endAngle: CGFloat(M_PI*2),
clockwise: true).CGPath
animation.toValue = UIBezierPath(arcCenter: animationCenter,
radius: 0,
startAngle: 0,
endAngle: CGFloat(M_PI*2),
clockwise: true).CGPath
fromView.layer.mask = maskLayer
}
maskLayer.addAnimation(animation, forKey: nil)
}
//動(dòng)畫結(jié)束后,必須要調(diào)用completeTransition方法來結(jié)束轉(zhuǎn)場動(dòng)畫蜓肆,不然會(huì)阻塞
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
if presenting {
maskLayer.removeFromSuperlayer()
}else{
//這里處理的是按鈕大小根據(jù)點(diǎn)擊變化的動(dòng)畫颜凯,與轉(zhuǎn)場沒有直接關(guān)系
let clickedBtn = side == .Left ? (toVC as! ViewController).leftBtn : (toVC as! ViewController).rightBtn
UIView.animateWithDuration(0.3, animations: {
clickedBtn.transform = CGAffineTransformMakeScale(1, 1)
})
}
//*** 這個(gè)方法在轉(zhuǎn)場動(dòng)畫結(jié)束后一定要手動(dòng)調(diào)用!
transitionContext.completeTransition(true)
}
}
extension Animator {
func radius() -> CGFloat{
let width = bounds.size.width
let radius: CGFloat
if side == .Left {
radius = sqrt(CGFloat(animationCenter.x * animationCenter.x + animationCenter.y * animationCenter.y))
}else{
let x = animationCenter.x - width
radius = sqrt(CGFloat(x * x + animationCenter.y * animationCenter.y))
}
return radius
}
}
這就是完成最開始效果的自定義轉(zhuǎn)場動(dòng)畫的基本代碼仗扬,具體的代碼可以到這里下載:
代碼
總結(jié):
關(guān)于自定義轉(zhuǎn)場動(dòng)的實(shí)現(xiàn)症概,最主要的的就是轉(zhuǎn)場動(dòng)畫控制器(上面的Animator)的具體實(shí)現(xiàn)部分,這一部分的思路就是我們拿到轉(zhuǎn)場前和轉(zhuǎn)場后的view早芭,對view的動(dòng)畫就可以按照正常的思路彼城,想加入各種動(dòng)畫都可以。
資料:
如果想要研究一下轉(zhuǎn)場動(dòng)畫退个,或者Core Animation的原理的募壕,可以去看看這些資料:
《iOS Core Animation: Advanced Techniques》
RayWenderlich的博客
《iOS.Animations.by.Tutorials.v2.0》
當(dāng)然還有官方文檔:
Customizing the Transition Animations
RayWenderlich的博客里面寫的非常好,還有書也很不錯(cuò)语盈,可惜太貴了(我上網(wǎng)上找的舱馅,原諒我確實(shí)買不起)還有就是都是英文的,如果有需要pdf的可以找我要刀荒,就不在這里發(fā)了习柠。
OK匀谣,以后爭取一個(gè)星期寫一篇來記錄學(xué)習(xí)的知識。资溃。武翎。好久沒寫過字了,見諒溶锭。