如何製作 Uber 啟動開屏介面

颶風飛揚發表於2017-05-26

Derek Selander 將 Uber 動畫一步步拆解,利用遮罩,向量計算,組合等多種方式, 重新將動畫建立起來。

2016.09.26更新:此教程已使用Xcode8 和Swift3進行更新.

Uber-feature

Oh,啟動動畫­­­—-當應用忙碌呼叫API埠獲取功能所需的重要資料時,開發者們可以抓住這個時機瘋狂展示有趣的動畫。啟動動畫(不要與靜態的,非動畫應用開始畫面混淆)在應用中承擔重要的角色:在應用等待啟動時刻保持對使用者的吸引力。

雖然有很多啟動動畫的例子,但你很難找到一個像Uber如此漂亮的啟動動畫。在2016年第一季度,Uber釋出了由其CEO領導的品牌再造戰略。該戰略成果之一就是一個炫酷的啟動畫面。

這個教程目的在於使用十分近似的方法複製Uber的啟動畫面。該方法大量使用CALayer和CAAnimation以及它們的子類集。相比介紹在這些類中發現的設計思想方法,此教程更集中於這些類在高質量動畫上的使用。你可以檢視Marin Todorov’s Intermediate iOS Animation video series去學習這些動畫背後的設計思想。

開始

因為在這個教程中有很多有意義的動畫需要實現完成,你將會以一個專案開始,這個專案已經包含了接下來製作優美動畫所需的CALayer包。在這下載開始專案

“開始專案”為一個名叫Fuber的應用。Fuber是一個請求式交通分享服務軟體,允許乘客請求Segway司機將他們載往城市不同的地方。Fuber增長十分迅速,已經在60多個國家為乘客們服務,但同時它也面臨著多個國家政府以及Segway聯盟的反對。

Splash screen

在此教程結尾時,你將會製作出一個像下面的啟動動畫:

Fuber Animation

 

開啟並執行Fuber啟動專案並瀏覽

以UIViewControler的角度來看,應用通過其父檢視控制類RootContainerViewController來啟動SplashViewController,該父檢視的控制可以改變子檢視的控制(UIViewController)。它迴圈播放啟動動畫直到應用啟動。當應用和API端點資訊交換或者應用需要必要的資料繼續傳送時,啟動畫面就會迴圈播放。  值得一提的是,在這個示例工程中啟動畫面在它本身的元件內. 在RootContainerViewController內showSplashViewController()和showSplashViewontrollerNoPing() 兩個函式可以用來執行畫面控制。教程中,大多數情況下,showSplashViewControllerNoPing()函式只會在動畫迴圈時呼叫,因此只需要專注SplashViewController內的子檢視動畫,接著先使用showSplashViewController()去模擬API延時,然後轉到動畫主控制器上。

啟動畫面檢視圖層的合成

SplashViewController檢視包含兩個子檢視。第一個子檢視由“波紋網格”為背景,作為TileGridView(貼片網格檢視),其包括一個網格狀佈局的TileView(貼片狀檢視)子檢視實體。另一個子檢視由‘U’圖示動畫組成,作為AnimatedULogoView(該應用logo中U動畫檢視)。

如何製作 Uber 啟動開屏介面

AnimatedULogoView包含四個CAShapeLayers:

  • CircleLayer代表‘U’的白色圓形背景
  • lineLayer是從circlelayer佈局的中心出發到其邊界的直線
  • squareLayer是在circleLayer層中心的方形
  • maskLayer被用於檢視的遮罩。當邊界跟隨動畫變化時,被用於在一個簡單動畫中摺疊其他的檢視層。

 

通過組合,這些CAShaperLayer合成Fuber應用中的‘U’。

RiderIconView

現在已經知道這些層是如何組成的,那接下來應該去生成動畫使AnimateULogoView動起來。

為圓新增動畫

當製作動畫時,最好先評估視覺噪聲並關注動畫當前執行情況。 轉到AimatedULogoView.swift檔案,在init(frame:)內,把除了circleLayer層的其他所有被新增到檢視的子檢視層註釋掉。當完成該動畫時,可以將它們一個一個從新新增回去。程式碼應該如下:

 

找到generateCircleLayer()函式去理解圓形是如何製作出來的。它是使用UIBexierPath 繪畫出的一個簡單的CAShapeLayer。仔細看這一行程式碼:

在預設情況下,將0作為起始角度(startAngle),貝塞爾曲線路徑從順時針3點鐘方向開始。 設定-M_PI_2的值為-90度,從圓的頂部開始到270度角處結束或者在3*M_PI_2角度處重新回到圓的頂部。因為需要實現整個圓形形成動畫,所以需要特別關注那些(引數的設定),同時將圓的半徑大小作為線的寬度(lineWidth)。
轉到annimateCircleLayer()函式佔位符處並新增如下程式碼:circleLayer動畫需要包含三個CAAnimaiton:一個CAKeyframeAnimation作為動畫行程的終點,一個CABasicAnimation作為動畫轉換,還有一個CAAnimationGroup用於將這兩者組合在一起。你將會同時建立這些動畫。

