iOS添加自定義轉場動畫和交互動畫(一)

準備寫兩篇,第一篇介紹下轉場動畫,第二篇介紹下我封裝的一個轉場動畫的庫,可以很簡便的給VC之間的轉變加上自定義動畫。

iOS場景對應的類是ViewController,基本上一個場景對應一個VC,從一個場景切換到另一個場景,基本是ViewController之間的切換,這切換過程的動畫成為transition animation,過渡動畫、轉場動畫。

有兩種類型嘛,一種是navigationController自帶的push\pop,就是由導航控制器來控制VC的切換,還一種是present\dismiss,在兩個VC之間直接切換。對這兩種類型,系統都有默認的交互動畫,但有時你可能想自定義交互的方式,比如像iOS自帶相冊里點擊圖片看大圖時,新的界面是從中間放大來展現出來的。

1、轉場動畫

添加自定義動畫非常簡單,就是給系統的切換過程提供動畫,其他的不變。
1.1 對于navigation的切換
UINavigationController的delgate具有一個方法,可以用來個給它提供動畫:

    //頁面推進的動畫
    var presentationTransition : UIViewControllerAnimatedTransitioning?
    //頁面返回的動畫
    var dismissionTransition : UIViewControllerAnimatedTransitioning?

//UINavigationController的delegate方法
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        if operation == .Push {
            
            return presentationTransition
            
        }else if operation == .Pop{
            return dismissionTransition
        }
        
        return nil
    }

看這個委托方法,很容易明白它的設計意圖,這個委托是UINavigationController的,它負責控制VC切換,切換的時候,它向自己的delegate調用這個方法,看有沒有自定義的動畫,有,那么接下換切換VC就用自定義的動畫;沒有就用系統準備的動畫。
對UINavigationController可能有這么一段:

...開始push\pop了
var animatedTransitioning : UIViewControllerAnimatedTransitioning?
if self.delegate.respondsToSelector("navigationController:animationControllerForOperation:fromViewController:toViewController"){
   animatedTransitioning =self.navigationController?.delegate?.navigationController(self, animationControllerForOperation: .Push, fromViewController: fromVC, toViewController: toVC)
}

if animatedTransitioning == nil{
  animatedTransitioning = 系統預備的動畫
}
...之后使用動畫animatedTransitioning

在這不得不說,這個接口開得恰到好處,把動畫相關的都開放出來,我們自由定義,而其他的都隱藏著,不需要我們操心。

1.2 present/dismiss類型的切換
這個邏輯上就一樣了,A -present-> B 或者 B -dismiss->A,那么要指定B的transitioningDelegate,實現里面的兩個委托提供動畫:

func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        return presentationTransition
    }
    
    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return dismissionTransition
    }

跟上面navigation方式的切換時一樣,也是給控制轉場的角色提供自定的動畫,就這么簡單

1.3 自定義動畫內容
上面提供的動畫,都是UIViewControllerAnimatedTransitioning這個類型的(其實這是個protocol哈),具體內容需要建個新類,重寫兩個方法,代碼:

import UIKit

class TFTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    
    var duration : NSTimeInterval
    var completeHander : ((Bool) -> Void)?
    
    init(duration : NSTimeInterval = 0.25){
        self.duration = duration
        super.init()
    }
    //方法1
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return duration
    }
    
    // 方法2
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        let toView = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!.view
        let fromView = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!.view
//這句一定要,這時toView是不會自己加上來的
        transitionContext.containerView()?.addSubview(toView)

        UIView.animateWithDuration(duration, delay: 0, options: .CurveLinear, animations: { () -> Void in
            
            寫入fromView的變化...
            寫入toView的變化...
            
            }, completion: { (finished) -> Void in
//結束動畫,否則會干擾下次動畫
      transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
        })
    }
}

方法1用來提供整個動畫的時間,同時也是轉場的時間;方法2給了一個transitionContext,從它可以得到fromView\toView\fromVC\toVC等相關信息。

接下來就是UIView層次的動畫了,就變得簡單多了,就是調調fromView的alpha啊、toView的frame,transform等等的屬性來形成你想要的想過就ok了。

最后,結束了提示轉場完成。!transitionContext.transitionWasCancelled()這樣寫的目的是,如果動畫取消了,就不算完成,這樣再次執行時,還是開始狀態。

#######2. 交互動畫

iOS的側滑返回,是你手滑動一點,界面也切換一點,整個過程是根據你的手勢來控制的,不像push\pop,只要開始,就自動執行。而交互動畫,就是可以讓我們也可以自定義一個方式,比如手向下滑動,來全程控制轉場動畫的執行。可以實現一些比較吊的效果。

