自定義轉場動畫
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
Custom View Controller Transitions with Orientation