通過設定CAKeyframeAnimation動畫起始和結束值為 0.0和1.0,就告訴動畫核心框架從起始角開始畫圓到結束角處結束,就像時鐘轉動動畫。隨著strokeEnd的值增加,線的長度會沿著圓周增加,圓逐漸被充滿 。如果將引數值設定為[0.0,0.5],那麼只會畫出半個圓,因為strokeEnd將只會到達圓的半周。

現在增加轉換動畫:

這個動畫既包含尺度變換同時包括繞‘Z’軸的旋轉變換。這導致當旋轉45度角時circleLayer會發生改變。這些旋轉很重要,因為當與其他圖層一起形成動畫時,需要與lineLayer的位置和轉動速度相匹配。最後,在animateCircleLayer()函式底部增加一個CAAnimationGroup。這個動畫將前兩個動畫組合在一起,這樣就可以在circleLayer層中只新增一個動畫。

CAAnimationGroup有兩個顯著的特性被更改了:beginTime和timeOffset。如果你對它們中的任意一個都不是很熟悉,你可以在這找到這些特性的描述及用法說明.

groupAnimation的beginTime參考它的父檢視進行設定。

時間補償(timeOffset)是需要的,因為動畫第一次執行時實際上會從動畫的一半處開始。如果有更多完整的動畫,可以試著改變startTimeOffset的值並觀察視覺上的不同。

新增groupAnimation到circleLayer,然後構建並執行應用去看看動畫是什麼樣子。

Splash Screen CircleIn Animation

提示:試一試在groupAnimation動畫組中移除strokeEndAnimation或者transformAnimation 去弄明白每個動畫的作用。 把在這個教程中你所製作的動畫都像這樣試一試,你將會驚奇於動畫組合產生的讓人意想不到的特殊視覺效果。

讓線動起來

已經完成了circleLayer動畫,接下來處理lineLayer的動畫。同樣還在AnimatedULogoView.swif檔案中,轉到startAnimating()函式並將除animateLineLayer()外的其它函式都註釋掉。結果如以下程式碼:

另外,更改init(frame:)中的內容,使circleLayer和lineLayer是唯一正在被使用的CALayers層:

正確註釋掉CALayers 動畫,轉到animateLineLayer()函式並執行下一組動畫:

這個動畫實現lineLayer寬度先增加後變小的變換

對下個動畫,新增下列程式碼:

用CAAnimationGroup將動畫組合並新增到lineLayer:同circleLayer變換動畫類似,定義一個繞Z軸順時針的旋轉。對於線條,同時執行一個25%的縮放變換,然後恢復,最後收縮到15%。


構建並執行程式,你將看到下面漂亮的畫面

Splash Screen Knockoutline Animation

注意使用相同的命名方法,用-M_PI_4初始化排列線條和圓周行程的轉換引數。同樣用 [0.0, 1.0-kAnimationDurationDelay/kAnimationDuration, 1.0]來設定keyTimes。陣列中第一個和最後一個元素的意義很明顯:0代表開始,1.0代表結束,因此也可以得到開始到圓的動畫結束,第二部分(線條收縮)動畫出現之間任意想要計算的值。

因為需要在動畫播放結束時再回到播放延遲的時間點(為迴圈播放),因此用1減去kAnimationDurationDelay和 kAnimationDuration的比值(獲取延遲的時間點)

現在已經完成circleLayer和lineLayer動畫,接下來轉到中心的方形動畫。

讓方形動起來

現在應該很熟練了,和之前一樣。轉到startAnimating()函式並註釋掉除animateSquareLayer()外的函式。像下面這樣更改init(frame:)函式:

上述工作完成後,轉到animateSquareLayer()函式並實現下面動畫:

這個動畫改變CALayer的大小,是一個實現將方形邊長縮小到三分之二,然後恢復,最後變成0的關鍵幀動畫.

繼續為背景色新增動畫效果:

注意fillMode屬性。即使beginTime不是0,背景色也會在動畫開始前保持動畫開始時的CGColor,在動畫結束後保持動畫結束時的CGColor。這會使得當將這些動畫被新增到父CAAnimationGroup時不產生閃爍。

說到這裡,接下來就來實現它吧:

構建並執行。注意方形的動畫效果。

Splash Screen Tutorial

現在可以結合前面的動畫看看整個效果。

提示:模擬器中的動畫可能會出現參差不齊不完整,這因為模擬只是在GPU上模仿IOS裝置的虛擬環境中進行的。如果你的電腦帶不動動畫,可以將模擬器顯示視窗調小或直接執行在ios裝置上。

遮罩

首先,取消所有在init(frame:)中增加的和動畫startAnimating()函式中的註釋。

在所有動畫組合下,構建並執行Fuber

PreMask Animation

動畫還有一點不足,當圓消失時,圓的尺寸會產生突變的感覺。幸運的是,遮罩動畫可以修復這個缺陷,使各子層動畫收縮平滑。