上圖:)

交互示意:先是左劃present到新的VC,然后用雙指縮放dismiss回來

交互動畫,一個是交互、一個是動畫,思路是這樣的:動畫有一個過程,比如A->B,那么我給定一個進度,比如0.5的時候,動畫就執行到一半的位置,給0.3的時候就執行到30%的進度位置。這樣,你在手拖動的時候,不斷的更新進度,畫面也就根據你的手變化更新畫面。

上代碼,以navigatonController的轉場為例:

//交互式動畫的進度管理器
    var interactiveTransition : UIPercentDrivenInteractiveTransition!

func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        
        return interactiveTransition
    }

也是實現一個UINavigationController的委托,提供一個遵循UIViewControllerInteractiveTransitioning協議對象,這里是UIPercentDrivenInteractiveTransition對象,它就是根據進度來確定動畫的過程的。

然后手勢開啟時,開啟動畫,以左劃為例:

//先添加手勢
let tapGesture = UITapGestureRecognizer(target: self, action: "interactWith:")
        self.view.addGestureRecognizer(tapGesture)

//手勢觸發,開始交互動畫
    func interactWith(gestureRecognizer : UIGestureRecognizer){
        
        switch gestureRecognizer.state {
        case .Began:
            //構建交互式動畫的管理器
            interactiveTransition =  UIPercentDrivenInteractiveTransition()
            //觸發轉場,代碼1
            self.navigationController?.pushViewController(toViewController, animated: true)
            
        case .Changed:
            //獲取進度,代碼2
            let progress = progressOfGestureRecognizer(gestureRecognizer)
            print(progress)
            //更新進度
            interactiveTransition.updateInteractiveTransition(progress)
            
        case .Ended,.Cancelled:
             //判斷動畫是回到開頭還是結尾,代碼3
            if isTowardEndingForGestureRecognizer(gestureRecognizer){
                interactiveTransition.finishInteractiveTransition()
                
            }else{
                interactiveTransition.cancelInteractiveTransition()
                
            }
            
            interactiveTransition = nil

        default:
            break
        }
    }

(1)可以看到,整個過程的觸發點在手勢上,是手勢開始后,才開始push的,看代碼1處,就是在手勢狀態為began時開啟push。為啥說這點,因為這決定了整個流程的方向不一樣了,之前是我們被動的提供動畫,這里是主動的控制流程,如果你不push,界面是不會有反應的、你不更新進度(代碼2),界面是不會變的。
(2)代碼2,progressOfGestureRecognizer是一個自定義方法,根據手勢獲取進度,左劃時我采用的是:

//使用滑動距離和屏幕寬度之比作為進度
let location = gestureRecognizer.locationInView(keyWindow)
let rate = (touchBeginning.x - location.x) / UIScreen.mainScreen().bounds.width
return max(0, rate)

這樣的好處是,如果之前的動畫timeFunction設為是線性的(.CurveLinear),那你收拖動多少,新界面就會進來多少,看起來比較自然。
(3)代碼3,是判斷結尾還是回到開頭。你拖動界面,進來一點,可能你又不想push了,松手,這時應該是剛push出來的界面又回去了是吧,其實就是動畫又滑到了開頭。如果push完成,動畫就是到結尾,所以需要一個判斷。

最后還有兩點:(1)這里使用手勢來控制進度,從代碼可以看出,其實可以不是手勢,只要你更新進度,動畫就會變。比如...可以聲控,你喊pu...就開始push,然后界面一點點左移,喊道...sh,動畫結束,當然這種交互有點怪哈。只是說這里的交互方式和進度調節是分開的,這樣也增加的交互多樣的可能性。
(2)其實交互動畫的委托需要的是一個遵循UIViewControllerInteractiveTransitioning協議的對象,不一定是UIPercentDrivenInteractiveTransition類型,也就是不一定是通過動畫的進度來實現交互動畫的。實現public func startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning)可以有更豐富的控制吧,不過我懶得去研究了??

源碼地址,封裝的類也在里面。

另:交互動畫,跟github上有個項目的思路挺像的,這個項目是JazzHands,效果挺吊的.它的思路是,使用多個關鍵幀(keyFrame),比如a b c d ...等多個狀態,然后你手滑動,根據滑動的位置,就算每個動畫(當前可能有多個動畫)的進度,比如算出動畫1的進度是0.33,但是動畫1的只有進度為0.2的b狀態和0.4的c狀態,這時就通過插值計算出需要的狀態。跟交互動畫一樣,都是手勢->進度->動畫狀態的一個思路。

下一篇

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容