iOS動(dòng)畫之自定義轉(zhuǎn)場動(dòng)畫(一)

隨便說兩句

最近在學(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è)效果如下圖:

tanzi.gif

自定義轉(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í)很簡單册招,大的步驟主要是兩步:

  1. 需要一個(gè)遵循UIViewControllerTransitioningDelegate協(xié)議的對象來提供動(dòng)畫控制器(具體動(dòng)畫如何實(shí)現(xiàn)的類)岔激。
  2. 創(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í)的知識。资溃。武翎。好久沒寫過字了,見諒溶锭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宝恶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子趴捅,更是在濱河造成了極大的恐慌垫毙,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拱绑,死亡現(xiàn)場離奇詭異综芥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)猎拨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門膀藐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人红省,你說我怎么就攤上這事额各。” “怎么了吧恃?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵虾啦,是天一觀的道長。 經(jīng)常有香客問我痕寓,道長傲醉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任呻率,我火速辦了婚禮硬毕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘筷凤。我一直安慰自己昭殉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布藐守。 她就那樣靜靜地躺著挪丢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卢厂。 梳的紋絲不亂的頭發(fā)上乾蓬,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機(jī)與錄音慎恒,去河邊找鬼任内。 笑死撵渡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的死嗦。 我是一名探鬼主播趋距,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼越除!你這毒婦竟也來了节腐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤摘盆,失蹤者是張志新(化名)和其女友劉穎翼雀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孩擂,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狼渊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了类垦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狈邑。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖护锤,靈堂內(nèi)的尸體忽然破棺而出官地,到底是詐尸還是另有隱情酿傍,我是刑警寧澤烙懦,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站赤炒,受9級特大地震影響氯析,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜莺褒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一掩缓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧遵岩,春花似錦你辣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至誊锭,卻和暖如春表悬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丧靡。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工蟆沫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留籽暇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓饭庞,卻偏偏與公主長得像戒悠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子舟山,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 前言的前言 唐巧前輩在微信公眾號「iOSDevTips」以及其博客上推送了我的文章后救崔,我的 Github 各項(xiàng)指標(biāo)...
    VincentHK閱讀 5,365評論 3 44
  • 路漫漫其修遠(yuǎn)兮,吾將上下而求索 前記 想研究自定義轉(zhuǎn)場動(dòng)畫很久了捏顺,時(shí)間就像海綿六孵,擠一擠還是有的,花了差不多有10天...
    半笑半醉間閱讀 7,470評論 10 51
  • 奧拉夫。 版本第一打野拆座,強(qiáng)勢打野主巍,S6世界總決賽上最活躍打野。 但是比較考驗(yàn)隊(duì)友的配合挪凑。 在低分段千萬不要使用孕索,狂...
    黃銅刀閱讀 1,649評論 0 0
  • iOS小菜一碟:1:用代碼修改圖片顏色 首先需要是Xcode的assets里面的圖片 選中圖片DraggedIma...
    windgo閱讀 7,513評論 5 9
  • 樂樂的分房睡,從樂樂的五周歲多我們就開始和她說:等到六周歲的時(shí)候就要自己睡了躏碳。樂樂很是嘚瑟搞旭,到處和人說,她六歲過完...
    醉翁之意_966e閱讀 228評論 0 0