[譯] 我們是如何創建 iOS 版的 Guillotine 菜單動畫的

jopen 9年前發布 | 10K 次閱讀 IOS iOS開發 移動開發

 

我們是如何創建iOS版的Guillotine菜單動畫的?

原文: How We Created Guillotine Menu Animation for iOS

翻譯:@這個昵稱有點萌

你是否曾經有過這樣的疑問?為什么app中幾乎是清一色的 邊欄(sidebar),為什么不把它做成 top Bar或者 bottom Bar,甚至 corner Bar呢?

本文將要談到的就是當前導航條動畫的一個新趨勢。

動畫很有趣,但更重要的是能發揮很大的作用,它們可以改變你思考問題的方式,使得你的產品更好用并提升的app整體的用戶體驗。接下來我們將要展示的是設計師 Vitaly Rubtsov 的一個非常棒的點子:

“每個設計師都有那么一刻感到無聊。因為幾乎所有(對動畫)的完善修補、裁剪以及規格設定都給他們留下了很少發揮想象的余地。而每當這些時候,我就會驅使自己打開Adobe After Effects軟件然后創作一些比較有趣的東西。

當我開始想我要創建一個什么東西的時候,我突然有個想法,通常側邊欄都從屏幕左側劃出,同時將所有的內容都移動到右側位置。這種傳統側邊欄的實現方式太過無聊了。那如果我們將側邊欄變成上邊欄會怎么樣呢?它從頁面的上方掉落然后以一種特別的方式呈現,聽起來很棒不是嗎?“

Vitaly設計的topBar動畫由我們的iOS開發工程師 Maksym Lazebnyi 使用swift語言實現,并且開發者給了它一個很有趣的名字—— Guillotine Menu

我們是如何開發Guillotine Menu的?

by Makssym Lazebnyi

實際上,我們的iOS團隊見到過很多實現這種動畫效果的方法。我們選擇了其中一種方式實現,這種方式允許開發者在Storyboard中以任何方式自定義菜單。

為了實現我們的轉場動畫(transitioning animation),我們創建了一個UIStoryboardSegue的子類和一個自定義動畫管理器(custom animation controller)。基本上這就是你實現該動畫所需要做的全部工作,除非你想讓它更炫酷。當然我們也確實想這樣做,因此還創建了一些輔助的類。

整體上,你需要三個類以及一個UIView的擴展類來創建這個動畫,如下所示:

  • GuillotineMenuSegue. 該類是繼承自UIStoryboardSegue類。我們使用它來模態顯示菜單,并實現由GuillotineMenuTransitionAnimation類控制的呈現動畫。GuillotineMenuSegue允許你為菜單添加透明度,當然本文并沒有做這個工作。

  • GuillotineMenuTransitionAnimation. 該類是為了自定義呈現GuillotineMenuViewController類中視圖的動畫所用。

  • GuillotineMenuViewController. 該類是一個UIViewController的子類,存放菜單視圖所用。

除此之外,我們還為UIView添加了擴展方法以便能為子視圖添加約束來更好的適應父視圖。

接下來,我們逐一對每一個類進行闡述。

GuillotineMenuSegue

這個類中沒有什么特別的地方,我只提及一些關鍵點。

在該類重載的init方法中,我們檢查目標視圖控制器(destination view controller)是否遵守GuillotineAnimationProtocol協議(該協議我們后面會講到)。在重載的perform方法中我們將self設置成一個過渡動畫的代理。

在代理方法animationControllerForPresentedController中我們使用關聯對象將GuillotineMenuSegue的實例對象( self )關聯到具體的將要呈現的視圖控制器中,這樣當menu view controller呈現在屏幕上的時候,segue實例不會被銷毀。(譯者注:代碼如下)

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
  // 將segue示例self關聯到將要呈現的試圖控制器presented中,這樣確保presented生命周期內segue實例不會被釋放
    objc_setAssociatedObject(presented, &key, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    return [[GuillotineTransitionAnimation alloc] initWithMode:AnimationModePresentation];
}

GuillotineMenuTransitionAnimation

所有的動畫處理邏輯都在這個類中實現。

起初我們考慮使用animateWithDuration usingSpringWithDamping & initialSprintVelocity方法,但是當我們認真考慮這個動畫之后我們改變了主意。我們需要實現的菜單動畫中,當菜單從上方掉落之后,需要和父視圖的左側邊界發生碰撞,產生碰撞效果。而上面方法中的回彈效果會穿過左邊界(spring through the border of the superview),因此我們放棄了這個實現方式,轉而采用了UIDynamicAnimator。

為了實現我們的動畫,GuillotineMenuTransitionAnimation類必須遵從UIViewControllerAnimatedTransitioning協議,該協議中有兩個代理方法:

  • transitionDuration 動畫的過渡時間對我們影響不是太多,因此你可以返回任意的時間值。

  • animationDurtation 當菜單開啟和關閉均會調用該方法

我們如何計算動畫的位置信息?

我們需要了解動畫中每一刻的精確位置。GuillotineMenuTransitionAnimation類需要GuillotineMenuViewController提供menu button中心的坐標來做為旋轉動畫的錨點(anchorPoint)。另外還需要GuillotineMenuViewController提供一些其他屬性,因此我們創建一個協議GuillotineMenuViewController,視圖控制器通過遵守該協議返回我們所需要的幾個屬性。

