5-2 IOS 7 --View Controller 轉場

自定義轉場動畫

iOS 7 中最讓我激動的特性之一就是提供了新的 API 來支持自定義 view contrioller 之間的轉場動畫。iOS 7 發布之前,我自己寫過一些 view controller 之間的轉場動畫,這是一個比較頭疼的過程,而且這種做法并不被蘋果完全地支持,尤其是如果你想讓這個轉場動畫有交互式的效果就更難了。

在繼續閱讀之前,我需要先聲明一下:這個 API 是新近才發布的,目前還沒有所謂的最佳實踐。通常來說,開發者需要探索幾個月才能得出關于新 API 的最佳實踐。因此請將本文看做對一個新 API 的探索,而非關于這個新 API 的最佳實踐介紹。如果您有更好的關于這個 API 的實踐,請不吝賜教,我們會把您的實踐更新到這篇文章中。

在開始研究新的 API 之間,我們先來看看在 iOS 7 中 navigation controller 之間的默認的行為發生了那些改變:在 navigation controller 中,切換兩個 view controller 的動畫變得更有交互性。比方說你想要 pop 一個 view controller 出去,你可以用手指從屏幕的左邊緣開始拖動,慢慢地把當前的 view controller 向右拖出屏幕去。

接下來,我們來看看這個新 API。很有趣的一個現象是,這部分 API 大量的使用了協議而不是具體的對象。這初看起來有點奇怪,但我個人更喜歡這樣的 API 設計,因為這種設計給了我們這些開發者更大的靈活性。下面,讓我們來做件簡單的事情:在 Navigation Controller 中,實現一個自定義的 push 動畫效果(本文中的示例代碼托管在 Github)。為了完成這個任務,需要實現UINavigationControllerDelegate中的新方法:

編者注:原文的作者在 Github 上面的示例代碼和文章中的代碼有一些出入(比如下面這里是 Push,但是在示例代碼中是 Pop)。如果需要,您也可以參考這個修正版示例代碼,和文章的代碼差異要小一點。


從上面的代碼可以看出,我們可以根據不同的 operation(Push 或 Pop)返回不同的 animator。我們可以把 animator 存到一個屬性中,從而在多個 operation 之間實現共享,或者我們也可以為每個 operation 都創建一個新的 animator 對象,這里的靈活性很大。

為了讓動畫運行起來,我們創建一個自定義類,并且實現UIViewControllerContextTransitioning這個協議:


這個協議要求我們實現兩個方法,其中一個定義了動畫的持續時間:


另一個方法描述整個動畫的執行效果:


從上面的例子中,你可以看到如何運用協議的:這個方法中通過接受一個類型為id的參數,來獲取 transition context。值得注意的是,執行完動畫之后,我們需要調用 transitionContext 的completeTransition:這個方法來更新 view controller 的狀態。剩下的代碼和 iOS 7 之前的一樣了,我們從 transition context 中得到了需要做轉場的兩個 view controller,然后使用最簡單的UIViewanimation 來實現了轉場動畫。這就是全部代碼了,我們已經實現了一個縮放效果的轉場動畫。

注意,這里只是為 Push 操作實現了自定義效果的轉場動畫,對于 Pop 操作,還是會使用默認的滑動效果,另外,上面我們實現的轉場動畫無法交互,下面我們就來看看解決這個問題。

交互式的轉場動畫

想要動畫變地可以交互非常簡單,我們只需要覆蓋另一個UINavigationControllerDelegate的方法:


注意,在非交互式動畫效果中,該方法返回 nil。

這里返回的 interaction controller 是UIPercentDrivenInteractionTransition類的一個實例,開發者不需要任何配置就可工作。我們創建了一個拖動手勢(Pan Recognizer),下面是處理該手勢的代碼:


編者注:這里的代碼有一點示意的意思,和實際代碼有些出入,為了尊重原作者,我們沒有進行修改,您可以參考原文在 Github 上的示例代碼進行對比,也可以參考這個修正版示例代碼

只有當用戶從屏幕右半部分開始觸摸的時候,我們才把下一次動畫效果設置為交互式的(通過設置interactionController這個屬性來實現),然后執行方法performSegueWithIdentifier:(如果你不是使用的 storyboards,那么就直接調用pushViewController...這類方法)。為了讓轉場動畫持續進行,我們需要調用 interaction controller 的一個方法:


該方法會根據用戶手指拖動的距離計算一個百分比,切換的動畫效果也隨著這個百分比來走。最酷的是,interaction controller 會和 animation controller 一起協作,我們只使用了簡單的UIViewanimation 的動畫效果,但是interaction controller 卻控制了動畫的執行進度,我們并不需要把 interaction controller 和 animation controller 關聯起來,因為所有這些系統都以一種解耦的方式自動地替我們完成了。

