自定義過渡動畫

歷時5天從各種英文教程中學(xué)習(xí)到的過渡動畫夺克,是一個很難忘的探索經(jīng)歷

比較好的參考文章自定義UIViewController過渡入門 怠堪,動畫入門

轉(zhuǎn)場方式

首先讓我們來了解iOS轉(zhuǎn)場的方式:

  • UINavigationController push/pop UIViewController導(dǎo)航欄的轉(zhuǎn)場
  • UITabBarController 切換Tab的轉(zhuǎn)場
  • present/dismiss 模態(tài)的方式轉(zhuǎn)場

這是iOS提供的3種基本轉(zhuǎn)場方式骑祟,默認(rèn)的轉(zhuǎn)場方式轉(zhuǎn)場風(fēng)格有限。例如模態(tài)轉(zhuǎn)場中,盡管有modalPresentationStylemodalTransitionStyle關(guān)于展現(xiàn)風(fēng)格和過渡風(fēng)格的設(shè)置得哆,但是轉(zhuǎn)場仍是死板從底部彈出脯颜。這并不能滿足我們在軟件開發(fā)的需求,側(cè)邊欄贩据、頂部欄的動畫效果都無法很好地實現(xiàn)栋操。在iOS 7.0之后Apple提供一套完整的自定義過渡動畫的API,為各種轉(zhuǎn)場動畫的實現(xiàn)帶了無限可能饱亮。

這里主要介紹模態(tài)轉(zhuǎn)場的自定義動畫矾芙。
頂部欄的動畫效果:

15.gif

SlideMune 的源碼

Modal 轉(zhuǎn)場

模態(tài)轉(zhuǎn)場分為非交互式轉(zhuǎn)場交互式轉(zhuǎn)場
非交互式轉(zhuǎn)場也就是普通轉(zhuǎn)場近上,轉(zhuǎn)場的動畫無法交互剔宪,不能在動畫的過程中終止轉(zhuǎn)場。
交互式轉(zhuǎn)場能通過手指觸摸屏幕通過滑動體驗過渡動畫的進行壹无,并能終止動畫過程葱绒。

在這里插入圖片描述

過渡動畫API

我們定義:如果視圖控制器Apresent之后展示視圖控制器B。在后文中斗锭,源視圖控制器fromVC地淀,目標(biāo)視圖控制器toVC

狀態(tài) 視圖控制器A 視圖控制器B
Present 源視圖控制器 目標(biāo)視圖控制器
Dissmiss 目標(biāo)視圖控制器 源視圖控制器
在這里插入圖片描述

transitioningDelegate 過渡代理

每個視圖控制器UIViewController都有一個transitioningDelegate屬性岖是,該代理需遵循UIViewControllerTransitioningDelegate協(xié)議帮毁,提供相關(guān)動畫控制器实苞。

每當(dāng)您顯示或關(guān)閉視圖控制器時,UIKit都會要求其過渡代理使用動畫控制器烈疚。要將默認(rèn)動畫替換為您自己的自定義動畫黔牵,必須實現(xiàn)過渡代理,并使其返回適當(dāng)?shù)膭赢嬁刂破鳌?/p>

AnimationController 動畫控制器

過渡代理在present/dismiss時返回相應(yīng)的動畫控制器胞得。動畫控制器是過渡動畫的核心荧止。它完成了動畫過渡的“繁重工作”。

TransitioningContext 過渡語境

過渡語境在過渡過程中實現(xiàn)并起著至關(guān)重要的作用:它封裝了有關(guān)過渡中涉及的視圖和視圖控制器的信息阶剑。過渡語境輔助動畫控制器實現(xiàn)動畫跃巡。
從圖中可以看出,自己并沒有實現(xiàn)此協(xié)議牧愁。UIKit會為您創(chuàng)建和配置過渡上下文素邪,并在每次發(fā)生過渡時將其傳遞給動畫控制器。

非交互式過渡動畫的過程

以present過渡動畫為例:

  1. 通過代碼或StoryBoard segue觸發(fā)模態(tài)視圖present過程猪半。
  2. UIKit向toVC(目標(biāo)視圖控制器)請求其過渡代理兔朦。如果沒有,UIKIt將使用標(biāo)準(zhǔn)的內(nèi)置過渡磨确。
  3. 然后沽甥,UIKit通過來向過渡代理請求動畫控制器animationController(forPresented:presenting:source:)。如果返回nil乏奥,則過渡將使用默認(rèn)動畫摆舟。
  4. UIKit構(gòu)造過渡語境。UIKit通過調(diào)用向動畫控制器詢問動畫的持續(xù)時間transitionDuration(using:)邓了。UIKit animateTransition(using:)在動畫控制器上調(diào)用以執(zhí)行過渡的動畫恨诱。
  5. 最后,動畫控制器調(diào)用completeTransition(_:)過渡上下文以指示動畫已完成骗炉。

