iOS交互式動畫詳解(下):iOS 10 的新變化

轉自http://www.infoq.com/cn/articles/ios-interactive-animation-p2

不久前結束的WWDC 2016 Session 216: Advances in UIKit Animations and Transitions介紹了 iOS 10 的新動畫 API,讓動畫與交互無縫連接,這是「開發者的大事、大快所有人心的大好事」。在上篇我探討了 iOS 10 以下的系統中如何使用 UIView Animation 實現交互動畫,本篇來探討 iOS 10 帶來的變化。

新 API 的改進

新 API 的核心是UIViewPropertyAnimator類,在UIViewAnimating協議中定義了交互動畫需要的所有基礎功能:暫停,恢復,停止,逆轉動畫以及控制動畫進度。UIView Animation 并沒有提供這些功能,這些功能都需要回到 Core Animation 作用的 CALayer 里使用分散且文檔晦澀難懂的 API 來實現。UIViewImplicitlyAnimating協議主要補充了與 UIView Animation 類似的添加動畫 Block 的方法。

UITimingCurveProvider協議重新封裝了時間函數,而UISpringTimingParameters類終于帶來了期待已久的兩點改進:

完全版本的彈簧動畫:iOS 7 引入了簡化的 Spring UIView Animation API,iOS 9 引入了無文檔的完全版本的 Spring Core Animation API;而這兩個版本的初始速度皆為數值,iOS 10 的所有彈簧動畫的速度都是向量。

UIViewPropertyAnimator類可以視為面向對象版本的 UIView Animation,以動畫 Block 為基礎的設計解決了多個 UIView 參與動畫時的交互控制,而使用 UIView Animation 時面對多個視圖參與交互動畫就需要針對每個視圖進行控制。

交互轉場的最后一塊拼圖

在轉場動畫里,非交互轉場與交互轉場之間有著明顯的界限:如果以交互轉場開始,盡管在交互結束后會切換到非交互狀態,但之后無法再次切換到交互狀態,只能等待其結束;如果以非交互轉場開始,在轉場動畫結束前是無法切換到交互控制狀態的,只能等待其結束。iOS 10 在轉場協議中引入了上述 API,這使得非交互轉場與交互轉場之間的界限不再涇渭分明。

讓轉場動畫在非交互狀態與交互狀態之間自由切換很困難,UIViewPropertyAnimator類實現了需要的所有基礎功能,使得難度降低了許多。在 session 的現場演示中,工程師演示了使用該類從頭打造可全程在非交互與交互狀態之間自由切換的轉場動畫。轉場協議為了實現高度定制化,定義的方法是比較冗余的,iOS 10 在此基礎上引入的新 API 使得協議更加復雜,雖然在演示中添加的代碼只有百來行,另一方面演示的轉場動畫本身也相對復雜,使得這一切看上去很非常復雜。

事實上,依靠UIViewPropertyAnimator類,在實現轉場動畫在非交互與交互狀態之間自由切換的基礎上,還可以大幅精簡現有的轉場協議體系。但轉場動畫本身是個很繁雜的話題,展開講將占用大量的篇幅,這部分具體內容我放在了「iOS 視圖控制器轉場詳解」更新的章節里。轉場動畫本質上是相關視圖控制器的轉換,并將其中視圖的轉換使用動畫的形式展現。除去控制器的部分,轉場動畫就與使用 UIView 下面這個方法來實現的的視圖轉換動畫無異。

transitionFromView:toView:duration:options:completion:

objc.io 在「交互式動畫」中探討了如何讓普通的動畫實現交互,這與 iOS 10 對轉場動畫的改進是一脈相承的,因此接下來我將使用UIViewPropertyAnimator類來繼續 objc.io 的探討來深度講解新 API。

新 API 實踐

要實現的效果如下:

這個簡單的位移動畫里包含了兩套交互:滑動控制(pan 手勢)和點擊控制(tap 手勢),要解決三個轉換問題,也是所有交互動畫需要解決的問題:

