iOS系列(LJ)Realank的iOS专题程序设计

iOS之实现自定义转场动画

2016-03-30  本文已影响3108人  不冷的南风

转场/控制器切换

含义:在 NavigationControllerpushpop 一个 View Controller,在TabBarController中切换到其他 View Controller,以 Modal方式显示另外一个View Controller,这些都是控制器切换(View Controller Transition),简称转场

iOS 7 之前,我们只能使用系统提供的转场效果,iOS 7之后苹果开放了相关 API 允许我们对转场效果进行全面定制,它是 以协议的方式开放了自定义转场的 API,协议的好处是不再拘泥于具体的某个类,只要是遵守该协议的对象都能参与转场,非常灵活,这样对自定义转场动画以及交互手段的支持带来了无限可能。

转场实现的本质

转场的本质是当前视图消失和下一视图出现,基于此进行动画,因为在转场的过程中,作为容器的父控制器维护着多个子控制器,但在视图结构上,只保留一个子控制器的视图。

目前主流的自定义转场

  1. UINavigationControllerPush 和 Pop
  1. UITabBarController中切换 TabBar
  2. 模态(Modal) 转场:Present 和 Dismiss,仅限于modalPresentationStyle属性为 UIModalPresentationFullScreenUIModalPresentationCustom这两种模式
  3. UICollectionViewController 布局转场(与 UINavigationController结合的转场方式)

转场动画的实现

转场协议由5种协议组成,在实际开发中只需我们提供其中的两个或三个便能实现绝大部分的转场动画。

/**
 *  除了<UIViewControllerTransitioningDelegate>是 iOS7 新增的协议,其他两种在 iOS2 里就存在了
 */
<UINavigationControllerDelegate> //UINavigationController 的 delegate 属性遵守该协议
<UITabBarControllerDelegate> //UITabBarController 的 delegate 属性遵守该协议
<UIViewControllerTransitioningDelegate> //UIViewController 的 transitioningDelegate 属性遵守该协议

下面就拿一个我之前写好的小栗子来看看自定义转场是如何实现的吧,这里我以特殊的Modal转场为例讲一下,TabBarController或是NavigationController自定义转场实现相对简单些,但本质其实都是相同的,先看效果图吧:

Modal转场

Modal转场的实现

视图结构.png

首先控制器遵守转场代理

//实现转场的触发操作
- (IBAction)clickBtn {
    PresentedViewController *presentedVC = [PresentedViewController new];
    
    //系统转场
    //presentedVC.modalTransitionStyle = UIModalTransitionStyleCoverVertical; //默认样式
    
    //自定义转场 模态转场 需要代理实现
    presentedVC.modalPresentationStyle = UIModalPresentationCustom;
    
    //遵守转场代理  代理需要强引用(即:self.delegate)
    presentedVC.transitioningDelegate = self.delegate;
    
    [self presentViewController:presentedVC animated:YES completion:nil];
}

在TransitionDelegate遵守对应的协议

@interface TransitionDelegate : NSObject<UIViewControllerTransitioningDelegate>

实现其代理协议


/**
 *  返回当view显示时执行动画的对象,该对象需实现转场动画
 */
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
    //return nil;
    
    return [ModalAnimation new];
}

/**
 *  返回当view消失时执行动画的对象,该对象需实现转场动画
 */
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{
    //return nil;
    
    return [ModalAnimation new];
}

创建动画控制器添加转场视图以及执行相应的动画,对于动画控制器来说,转场方式并不重要,可以对fromView 和 toView 进行任何动画,需要遵守转场动画协议<UIViewControllerAnimatedTransitioning>,并实现对应的协议方法,如下:

//返回转场动画时间
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext{
    return 0.5;
}

