轉(zhuǎn)自http://www.infoq.com/cn/articles/ios-interactive-animation-p2
不久前結(jié)束的WWDC 2016 Session 216: Advances in UIKit Animations and Transitions介紹了 iOS 10 的新動畫 API辉阶,讓動畫與交互無縫連接竭缝,這是「開發(fā)者的大事磕秤、大快所有人心的大好事」。在上篇我探討了 iOS 10 以下的系統(tǒng)中如何使用 UIView Animation 實現(xiàn)交互動畫买乃,本篇來探討 iOS 10 帶來的變化。
新 API 的改進
新 API 的核心是UIViewPropertyAnimator類,在UIViewAnimating協(xié)議中定義了交互動畫需要的所有基礎(chǔ)功能:暫停廷粒,恢復(fù)贴见,停止烘苹,逆轉(zhuǎn)動畫以及控制動畫進度。UIView Animation 并沒有提供這些功能片部,這些功能都需要回到 Core Animation 作用的 CALayer 里使用分散且文檔晦澀難懂的 API 來實現(xiàn)镣衡。UIViewImplicitlyAnimating協(xié)議主要補充了與 UIView Animation 類似的添加動畫 Block 的方法。
UITimingCurveProvider協(xié)議重新封裝了時間函數(shù)吞琐,而UISpringTimingParameters類終于帶來了期待已久的兩點改進:
完全版本的彈簧動畫:iOS 7 引入了簡化的 Spring UIView Animation API捆探,iOS 9 引入了無文檔的完全版本的 Spring Core Animation API;而這兩個版本的初始速度皆為數(shù)值站粟,iOS 10 的所有彈簧動畫的速度都是向量黍图。
UIViewPropertyAnimator類可以視為面向?qū)ο蟀姹镜?UIView Animation,以動畫 Block 為基礎(chǔ)的設(shè)計解決了多個 UIView 參與動畫時的交互控制奴烙,而使用 UIView Animation 時面對多個視圖參與交互動畫就需要針對每個視圖進行控制助被。
交互轉(zhuǎn)場的最后一塊拼圖
在轉(zhuǎn)場動畫里,非交互轉(zhuǎn)場與交互轉(zhuǎn)場之間有著明顯的界限:如果以交互轉(zhuǎn)場開始切诀,盡管在交互結(jié)束后會切換到非交互狀態(tài)揩环,但之后無法再次切換到交互狀態(tài),只能等待其結(jié)束幅虑;如果以非交互轉(zhuǎn)場開始丰滑,在轉(zhuǎn)場動畫結(jié)束前是無法切換到交互控制狀態(tài)的,只能等待其結(jié)束倒庵。iOS 10 在轉(zhuǎn)場協(xié)議中引入了上述 API褒墨,這使得非交互轉(zhuǎn)場與交互轉(zhuǎn)場之間的界限不再涇渭分明。
讓轉(zhuǎn)場動畫在非交互狀態(tài)與交互狀態(tài)之間自由切換很困難擎宝,UIViewPropertyAnimator類實現(xiàn)了需要的所有基礎(chǔ)功能郁妈,使得難度降低了許多。在 session 的現(xiàn)場演示中绍申,工程師演示了使用該類從頭打造可全程在非交互與交互狀態(tài)之間自由切換的轉(zhuǎn)場動畫噩咪。轉(zhuǎn)場協(xié)議為了實現(xiàn)高度定制化顾彰,定義的方法是比較冗余的,iOS 10 在此基礎(chǔ)上引入的新 API 使得協(xié)議更加復(fù)雜胃碾,雖然在演示中添加的代碼只有百來行涨享,另一方面演示的轉(zhuǎn)場動畫本身也相對復(fù)雜,使得這一切看上去很非常復(fù)雜书在。
事實上灰伟,依靠UIViewPropertyAnimator類,在實現(xiàn)轉(zhuǎn)場動畫在非交互與交互狀態(tài)之間自由切換的基礎(chǔ)上儒旬,還可以大幅精簡現(xiàn)有的轉(zhuǎn)場協(xié)議體系栏账。但轉(zhuǎn)場動畫本身是個很繁雜的話題,展開講將占用大量的篇幅栈源,這部分具體內(nèi)容我放在了「iOS 視圖控制器轉(zhuǎn)場詳解」更新的章節(jié)里挡爵。轉(zhuǎn)場動畫本質(zhì)上是相關(guān)視圖控制器的轉(zhuǎn)換,并將其中視圖的轉(zhuǎn)換使用動畫的形式展現(xiàn)甚垦。除去控制器的部分茶鹃,轉(zhuǎn)場動畫就與使用 UIView 下面這個方法來實現(xiàn)的的視圖轉(zhuǎn)換動畫無異。
transitionFromView:toView:duration:options:completion:
objc.io 在「交互式動畫」中探討了如何讓普通的動畫實現(xiàn)交互艰亮,這與 iOS 10 對轉(zhuǎn)場動畫的改進是一脈相承的闭翩,因此接下來我將使用UIViewPropertyAnimator類來繼續(xù) objc.io 的探討來深度講解新 API。
新 API 實踐
要實現(xiàn)的效果如下:
這個簡單的位移動畫里包含了兩套交互:滑動控制(pan 手勢)和點擊控制(tap 手勢)迄埃,要解決三個轉(zhuǎn)換問題疗韵,也是所有交互動畫需要解決的問題:
Animation to Gesture:動畫過程中切入滑動控制,需要中止當(dāng)前的動畫并由手指來控制控制板的移動侄非;
Gesture to Animation:滑動結(jié)束后添加新的動畫蕉汪,并與當(dāng)前的狀態(tài)平滑銜接,這需要 Spring 動畫逞怨;
Animation to Animation:動畫過程中每次點擊視圖后使動畫逆轉(zhuǎn)者疤。
前面提到UIViewPropertyAnimator封裝了交互動畫需要的所有基礎(chǔ)功能,實現(xiàn)交互動畫的難度大大降低了叠赦,這篇文章似乎沒有寫的必要了驹马。以上每個轉(zhuǎn)換問題該類都有幾種解決辦法,使用方法非常靈活除秀,但相對地窥翩,復(fù)雜性增加了不少,也有不少地方需要注意鳞仙。這次不像上篇中分別解決三個轉(zhuǎn)換問題,而是將之歸類為實現(xiàn)滑動控制和點擊控制笔时,并首先解決后者棍好。
點擊交互:逆轉(zhuǎn)動畫
先進行設(shè)置:
添加的 Animation Block 和 Completion Blcok 是一次性的,不會重復(fù)使用。接下來處理 Tap 手勢:
上面的代碼逆轉(zhuǎn)動畫的效果如同下面的 BeginFromCurrentState借笙,而我們更需要的是更加自然的 Additive 效果扒怖,雖然在這個場景里,0.5s的動畫時間無法看出這兩種效果的差別:
實現(xiàn) Additive 效果可以通過添加反向的動畫來實現(xiàn)业稼,使用 UIView Animation 時也是這樣做來逆轉(zhuǎn)動畫:
//每次 Tap 手勢結(jié)束后添加向反方向運動的動畫animator.addAnimations({//targetY 為相反位置的坐標(biāo)panelView.center.y = targetY })
為何不選擇這種方法盗痒?不能僅僅為了展示UIViewPropertyAnimator不同于 UIView Animation 的特性而讓效果打折,事實上低散,這是無奈之舉:不知是否是 Bug俯邓,當(dāng) Spring Timing 的初始速度不為(0, 0)時,這種方法無法實現(xiàn) Additive 效果熔号,而是中止動畫直接跳躍到最終位置稽鞭,其他類型的 Timing 則沒有這個問題,然而這個場景里的位移動畫必須是帶初始速度的 Spring 動畫引镊;不過即使此處不要求初始速度>0朦蕴,通過添加反向動畫實現(xiàn) Additive 效果的做法也會有瑕疵,同樣不知是否 Bug:最初添加的動畫的運行時間截止時弟头,如果依然添加動畫吩抓,動畫會直接跳躍到最終位置。
其實UIViewPropertyAnimator使用初始速度不為(0, 0)的 Spring Timing 也可以實現(xiàn) Additive 效果赴恨,關(guān)鍵在于isInterruptible屬性疹娶,默認(rèn)為 true。禁用這個屬性后嘱支,UIViewPropertyAnimator完全與 UIView Animation 無異蚓胸,上段里提到的問題都不存在;然而除师,禁用這個屬性后沛膳,UIViewAnimating協(xié)議里定義的與交互動畫有關(guān)的方法和屬性都不能使用:包括上面使用的暫停和逆轉(zhuǎn)動畫的功能,以及接下來會用到的停止動畫的功能汛聚,禁用后使用這些方法和屬性會觸發(fā)異常锹安。將UIViewPropertyAnimator當(dāng)作 UIView Animation 使用的話,去看上篇就好了倚舀,我在文末給出的 Demo 里示范了這種用法叹哭。
綜合來講,UIViewPropertyAnimator逆轉(zhuǎn)轉(zhuǎn)動畫的效果比不上 UIView Animation 痕貌,現(xiàn)在暫且?guī)еЧ蛘鄣倪z憾繼續(xù)使用UIViewPropertyAnimator來實現(xiàn)滑動交互风罩。
滑動交互:控制進度、平滑轉(zhuǎn)變
當(dāng)手指接觸到視圖時舵稠,如何中止當(dāng)前的動畫超升?UIViewPropertyAnimator給了我們兩個選擇:暫腿牖拢或停止動畫。在使用 UIView Animation 時室琢,我們直接取消了視圖的動畫乾闰,也就是停止動畫,這里選擇用該類的方式來停止動畫:
停止動畫還有另外一種使用方法:
不管手指接觸控制板視圖時是否在運動中盈滴,手指離開屏幕后都需要添加新的彈簧動畫涯肩。然而上面的方案在特定條件下有漏洞:假設(shè)此時控制板處于打開狀態(tài)(底部位置),用戶向上滑動來關(guān)閉控制板巢钓,滑動結(jié)束后控制板在動畫中移往頂部位置病苗,如果用戶想取消這個操作,于是點擊了控制板視圖竿报,那么控制板視圖最終并不會回到底部位置铅乡,而是在中間某個位置(滑動結(jié)束時的位置)。造成這個結(jié)果的根源在于點擊交互的實現(xiàn)手法:如果是通過添加反向的動畫來實現(xiàn)逆轉(zhuǎn)烈菌,那么就不會出現(xiàn)這個問題阵幸;而無論是出于展示新 API 特點的目的還是為了能夠在這里使用stopAnimation:方法,我選擇了使用isReversed屬性來逆轉(zhuǎn)動畫芽世≈可蓿滑動結(jié)束后動畫的起始位置是手指離開屏幕的位置,使用isReversed逆轉(zhuǎn)動畫最終只能回到這個位置济瓢,而這個位置肯定和控制板在打開/關(guān)閉狀態(tài)所處的位置有段差距荠割。
選擇使用isReversed來逆轉(zhuǎn)動畫時,在所有連續(xù)類型的手勢參與的交互動畫里旺矾,使用stopAnimation:都會有這樣的漏洞蔑鹦。完美的解決方案是在手指接觸視圖時將其暫停,不過不注意的話也會出現(xiàn)這樣的漏洞:
使用pauseAnimation()能夠解決這個漏洞的原因在于:在手勢的起始階段為控制板視圖提供從底部位置到頂部位置的完整動畫箕宙,逆轉(zhuǎn)后始終能夠回到正確的位置嚎朽;而使用stopAnimation:時不能提供完整路徑的動畫。
如果不在手勢的起始階段就添加動畫柬帕,而是在手勢的結(jié)束階段才添加動畫哟忍,pauseAnimation()也會出現(xiàn)上述漏洞;另一方面陷寝,使用stopAnimation:無法在手勢的變化階段控制動畫的進度锅很,只能修改視圖本身。從這兩點考慮凤跑,實現(xiàn)轉(zhuǎn)場動畫以及在非交互與交互狀態(tài)之間自由切換應(yīng)該選擇pauseAnimation()這條路線爆安。
continueAnimation(withTimingParameters:durationFactor:)是UIViewImplicitlyAnimating協(xié)議定義的方法,這是保證交互動畫流暢的關(guān)鍵仔引,如同使用 UIView Animation 實現(xiàn)交互動畫時 Spring Animation 的作用一樣扔仓。這個方法將動畫的起始位置重置為當(dāng)前位置致扯,然后繼續(xù)執(zhí)行,在這里可以動態(tài)修改剩余這段動畫運行時的 Timing 和 Duration当辐。withTimingParameters = nil時,以原來的 Timing 運行鲤看,這里以springTiming繼續(xù)剩下的動畫缘揪;動畫的剩余運行時間為durationFactor * duration,durationFactor = 0時义桂,運行時間依然為原來的duration找筝。因此,
animator.continueAnimation(withTimingParameters:nil, durationFactor: 0)
相當(dāng)于執(zhí)行animator.startAnimation()來繼續(xù)動畫慷吊。
continueAnimation(withTimingParameters:durationFactor:)結(jié)束后袖裕,animator 的 Timing 依然是初始化時的 Timing,修改只是暫時的溉瓶;不過durationFactor會修改 animator 原來的的duration(規(guī)則未知急鳄,每次調(diào)用這個方法都會修改,durationFactor = 0不會修改)堰酿,從而影響后面添加的動畫的運行時間疾宏,這是個奇怪的設(shè)計。
小結(jié)
上面的演示主要偏向于突出UIViewPropertyAnimator在交互方面的特性触创,它也完全可以當(dāng)作 UIView Animation 一樣使用坎藐,也可以混合這兩種風(fēng)格,我在ControlPanelAnimation中演示了多種風(fēng)格實現(xiàn)上面的交互動畫哼绑。不過即使假設(shè)實現(xiàn)逆轉(zhuǎn)動畫時的各種瑕疵是實現(xiàn)上的 Bug岩馍,在讓普通的動畫實現(xiàn)交互時,UIViewPropertyAnimator相對于 UIView Animation 并不具備優(yōu)勢:相比上篇中使用 UIView Animation 時的簡單抖韩,UIViewPropertyAnimator引入的交互狀態(tài)和解決不同轉(zhuǎn)換問題時看似靈活的搭配選擇蛀恩,都顯得太復(fù)雜了。
不過帽蝶,使用UIViewPropertyAnimator實現(xiàn)轉(zhuǎn)場動畫在非交互與交互狀態(tài)之間的自由切換是非常方便的赦肋,而且還能大幅精簡當(dāng)前復(fù)雜的轉(zhuǎn)場協(xié)議體系,這得益于其封裝的交互功能解決了最困難的部分励稳,具體可查看「iOS 視圖控制器轉(zhuǎn)場詳解」佃乘。
參考
WWDC 2016 Session 216: Advances in UIKit Animations and Transitions:https://developer.apple.com/videos/play/wwdc2016/216/
iOS 視圖控制器轉(zhuǎn)場詳解:https://github.com/seedante/iOS-Note/wiki/ViewController-Transition
感謝徐川對本文的審校。
給InfoQ中文站投稿或者參與內(nèi)容翻譯工作驹尼,請郵件至editors@cn.infoq.com趣避。也歡迎大家通過新浪微博(@InfoQ,@丁曉昀)新翎,微信(微信號:InfoQChina)關(guān)注我們程帕。