Animation to Gesture:動畫過程中切入滑動控制,需要中止當前的動畫并由手指來控制控制板的移動;

Gesture to Animation:滑動結束后添加新的動畫,并與當前的狀態平滑銜接,這需要 Spring 動畫;

Animation to Animation:動畫過程中每次點擊視圖后使動畫逆轉。

前面提到UIViewPropertyAnimator封裝了交互動畫需要的所有基礎功能,實現交互動畫的難度大大降低了,這篇文章似乎沒有寫的必要了。以上每個轉換問題該類都有幾種解決辦法,使用方法非常靈活,但相對地,復雜性增加了不少,也有不少地方需要注意。這次不像上篇中分別解決三個轉換問題,而是將之歸類為實現滑動控制和點擊控制,并首先解決后者。

點擊交互:逆轉動畫

先進行設置:

添加的 Animation Block 和 Completion Blcok 是一次性的,不會重復使用。接下來處理 Tap 手勢:

上面的代碼逆轉動畫的效果如同下面的 BeginFromCurrentState,而我們更需要的是更加自然的 Additive 效果,雖然在這個場景里,0.5s的動畫時間無法看出這兩種效果的差別:

實現 Additive 效果可以通過添加反向的動畫來實現,使用 UIView Animation 時也是這樣做來逆轉動畫:

//每次 Tap 手勢結束后添加向反方向運動的動畫animator.addAnimations({//targetY 為相反位置的坐標panelView.center.y = targetY })

為何不選擇這種方法?不能僅僅為了展示UIViewPropertyAnimator不同于 UIView Animation 的特性而讓效果打折,事實上,這是無奈之舉:不知是否是 Bug,當 Spring Timing 的初始速度不為(0, 0)時,這種方法無法實現 Additive 效果,而是中止動畫直接跳躍到最終位置,其他類型的 Timing 則沒有這個問題,然而這個場景里的位移動畫必須是帶初始速度的 Spring 動畫;不過即使此處不要求初始速度>0,通過添加反向動畫實現 Additive 效果的做法也會有瑕疵,同樣不知是否 Bug:最初添加的動畫的運行時間截止時,如果依然添加動畫,動畫會直接跳躍到最終位置。

其實UIViewPropertyAnimator使用初始速度不為(0, 0)的 Spring Timing 也可以實現 Additive 效果,關鍵在于isInterruptible屬性,默認為 true。禁用這個屬性后,UIViewPropertyAnimator完全與 UIView Animation 無異,上段里提到的問題都不存在;然而,禁用這個屬性后,UIViewAnimating協議里定義的與交互動畫有關的方法和屬性都不能使用:包括上面使用的暫停和逆轉動畫的功能,以及接下來會用到的停止動畫的功能,禁用后使用這些方法和屬性會觸發異常。將UIViewPropertyAnimator當作 UIView Animation 使用的話,去看上篇就好了,我在文末給出的 Demo 里示范了這種用法。

綜合來講,UIViewPropertyAnimator逆轉轉動畫的效果比不上 UIView Animation ,現在暫且帶著效果打折的遺憾繼續使用UIViewPropertyAnimator來實現滑動交互。

滑動交互:控制進度、平滑轉變

當手指接觸到視圖時,如何中止當前的動畫?UIViewPropertyAnimator給了我們兩個選擇:暫停或停止動畫。在使用 UIView Animation 時,我們直接取消了視圖的動畫,也就是停止動畫,這里選擇用該類的方式來停止動畫:

停止動畫還有另外一種使用方法:

不管手指接觸控制板視圖時是否在運動中,手指離開屏幕后都需要添加新的彈簧動畫。然而上面的方案在特定條件下有漏洞:假設此時控制板處于打開狀態(底部位置),用戶向上滑動來關閉控制板,滑動結束后控制板在動畫中移往頂部位置,如果用戶想取消這個操作,于是點擊了控制板視圖,那么控制板視圖最終并不會回到底部位置,而是在中間某個位置(滑動結束時的位置)。造成這個結果的根源在于點擊交互的實現手法:如果是通過添加反向的動畫來實現逆轉,那么就不會出現這個問題;而無論是出于展示新 API 特點的目的還是為了能夠在這里使用stopAnimation:方法,我選擇了使用isReversed屬性來逆轉動畫。滑動結束后動畫的起始位置是手指離開屏幕的位置,使用isReversed逆轉動畫最終只能回到這個位置,而這個位置肯定和控制板在打開/關閉狀態所處的位置有段差距。

