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状态,这时就通过插值计算出需要的状态。跟交互动画一样,都是手势->进度->动画状态的一个思路。

下一篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容