代碼如下:

@objc protocol GuillotineAnimationProtocol: NSObjectProtocol {
     func navigationBarHeight() ->CGFloat
     func anchorPoint() ->CGPoint
     func hostTitle() ->NSString
}

譯者注:Objective-C版本代碼如下:
@protocol GuillotineAnimationProtocol <NSObject>
  - (CGFloat) navigationBarHeight;
  - (CGPoint) anchorPoint;
  - (NSString*) hostTitle;
@end

這三個方法的意義如下:

  • navigationBarHeightGuillotineMenuViewController開始顯示動畫的時候需要旋轉90度,同時覆蓋導航條(navigation bar)。我們需要將GuillotineMenuViewController中的視圖位置設置成CGPoint(0,navigationBarHeight);

  • anchorPoint提供我們動畫的旋轉軸心,這里是GuillotineMenuViewController中的menu button的中心位置;

  • hostTitle用來詢問GuillotineMenuViewController獲得主視圖控制器的標題。

我們如何實現菜單視圖的掉落以及旋轉?

為了實現掉落以及旋轉的動畫,我們使用UIDynamicAnimator并為其添加四種動力行為:

(譯者注:實際上實現UIKit動力學中的推動力、吸附、碰撞以及輔助行為,詳見 UIKit動力學

  • UIPushBehavior為view添加一個拖拽的力,當我們需要呈現顯示動畫的時候,施加到view的底部,當我們需要呈現關閉菜單的動畫時,施加到view的頂部;

  • UIAttachmentBehavior相當于一個釘子在menu button的中心點將整個view固定住。

  • UICollisionBehavior我們為view的父視圖( superview )添加了一個邊界,從視圖中心位置到左下角位置的長度。用以實現GuillotineMenuViewController在掉落路徑的末端模擬碰撞效果(譯者注:當菜單視圖關閉而回彈到上方時,同樣需要添加boundary,不過此時是在頂部,屏幕水平方向菜單視圖的中心位置到其右下角位置);

  • UIDynamicItemBehavior實現菜單碰撞左邊界之后的回彈效果。

基本上,我們的動畫是這樣,首先使用CGAffineTransformRotate將GuillotineMenuViewController的view旋轉正向90度,設置該view的邊框位置為CGPoint(0, navigationBarHeight)。然后,我們將該view添加以上每一種需要使用的UIDynamicBehavior(UIPushBehavior、UIAttachmentBehavior、UICollisionBehavior以及UIDynamicItemBehavior)。

UIDynamicAnimator會使得菜單的動畫持續,一直到所有附加在其上的物理作用力達到平衡。

我們通過代理協議UIDynamicAnimatorDelegate來告知視圖控制器動畫的完成情況。另外,我們還需要調用endAppearanceTransition()方法。

這里有一個比較棘手的地方就是設置 anchorPoint 。為了使得動畫正確呈現,錨點的位置到GuillotineMenuViewController中視圖的左邊界的距離必須和錨點到頂部導航條底部之間的距離相同。而且,當設備發生旋轉也需要修改錨點位置。但是GuillotineMenuTransitionAnimation類調用代理方法anchorPoint()是在viewDidLayoutSubviews()用之前。

因此我們將設備處于水平方向時的按鈕位置進行了硬編碼

譯者注: 設備布局一旦發生變化,例如設備進行了旋轉,便會調用viewDidLayoutSubviews()方法,本身我們可以在該方法中動態調整按鈕位置(也就是錨點位置),可是轉場動畫必須在該方法調用之前取到錨點位置,因此矛盾。作者就在代碼里硬編碼處理布局發生變化之后菜單按鈕的位置了。

UIViewExtension

針對UIView的簡單擴展,主要是針對子視圖添加約束以更好的適應父視圖。代碼本身就很好能夠說明功能了(self explanatory),這里就不敘述了。

譯者注: 針對Objective-C語言的類擴展和swift語言不同,在objc實現版本中,文件名字為UIView+ConstraintExtension.h和UIView+ConstraintExtension.m

Guillotine Menu View Controller

你可以繼承該視圖控制器或者進行任何的自定義甚至重寫。唯一必須要記得是的遵守GuillotineAnimationProtocol協議。

你如何才能定制該動畫?

你可以用任何可能的方式來定制菜單視圖!你只需要創建一個自定義的GuillotineMenuSegue,其中源視圖控制器就是你的主視圖控制器(host view controller),目標視圖控制器就是你需要呈現的菜單視圖控制器。

實話講,剛開始創建這個動畫的時候我自認為這是一件很簡單的事情,這個過程應該也沒什么挑戰。可是現在我們必須承認,對于iOS開發者而言這里面還有巨大的潛力可挖。另外,我們的動畫還可以作為一個簡單的動畫視圖來使用,或者作為一個帶有自定義導航條的UINavigationViewController的子類來使用。接下來我們計劃將不斷更新這項工作,力圖創造一個完整的帶自定義轉場動畫效果的UINavigationViewController的子類。

你可以在以下這幾個位置找到我們的工程源碼以及設計:

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!