最后,我們需要根據用戶手勢的停止狀態來判斷該操作是結束還是取消,并調用 interaction controller 中對應的方法:


注意,當切換完成或者取消的時候,記得把 interaction controller 設置為 nil。因為如果下一次的轉場是非交互的, 我們不應該返回這個舊的 interaction controller。

現在我們已經實現了一個完全自定義的可交互的轉場動畫了。通過簡單的手勢識別和 UIKit 提供的一個類,用幾行代碼就達到完成了。對于大部分的應用場景,你讀到這兒就夠用了,使用上面提到的方法就可以達到你想要的動畫效果了。但如果你想更對轉場動畫或者交互效果進行深度定制,請繼續閱讀下面一節。

使用 GPUImage 定制動畫

下面我們就來看看如何真正的,徹底的定制動畫效果。這一次我們不使用 UIView animation,甚至連 Core Animation 也不用,完全自己來實現所有的動畫效果。在Letterpress-style這個項目中,剛開始我嘗試使用 Core Image 來做動畫效果,但是在我的 iPhone 4 上,動畫的渲染最高只能達到 9 幀/秒,離我想要的 60 幀/秒差得很遠。

但是當我使用了GPUImage之后,實現一個非常漂亮的動畫變的異常簡單。這里我們要實現的轉場效果是:兩個 view controller 像素化,然后相互消融在一起。實現方法是先對兩個 view controller 進行截屏,然后再用 GPUImage 的圖片濾鏡(filter)處理這兩張截圖。

首先,我們先創建一個自定義類,這個類實現了UIViewControllerAnimatedTransitioning和UIViewControllerInteractiveTransitioning這兩個協議:


為了加速動畫的運行,我們可以把圖片一次加載到 GPU 中,然后所有的處理和繪圖都直接在 GPU 上執行,不需要再傳送到 CPU 處理(這種數據傳輸非常慢)。通過使用 GPUImageView,我們就可以直接使用 OpenGL 畫圖(我們不需要手寫 OpenGL 這種底層的代碼,只要繼續使用 GPUImage 封裝好的接口就可以)。

創建濾鏡鏈(filter chain)也非常的直觀,我們可以直接在樣例代碼的setup方法中看到如何構造它。比較有挑戰的是如何讓濾鏡也“動”起來。GPUImage 沒有直接提供給我們動畫效果,因此我們需要每渲染一幀就更新一下濾鏡來實現動態的濾鏡效果。使用CADisplayLink可以完成這個工作:

編者注:原文中的示例代碼中缺少了這一章的內容,我在原作者的 Github Gist 上找到了相關的源碼,整理之后放到了 Github 上,您可以在這里找到它。


在frame方法中,我們可以根據時間來更新動畫進度,并相應地更新濾鏡:


好了,基本上這樣就完成了。如果你想要實現交互式的轉場效果,那么在這里,就不能使用時間,而是要根據手勢來更新動畫進度,其他的代碼基本差不多。

這個功能非常強大,你可以使用 GPUImage 中任何已有的濾鏡,或者寫一個自己的 OpenGL著色器(shader)來達到你想要的效果。

結論

本文只探討了在 navigation controller 中的兩個 view controller 之間的轉場動畫,但是這些做法在 tab bar controller 或者任何你自己定義的 view controller 容器中也是通用的。另外,在 iOS 7 中,UICollectionViewController也進行了擴展,現在你可以在布局之間進行自動以及交互的動畫切換,背后使用的也是同樣的機制。這真是太強大了。

在和Orta討論這個 API 的時候,他提到他已經在大量地使用這些機制以創建更輕量的 view controller。與其在一個 view controller 中維護各種狀態,不如再創建一個新的 view controller,使用自定義的轉場動畫,然后在這個轉場動畫中來移動你的各種 view。

擴展閱讀

WWDC: Custom Transitions using View Controllers

Custom UIViewController transitions

iOS 7: Custom Transitions

Custom View Controller Transitions with Orientation


翻譯作者:方一雄

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • 前言的前言 唐巧前輩在微信公眾號「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各項指標...
    VincentHK閱讀 5,404評論 3 44
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,180評論 4 61
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,771評論 25 708
  • 今天看到一句話“你取悅自己的樣子,真美”,心里突然有所觸動。 前一陣子或許是因為身邊的人和事變動太大,自己煩躁不安...
    君悅君語閱讀 149評論 0 1
  • 【教子】小學語文背課文的16大訣竅!一定要為孩子收藏! 背課文一直是中國人幾千年來的老大難。為什么我們會覺得孩子背...
    藝趣書苑閱讀 549評論 0 1