dimiss過渡動畫的步驟幾乎相同照宝。
在這種情況下,UIKit向fromVC視圖控制器(正在關(guān)閉的視圖控制器)請求其過渡代理句葵,要求提供動畫控制器animationController(forDismissed:)厕鹃。

非交互式過渡動畫需要提供的條件

  1. 設(shè)置過渡動畫代理。設(shè)置(目標(biāo)視圖控制器)的transitioningDelegate屬性乍丈,即設(shè)置過渡動畫代理對象熊响,該代理對象遵循UIViewControllerTransitioningDelegate協(xié)議,實現(xiàn)forPresentedforDismissed兩個方法诗赌,分別提供present和dismiss的視圖控制器實例汗茄。
  2. 創(chuàng)建動畫控制器。創(chuàng)建present和dismiss的動畫控制器铭若,實現(xiàn)動畫持續(xù)時間和構(gòu)造動畫方法洪碳。

這里我們完成一個簡單的從左到右的過渡動畫递览。

1. 構(gòu)建present/dimiss場景

這里不再講述構(gòu)建過程,無論是stroyboard還是純代碼都是很好完成的瞳腌。這里為了方便绞铃,使用storyboard完成。
左視圖控制器為:ViewController
右視圖控制器為:LeftViewController

2. 創(chuàng)建Animation Controller

Animation Controller是自定義過渡動畫的核心對象嫂侍。
AnimationControlle繼承至NSObject儿捧,遵循UIViewControllerAnimatedTransitioning協(xié)議,實現(xiàn)兩個required方法挑宠。

class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        //要求提供動畫師對象的動畫持續(xù)時間屬性
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        //過渡動畫的實現(xiàn)效果菲盾,也是自定義過渡動畫的核心方法,需要構(gòu)建動畫的實現(xiàn)各淀。
    }
}

這里貼出Present狀態(tài)下的AnimationController的代碼懒鉴,Dismiss狀態(tài)與其類似(動畫過程執(zhí)行反過程)。

import UIKit

class PresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.6
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        // 1 
        guard let _ = transitionContext.viewController(forKey: .from),
            let toVC = transitionContext.viewController(forKey: .to) else {
                return
        }
        
        // 2
        let containerView = transitionContext.containerView
        containerView.addSubview(toVC.view)
        let duration = transitionDuration(using: transitionContext)
        toVC.view.frame = CGRect(x: -ScreenWidth, y: 0, width: ScreenWidth, height: ScrennHeight)
        
        // 3
        UIView.animate(withDuration: duration, animations: {
            toVC.view.frame = CGRect(x: 0, y: 0, width: ScreenWidth, height: ScrennHeight)
        }) { (_) in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
        
    }
    
}

在第一個transitionDuration(using:)方法中設(shè)置動畫的持續(xù)時間碎浇。

在第二個transitionDuration(using:)方法中構(gòu)建動畫临谱。

  1. 獲取過渡動畫所需的視圖控制器及snapshot。從過渡語境中transitionContext.viewController我們可以獲取源視圖控制器fromVC目標(biāo)視圖控制器toVC奴璃,過渡語境封裝了設(shè)計的視圖控制器的信息悉默,極大地幫助我們處理視圖控制器的動畫轉(zhuǎn)化。還可以獲取fromVC和toVC的snapshot(屏幕快照)苟穆,來構(gòu)造更加復(fù)雜和優(yōu)秀的動畫抄课。
  2. 管理過渡語境的容器視圖 --- containerView和視圖動畫的位置初始化。UIKit將整個過渡封裝在容器視圖中鞭缭,以簡化視圖層次結(jié)構(gòu)和動畫的管理,容器視圖負(fù)責(zé)管理fromVC.viewtoVC.view魏颓。由UIKit創(chuàng)建的容器視圖僅包含fromVC視圖岭辣。您必須添加任何其他將參與過渡的視圖。

addSubview(_:)將新視圖置于視圖層次結(jié)構(gòu)中的所有其他視圖之前甸饱,因此添加子視圖的順序很重要沦童。

  1. 設(shè)置動畫效果。動畫有兩種實現(xiàn)方法叹话,一種是基礎(chǔ)動畫animate偷遗,另一種是關(guān)鍵幀動畫animateKeyframes。這里只是簡單地將fromVC.view從屏幕的左邊界外移動到屏幕中驼壶。<font color = red>注意:在動畫完成后需要調(diào)用completeTransition(_:)通知UIKit動畫已完成氏豌。這將確保最終狀態(tài)是一致的。</font>