/**
 *  最重要的方法 必须实现 否则自定义转场无效
 *  该方法接受一个遵守<UIViewControllerContextTransitioning>协议的转场环境对象
 *  该转场环境对象提供了转场所需要的重要数据:参与转场的视图控制器和转场过程的状态信息
 *  在转场开始前<UIKit>生成遵守转场环境协议<UIViewControllerContextTransitioning>的对象transitionContext
 *  转场环境对象transitionContext提供了以下信息
 *  1. containerView 容器视图
 *  2. 获取参与转场的视图控制器,有 UITransitionContextFromViewControllerKey 和 UITransitionContextToViewControllerKey 两个 Key
 *  2. iOS8之后 新增的 API 用于方便获取参与转场的视图,有 UITransitionContextFromViewKey 和 UITransitionContextToViewKey 两个 Key
 *  在 iOS8 中可通过方法"viewForKey"来获取参与转场的三个重要视图,在 iOS7 中则需要通过对应的视图控制器来获取
 */
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
    
    //获取转场相关的两个控制器 iOS7的API
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    //iOS8之后使用
    //UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
    //UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    
    //获取参与转场的视图
    UIView *fromView = fromVC.view;
    UIView *toView = toVC.view;
    
    //获取容器视图
    UIView *containerView = [transitionContext containerView];
    
    if (toVC.isBeingPresented) {
        //添加目标View
        [containerView addSubview:toView];
        
        //实现动画
        toView.transform = CGAffineTransformMakeRotation(-M_PI_2); //设置初始值
        
        //获取动画的时间
        //NSTimeInterval duration = [self transitionDuration:transitionContext];
        
        //回到默认位置
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            toView.transform = CGAffineTransformIdentity;
        } completion:^(BOOL finished) {
            
            /**
             *  正确地结束转场过程。转场的结果有两种:完成或取消
             *  转场动画结束后 必须调用 否则呈现的新视图无法监听任何事件
             *  非交互转场的结果只有完成一种情况,不过交互式转场需要考虑取消的情况
             *  如何结束取决于转场的进度,通过transitionWasCancelled()方法来获取转场的状态,使用completeTransition:来完成或取消转场
             */
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        }];
    }else{
        //视图消失 Dismiss 转场中不要将 toView 添加到 containerView
        if (fromVC.isBeingDismissed) {
            [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
                fromView.transform = fromView.transform.b > 0.20 ? CGAffineTransformMakeRotation(M_PI_2):CGAffineTransformMakeRotation(-M_PI_2);
            } completion:^(BOOL finished) {
                [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
            }];
        }
    }
}

下面需要处理Modal转场后的控制器,因为Demo实现的一个视图基于某个点旋转,所以这里使用了layer的属性锚点anchorPoint,对于橘色视图的旋转监听,这里采用了拖动手势UIPanGestureRecognizer,代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    /**
     *  自定义转场 这里需要设置控制器View的大小
     *  anchorPoint 锚点 默认是其中心点(0.5,0.5) 设置必须在frame之前
     */
    self.view.backgroundColor = [UIColor orangeColor];
    self.view.layer.anchorPoint = CGPointMake(0.5, 2.0);
    self.view.frame = [UIScreen mainScreen].bounds;
    
    //创建拖动手势
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panView:)];
    
    [self.view addGestureRecognizer:pan];
}

处理拖动手势事件

- (void)panView:(UIPanGestureRecognizer *)pan{
    switch (pan.state) {
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled:{
            
            NSLog(@"%f...",self.view.transform.b);
            
            /**
             *  根据矩阵中b的值大小操作橘色View  大于0.20实现让其掉落下来的效果 弧度为正代表顺时针
             *  CGAffineTransform的各种操作 旋转 平移 缩放 本质都是矩阵形式实现
             */
            if (ABS(self.view.transform.b) > 0.20) {
                [UIView animateWithDuration:1.0 animations:^{
                    self.view.transform =  self.view.transform.b >0 ? CGAffineTransformMakeRotation(M_PI_2): CGAffineTransformMakeRotation(-M_PI_2);
                }];
                
                [self dismissViewControllerAnimated:YES completion:nil]; //保证视图完全移除
            }else{
                self.view.transform = CGAffineTransformIdentity; //默认位置
            }
        }
            break;
        default:{
            //获取偏移量
            CGFloat offSetX = [pan translationInView:self.view].x;
            
            //计算偏移百分比
            CGFloat percentage = offSetX /self.view.bounds.size.width;
            
            //计算旋转的度数 这里设置旋转的度数范围 M_PI_2
            CGFloat radios = percentage * M_PI_2;
            
            //实现旋转
            self.view.transform = CGAffineTransformMakeRotation(radios);
        }
            break;
    }
}

到这里的话,Modal转场的核心基本知识就说完了,嗯…上述步骤不错的话,就能实现一个简单的自定义转场动画,其实对于自定义的转场动画,自定义容器的控制器转场应该是复杂度最高的,这里呢,先说下转场动画的简单使用吧,后面文章会更新下这个复杂知识点的,有什么理解错误的地方欢迎指正,谢谢咯。。。

附上Demo链接
参考的博文链接:http://blog.devtang.com/2016/03/13/iOS-transition-guide/

上一篇下一篇

猜你喜欢

热点阅读