選擇使用isReversed來逆轉動畫時,在所有連續類型的手勢參與的交互動畫里,使用stopAnimation:都會有這樣的漏洞。完美的解決方案是在手指接觸視圖時將其暫停,不過不注意的話也會出現這樣的漏洞:

使用pauseAnimation()能夠解決這個漏洞的原因在于:在手勢的起始階段為控制板視圖提供從底部位置到頂部位置的完整動畫,逆轉后始終能夠回到正確的位置;而使用stopAnimation:時不能提供完整路徑的動畫。

如果不在手勢的起始階段就添加動畫,而是在手勢的結束階段才添加動畫,pauseAnimation()也會出現上述漏洞;另一方面,使用stopAnimation:無法在手勢的變化階段控制動畫的進度,只能修改視圖本身。從這兩點考慮,實現轉場動畫以及在非交互與交互狀態之間自由切換應該選擇pauseAnimation()這條路線。

continueAnimation(withTimingParameters:durationFactor:)是UIViewImplicitlyAnimating協議定義的方法,這是保證交互動畫流暢的關鍵,如同使用 UIView Animation 實現交互動畫時 Spring Animation 的作用一樣。這個方法將動畫的起始位置重置為當前位置,然后繼續執行,在這里可以動態修改剩余這段動畫運行時的 Timing 和 Duration。withTimingParameters = nil時,以原來的 Timing 運行,這里以springTiming繼續剩下的動畫;動畫的剩余運行時間為durationFactor * duration,durationFactor = 0時,運行時間依然為原來的duration。因此,

animator.continueAnimation(withTimingParameters:nil, durationFactor: 0)

相當于執行animator.startAnimation()來繼續動畫。

continueAnimation(withTimingParameters:durationFactor:)結束后,animator 的 Timing 依然是初始化時的 Timing,修改只是暫時的;不過durationFactor會修改 animator 原來的的duration(規則未知,每次調用這個方法都會修改,durationFactor = 0不會修改),從而影響后面添加的動畫的運行時間,這是個奇怪的設計。

小結

上面的演示主要偏向于突出UIViewPropertyAnimator在交互方面的特性,它也完全可以當作 UIView Animation 一樣使用,也可以混合這兩種風格,我在ControlPanelAnimation中演示了多種風格實現上面的交互動畫。不過即使假設實現逆轉動畫時的各種瑕疵是實現上的 Bug,在讓普通的動畫實現交互時,UIViewPropertyAnimator相對于 UIView Animation 并不具備優勢:相比上篇中使用 UIView Animation 時的簡單,UIViewPropertyAnimator引入的交互狀態和解決不同轉換問題時看似靈活的搭配選擇,都顯得太復雜了。

不過,使用UIViewPropertyAnimator實現轉場動畫在非交互與交互狀態之間的自由切換是非常方便的,而且還能大幅精簡當前復雜的轉場協議體系,這得益于其封裝的交互功能解決了最困難的部分,具體可查看「iOS 視圖控制器轉場詳解」。

參考

WWDC 2016 Session 216: Advances in UIKit Animations and Transitions:https://developer.apple.com/videos/play/wwdc2016/216/

iOS 視圖控制器轉場詳解:https://github.com/seedante/iOS-Note/wiki/ViewController-Transition

感謝徐川對本文的審校。

給InfoQ中文站投稿或者參與內容翻譯工作,請郵件至editors@cn.infoq.com。也歡迎大家通過新浪微博(@InfoQ,@丁曉昀),微信(微信號:InfoQChina)關注我們。

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

推薦閱讀更多精彩內容