3.設(shè)置過渡動畫代理

設(shè)置(目標(biāo)視圖控制器)destinationVC的modalPresentationStyle為枚舉屬性custom热凹,過渡動畫的代理為self即(源視圖控制器)ViewController泵喘。

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destinationVC = segue.destination as? LeftViewController {
            destinationVC.modalPresentationStyle = .custom
            destinationVC.transitioningDelegate = self
        }
    }

然后在(源視圖控制器)添加擴展泪电,遵循UIViewControllerTransitioningDelegate協(xié)議,實現(xiàn)forPresentedforDismissed方法纪铺。

extension ViewController: UIViewControllerTransitioningDelegate {
    
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        //目標(biāo)VC是presented相速,源VC是presenting
        guard let _ = presented as? LeftViewController, let _ = presenting as? ViewController else {
            return nil
        }
        return PresentAnimationController()
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        guard let _ = dismissed as? LeftViewController else {
            return nil
        }
        return DismissAnimationController()
    }
}

這樣就構(gòu)建好了一個簡單、基礎(chǔ)的自定義轉(zhuǎn)場過渡動畫鲜锚。

交互式過渡動畫

交互式動畫會使使用者的動畫體驗更自然舒適而不顯得尖銳突诬,給控制動畫過程留下了余地。iOS原生APP設(shè)置中便有這樣的交互式動畫的例子芜繁。

過渡動畫的進度跟隨手指的滑動來啟動/終止轉(zhuǎn)場動畫旺隙,這樣可以帶來良好的用戶交互體驗。

交互式控制器的工作方式

交互控制器通過加快浆洗,減慢甚至反轉(zhuǎn)過渡的過程來響應(yīng)觸摸事件或編程輸入催束。為了啟用交互式轉(zhuǎn)換,轉(zhuǎn)換代理必須提供一個交互控制器伏社。您已經(jīng)制作了過渡動畫抠刺。在過渡動畫的基礎(chǔ)上,Apple將交互式動畫封裝成交互控制器將響應(yīng)手勢來管理此動畫摘昌,而不是讓其像視頻一樣播放速妖。Apple提供了現(xiàn)成的UIPercentDrivenInteractiveTransition類,通過繼承該類來創(chuàng)建自己的交互式控制器聪黎。

1. 創(chuàng)建交互式控制器

我們在之前過渡動畫的基礎(chǔ)上構(gòu)建交互式過渡動畫罕容,我們首先需要創(chuàng)建交互式控制器。這里貼出顯示交互式控制器PresentInteractionController的代碼稿饰,繼承封裝好的UIPercentDrivenInteractiveTransition類锦秒。

class PresentInteractionController: UIPercentDrivenInteractiveTransition {
    var interactionInProgress = false
    private var shouldCompleteTrantision = false
    private weak var viewController: UIViewController!
    private weak var toViewController: UIViewController!
    
    init(viewController: UIViewController, toViewController: UIViewController) {
        super.init()
        self.viewController = viewController
        self.toViewController = toViewController
        prepareGestureRecognizer(in: self.viewController.view)
    }
    
    private func prepareGestureRecognizer(in view: UIView) {
        let gesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleGsture(_:)))
        gesture.edges = .left
        view.addGestureRecognizer(gesture)
    }
    
    @objc func handleGsture(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) {
        let translation = gestureRecognizer.translation(in: gestureRecognizer.view?.superview)
        var progress = translation.x / 200
        progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))
        
        switch gestureRecognizer.state {
        case .began:
            interactionInProgress = true
            viewController.present(toViewController, animated: true, completion: nil)
        case .changed:
            shouldCompleteTrantision = progress > 0.5
            update(progress)
        case .cancelled:
            interactionInProgress = false
            cancel()
        case .ended:
            interactionInProgress = false
            if shouldCompleteTrantision {
                finish()
            } else {
                cancel()
            }
        default:
            break
        }
    }
}
  • interactionInProgressBool屬性,表示交互式場景是否在發(fā)生喉镰。
  • shouldCompleteTrantisionBool屬性旅择,表示是否應(yīng)該終止過渡動畫。用于內(nèi)部管理過渡侣姆。
  • viewControllertoViewController生真,獲取源視圖控制器和目標(biāo)視圖控制器的引用,用于管理過渡某狀態(tài)present視圖控制器捺宗,達到交互式控制器與動畫控制器相聯(lián)系的作用柱蟀。
  • prepareGestureRecognizer(in:)為源視圖添加屏幕手勢的方法,這里的交互式動畫為從左往右present出VC蚜厉,所以為源視圖添加屏幕相應(yīng)在.left的手勢长已。
  • handleGsture(_:)為相應(yīng)手勢變化從而改變過渡動畫狀態(tài)的方法。通過聲明局部變量以跟蹤滑動進度translation,根據(jù)translation在視圖中獲取并計算過渡進度progress痰哨。手勢開始時胶果,您將設(shè)置interactionInProgresstrue并觸發(fā)prsent視圖控制器。手勢移動時斤斧,調(diào)用update(_:)更新過渡進度早抠。這是一種根據(jù)UIPercentDrivenInteractiveTransition您傳入的百分比移動過渡的方法。如果取消手勢撬讽,則更新interactionInProgress并取消過渡蕊连。一旦動作已經(jīng)結(jié)束,您使用的過渡進度來決定cancel()finish()游昼。