轉到animateMaskLineLayer()函式並新增下列程式碼:

這是平滑邊界變化的動畫。記住當邊界改變時,整個AnimatedULogoView將會消失直到該層的遮罩作用於所有的子動畫層。

現在執行一個圓角動畫 使遮罩保值圓形:


把這兩個動畫新增到一個CAAnimationGroup去完成這個動畫層:

構建並執行

RiderIconView Animation

看起來不錯哦!

網格

數字邊界。試著想象成串的UIViews穿過TileGridView的情景。那會是什麼樣子呢?

好…可以參考Tron這部電影去看看是什麼樣子!

背景網格由一系列新增到父類TileGridView的TileView構成。為了能更快了理解這一點,開啟TileView.swift檔案並找到init(frame:)函式。在最底部新增如下屬性:

 

構建並執行該應用

Fuber-Grid-View

正如所見,TileView 被排在一個網格里。

所有這些邏輯被建立在TileGridView.swift中renderTileView()函式內。 所幸的是,方格佈局的這些邏輯在開始專案中實現了。因此你所要做的就是讓它動起來。

給TileView新增動畫

TileGridView有一個唯一的子檢視containerView。它會新增所有的子 TileView。另外,它還有一個 tileViewRows的屬性,該屬性是一個包含所有新增到containerView的 TileView二維矩陣。返回到TileView的init(frame:)函式。刪除為了顯示邊界而增加的邊框,並將chimeSplashImage前的註釋去掉,將它新增到圖層。函式如下:

構建並執行

Grid Starting

然而, TileGridView (和所有的TileView)也需要新增一些動畫。開啟TileView.swift,轉到startAnimatingWithDuration(_:beginTime:rippleDelay:rippleOffset:)並完成下一塊的動畫:

這段程式碼設定了一系列的定時函式,這些函式不久將會被用到。新增如下程式碼:

shouldEnableRipple 是一個布林型別變數,它決定變換和方位動畫什麼時候會被新增到動畫序列中。對所有不在網格邊界的TileView,shouldEnableRipple的值被設為True,。這一邏輯已經隨TileGridView在renderTileView()中被建立時實現了。

新增透明動畫:

這個動畫簡單明瞭,但包含了一些特殊的keyTimes。

現在把所有的動畫新增到到一個組:

這將會把groupAnimation新增到TileView的實體中。注意動畫組中可以是一個或者三個動畫在一個組,這取決於shouldEnableRipple的值。

現在已經完成了每一個讓TileView動起來的函式,接下來在TileGridView中呼叫它們。轉到TileGridView.swift並在startAnimatingWithBeginTime()函式中增加如下程式碼:

構建並執行

Grid-1

Hmm……這確實看起來好多了,但是AnimatedULogoView的徑向膨脹應該對所有方格中的TileView產生震盪波的動畫效果。那也就意味著,需要根據外圍檢視與大綱(基準)檢視中心之間的距離設定一個延時補償乘於適當常數的參量。

在startAnimatingWithBeginTime(:)函式底部,增加如下程式碼:


上述程式碼僅僅是獲取了從centerTileView中心到檢視中心的相對距離。

轉到startAnimatingWithBeginTime(_:)並用下列程式碼進行替換:

 

使用distanceFromCenterViewWithView(_:)函式確定動畫所需要的延時。

構建並執行

Grid-2

 

好多了!動畫開始看起來得體了,但還是有些缺陷。拼貼塊(TileView)應該隨著震盪波的方向和起伏符合物理運動。

最好的方法是拿出高中時學的數學(不要畏縮——它將會超越你對它的認識)並根據拼貼塊到中心的距離確定拼貼塊的移動向量。

 

返回startAnimatingWithBeginTime(_:),對程式碼進行如下修改:


這段程式碼計算出了拼貼塊(TileView)應該移動的向量並將它作為到了波紋運動補償值(設為rippleOffset的值)。

構建並執行

Grid-3

 

太酷了!現在來個錦上添花:實現放大效果,但這個動畫需要正好在遮罩邊界改變之前出現。

在startAnimatingWithBeginTime(_:)函式頂部增加如下程式碼:


再次構建和執行

FuberFinal

 

漂亮!現在你已經制作了產品級質量的動畫,很多Fuber使用者將會在Twitter上為此點讚的。

提示:試一試改變 kRippleMagnitudeMultiplier和 kRippleDelayMultiplier的值,會有很有趣的結果的。

最後,轉到RootContainerViewController.swift.。在viewDidLoad()函式中最後一行,把showSplashViewControllerNoPing()改成 showSplashViewController()。

最後一次構建和執行程式,欣賞一下自己的工作成果吧。

Fuber Animation

給自己一點讚美……真是一個酷斃了的啟動動畫!

從這出發可以去幹什麼?

你可以在這下載Fuber最終完整的工程檔案。

如果你想學習更多與動畫相關的知識,可以瀏覽iOS系列動畫教程

相關文章