2. 控制器聯(lián)系

(源)視圖控制器與交互控制器相聯(lián)

viewController中添加以下屬性:

var presentInteractionController: PresentInteractionController?

并在viewDidLoad()方法中初始化屬性:

presentInteractionController = PresentInteractionController(viewController: self, toViewController: leftViewController)

交互控制器與動畫控制器相聯(lián)

PresentAnimationController中添加以下屬性:

let interactionController: PresentInteractionController?

并添加init方法:

init(interactionController: PresentInteractionController?) {
    self.interactionController = interactionController
}

3. 實現(xiàn)過渡代理協(xié)議方法

動畫控制器forPresented方法中修改PresentAnimationController對象的創(chuàng)建甘苍。

func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        //展現(xiàn)VC是presented,源VC是presenting
        guard let _ = presented as? LeftViewController, let fromVC = presenting as? ViewController else {
            return nil
        }
        return PresentAnimationController(interactionController: fromVC.presentInteractionController)
    }

添加以下方法:

func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        guard let animator = animator as? PresentAnimationController,
            let interactionController = animator.interactionController,
            interactionController.interactionInProgress else {
                return nil
        }
        return interactionController
    }

這首先檢查所涉及的動畫控制器是否為PresentAnimationController烘豌。如果是這樣载庭,它將獲取關(guān)聯(lián)的交互控制器的引用,并驗證用戶交互正在進行中廊佩。如果不滿足這些條件中的任何一個囚聚,它將返回nil以便轉(zhuǎn)換將繼續(xù)進行而不會發(fā)生交互。否則标锄,它將交互控制器交還給UIKit顽铸,以便它可以管理過渡。

dismiss狀態(tài)的交互式控制器的方法也是相近的料皇,最終實現(xiàn)效果如下:

2020-02-03 14.50.13.gif

源碼下載

以上便是自定義過渡動畫的全部內(nèi)容谓松,讀者學(xué)習(xí)完后可以學(xué)一些進階動畫,參考一些優(yōu)秀App的轉(zhuǎn)場動畫践剂,進而創(chuàng)建自己過渡動畫鬼譬,從而獲得良好的用戶體驗。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逊脯,一起剝皮案震驚了整個濱河市优质,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌男窟,老刑警劉巖盆赤,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贾富,死亡現(xiàn)場離奇詭異歉眷,居然都是意外死亡,警方通過查閱死者的電腦和手機颤枪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門汗捡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事扇住〈郝疲” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵艘蹋,是天一觀的道長锄贼。 經(jīng)常有香客問我,道長女阀,這世上最難降的妖魔是什么宅荤? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮浸策,結(jié)果婚禮上冯键,老公的妹妹穿的比我還像新娘。我一直安慰自己庸汗,他們只是感情好惫确,可當(dāng)我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蚯舱,像睡著了一般改化。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晓淀,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天所袁,我揣著相機與錄音,去河邊找鬼凶掰。 笑死燥爷,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的懦窘。 我是一名探鬼主播前翎,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼畅涂!你這毒婦竟也來了港华?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤午衰,失蹤者是張志新(化名)和其女友劉穎立宜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體臊岸,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡橙数,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了帅戒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灯帮。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钟哥,到底是詐尸還是另有隱情迎献,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布腻贰,位于F島的核電站吁恍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏播演。R本人自食惡果不足惜践盼,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宾巍。 院中可真熱鬧咕幻,春花似錦、人聲如沸顶霞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽选浑。三九已至蓝厌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間古徒,已是汗流浹背拓提。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留隧膘,地道東北人代态。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像疹吃,于是被迫代替她去往敵國和親蹦疑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,492評論 2 348

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