zoukankan      html  css  js  c++  java
  • 容器转场动画

      前言

      目前而言自定义继承与UIViewController的容器子类,并没有现成的可使用的API来进行动画(将一个视图控制器转场到另外一个)。本文中将介绍如何自定义视图控制器的容器类的转场动画(如从一个控制器视图推出另一个控制器视图)。

           视图控制器的推出目前看来有三种方式:

    1. UINavigationController的push和pop
    2. 模态推送的present和dismiss
    3. UITabbarViewController的界面跳转    

      在这里当然还是有方法可以进行容器视图的转场,transitionFromViewController:toViewController:duration:options:animations:completion:,这方法看似好用,但若是对每个容器转场动画实现相同的逻辑,那就得实现对应的若干个相同方法。这样便产生了严重的代码冗余。若我们通过指定一个转场的动画,让所有的转场都执行此动画就提高了其代码的复用性,也降低了其耦合性。因此在这里主要讲解如何来自定义转场动画。demo地址:转场动画-demo,各位可以通过demo与本文描述结合理解,这样更能加深印象!

      相关API

      在开始代码之前,先看看我们所需要用到的相关类别和接口

    1. 转场代理(Transitioning Delegate)根据不同的转场类型提供其所需要的动画控制类和交互控制类
    2. 动画控制类(Animation Controller)遵从UIViewControllerAnimatedTransitioning协议,并且负责执行实际的动画。
    3. 交互控制类(Interaction Controller)遵从UIViewControllerInteractiveTransition协议来控制可交互的转场动画
    4. 转场上下文(Transitioning Context)定义了转场时需要的元数据(比如转场所参与了的视图控制器和视图的属性),其遵从UIViewControllerContextTransitioning协议,并且这是由系统负责生成和提供
    5. 转场协调器(Transition Coordinators)可以在运行转场动画时,并行的运行其他动画。转场协调器遵从UIViewControllerTransitionCoordinator协议

      转场动画交互方式分两种,第一种是属于非交互式:必须要实现动画控制类(相当于我们平时的直接点击一个按钮然后present出另一个视图控制器),第二种交互式:必须要实现动画控制类和交互控制类(例如可以通过手势的滑动距离来控制转场动画的一个进度,一般应用中都可以通过手势的滑动来推出一个视图控制器)。当然不论哪种转场动画,都必须要设定一个转场代理。

      代码部分

          Present转场动画

          非交互式转场动画

          1.新建工程,给FirstVc视图控制器的视图配置背景色和一个点击按钮(按钮用于present下一个控制器),在推出第二控制器视图的时候要注意配置其转场动画代理

          2.创建一个视图控制器(SecondVc)用于被推出,给其配置另外一种背景色和一个返回按钮(用于dismiss)

          3.配置转场代理,以下为FirstVc的按钮点击事件中配置代码示例

        SecondViewController *secondVc = [[SecondViewController alloc] init];
        //UIModalPresentationFullScreen:由系统管理推出完成后的视图
        //UIModalPresentationCustom:自己管理推出完成后的视图
        secondVc.modalPresentationStyle = UIModalPresentationCustom;
        //配置转场代理
        secondVc.transitioningDelegate = self;
        [self presentViewController:secondVc animated:YES completion:nil];
        //此处需要注意不要将transitioningDelegate写为modalTransitionStyle。modalTransitionStyle是系统提供转场动画效果,效果较少。

      4.配置转场代理协议方法

      prsent推送时调用的方法,返回动画控制类

      1)animationControllerForPresentedController: presentingController: sourceController:dismiss推送时调用方法,返回动画控制类

        2)animationControllerForDismissedController:

        交互式present推送调用方法,返回交互控制类

          3)interactionControllerForPresentation:

          交互式dismiss推送调用方法,返回交互控制类

          4)interactionControllerForDismissal:

          iOS8后引入的转场动画控制器,可以在转场动画的同时利用此控制器配置其他动画,只有在配置presentationStyle为Custom类型才有效果

          5)presentationControllerForPresentedViewController: presentingViewController:sourceViewController:

          在非交互式的情况下3),4)方法默认返回nil对象,不进行交互。若此时强行返回一个交互控制类会导致视图不能正常推出,造成程序假死情况!

          5.新建一个类(BQTransitionAnimation)继承自NSObject对象,遵从UIViewControllerAnimatedTransitioning协议,协议方法有:

          配置转场动画时间

          1)transitionDuration:

          配置转场动画具体逻辑方法

          2)animateTransition:

          方法中自带参数transitionContext,即前文中所提到的转场上下文,我们可以通过获取上下文中的元数据。如下所示:

        UIViewController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        UIViewController *fromVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

      从上下文中取出了fromVc和toVc,接着再给这些视图控制器的视图做相应的动画即是转场动画。其中需要注意的是fromVc和toVc的一个相对概念。fromVc代表的是从哪个视图控制器来,toVc代表的是要到哪个视图控制器去。如上述中prsent时fromVc即为ViewController,toVc为NextViewController。但若是dismiss时,fromVc就变为了NextViewController,toVc为ViewController。以下是一个完整的BQTransitionAnimation.m代码,其中成员变量_presenting 用于判断此时控制器是在进行present还是dismiss,其中的动画逻辑可以根据自己的实际需要来进行编写,笔者的动画逻辑可在demo中BQTransitionAnimation类中查看

        6.在Viewcontroller中配置转场代理协议方法,实现其中的prsent和dismiss方法,代码如下:

        - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
        return [[BQTransitionAnimation alloc] initWithAnimationType:AnimationType_Alpha_Change];
    }
        - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
        return [[BQTransitionAnimation alloc] initWithAnimationType:AnimationType_Rotate];
    }

      根据以上代码思路,一个简单的自定义非交互式转场动画便完成了。

      交互式转场动画

          交互式转场动画需要在非交互式的基础上再增加一个交互控制类,并以手势的方式来进行交互。

        1.在FirstVc视图中添加一个边缘滑动手势(UIScreenEdgePanGestureRecognizer),并指定滑动方向,并添加手势响应方法,具体代码实例如下:

    - (UIScreenEdgePanGestureRecognizer *)gesture {
        if (_gesture == nil) {
            _gesture = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(gestureSliderEvent:)];
            _gesture.edges = UIRectEdgeRight;
        }
        return _gesture;
    }
    - (void)gestureSliderEvent:(UIScreenEdgePanGestureRecognizer *)sender {
    if (sender.state == UIGestureRecognizerStateBegan) {
            //模态推送按钮
            [self showNextVcBtnClickedEvent:nil];
        }
    }
    - (void)showNextVcBtnClickedEvent:(UIButton *)sender {
        BQPresentInteractionSecondVc *nextVc = [[BQPresentInteractionSecondVc alloc] init];
        nextVc.modalPresentationStyle = UIModalPresentationCustom;
        //将转场代理设置为单独的一个对象,这样方便控制其是否需要交互
        nextVc.transitioningDelegate = self.transitionDelegate;
        nextVc.preVc = self;
        //通过判断是否通过按钮点击推出
        if (sender != nil) {
            //按钮点击推出,则转场代理手势设置为nil
            self.transitionDelegate.gesture = nil;
        }else {
            //非按钮点击推出(手势交互),则将手势传入转场代理,转场代理根据此手势来进行交互比例判断
            self.transitionDelegate.gesture = self.gesture;
        }
        [self presentViewController:nextVc animated:YES completion:nil];
    }

      2.接下来在FirstVc中配置转场代理,并新建一个转场代理(BQTransitioningDelegate)类,关键代码示例如下:

    - (BQTransitioningDelegate *)transitionDelegate {
        if (_transitionDelegate == nil) {
            _transitionDelegate = [[BQTransitioningDelegate alloc] init];
        }
        return _transitionDelegate;
    }

      BQTransitioningDelegate.m文件中关键代码

    - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator {
        if (self.gesture != nil) {
            return [[BQpercentDrivenInteractive alloc] initWithPanGesture:self.gesture];
        }
        return nil;
    }
    - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator{
        if (self.gesture != nil) {
            return [[BQpercentDrivenInteractive alloc] initWithPanGesture:self.gesture];
        }
        return nil;
    }

      3.创建一个类(BQpercentDrivenInteractive)继承自UIPercentDrivenInteractiveTransition。其内部的主要方法是

        1)startInteractiveTransition:(开启交互)

        2)updateInteractiveTransition:(更新交互动画)

        3)finishInteractiveTransition(完成交互动画)

        4)cancelInteractiveTransition(取消交互动画)

        在类中根据传入的手势添加一个手势响应方法,通过其响应可以计算出手势完成比例,再根据此比例数值更新其交互动画,具体事例代码如下:

    - (instancetype)initWithPanGesture:(UIScreenEdgePanGestureRecognizer *)gesture {
        self = [super init];
        if (self) {
            _gesture = gesture;
            //添加手势触发事件
            [_gesture addTarget:self action:@selector(updateViewContorllerTransition:)];
        }
        return self;
    }
    - (void)dealloc {
        [_gesture removeTarget:self action:@selector(updateViewContorllerTransition:)];
    }
    //根据手势状态来更新交互动画操作
    - (void)updateViewContorllerTransition:(UIScreenEdgePanGestureRecognizer *)sender {
        switch (sender.state) {
            case UIGestureRecognizerStateBegan:
                
                break;
            case UIGestureRecognizerStateChanged:
                [self updateInteractiveTransition:[self completPercentFromGesture]];
                break;
            case UIGestureRecognizerStateEnded:
            {
                if ([self completPercentFromGesture] >= 0.5) {
                    [self finishInteractiveTransition];
                }else {
                    [self cancelInteractiveTransition];
                }
            }
                break;
            default:
                [self cancelInteractiveTransition];
                break;
        }
    }
    //开始交互动画时调用
    - (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
        _transitionContext = transitionContext;
        [super startInteractiveTransition:transitionContext];
    }
    //根据手势计算当前手势的完成度
    - (CGFloat)completPercentFromGesture {
        UIView *sourceView = _transitionContext.containerView;
        CGPoint point = [_gesture locationInView:sourceView];
        CGFloat percent = 0;
        if (_gesture.edges == UIRectEdgeRight) {
            percent = (Screen_Width - point.x) / Screen_Width;
        }else {
            percent = point.x / Screen_Width;
        }
        NSLog(@"%lf",percent);
        return percent;
    }

      4.在SecondVc视图控制器中同样添加一个滑动手势,并同First中做出同样配置。当由手势交互引发视图控制器dismiss时,先将转场代理的手势配置为SecondVc中的滑动手势。这样就可以进行反向的交互转场动画了,关键示例代码如下:

    - (void)backBtnClickedEvent:(UIButton *)sender {
        if (sender != nil) {
            ((BQTransitioningDelegate *)self.preVc.transitionDelegate).gesture = nil;
        }else{
            __weak typeof(self) weakSelf = self;
            ((BQTransitioningDelegate *)self.preVc.transitionDelegate).gesture = weakSelf.gesture;
        }
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    - (void)gestureSliderChange:(UIScreenEdgePanGestureRecognizer *)sender {
        if (sender.state == UIGestureRecognizerStateBegan) {
            [self backBtnClickedEvent:nil];
        }
    }

      至此Present交互式的转场动画就告一段落。

      Navagation的转场动画

        Navagation配置同Present方法基本相同。首先只需要配置Navigation代理,接下来实现其代理方法即可。其中思路Present相同,代码示例如下:

    self.navigationController.delegate = self;
    #pragma mark - UINavigationControllerDelegate Method
    - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
        return [[BQTransitionAnimation alloc] initWithAnimationType:AnimationType_Alpha_Change];
    }
    - (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController {
        if (_isInteraction == YES) {
            return [[BQpercentDrivenInteractive alloc] initWithPanGesture:self.panGesture];
        }
        return nil;
    }

      TabbarController转场动画

        关于tabbarController的转场动画配置同上面两种一样,此处并没有实现交互式转场动画。因为tabbarController在项目中一般是作为容器使用,其内部包含的视图一般包含有其他手势,所以对tabbarController做交互动画的极容易产生手势冲突。所以只需要配置其非交互是转场动画设置即可。自定义的转场动画就讲到这里,关于更多的细节部分(包括转场动画控制器、协调器的基本使用),请参照demo学习研究。如果上述有任何错误欢迎指正!

  • 相关阅读:
    PlateSpin 完全复制由于LVM没有可用空闲空间导致失败
    Linux LVM学习总结——放大LV容量
    Linux AVG ANTIVIRUS FREE使用介绍
    Linux如何查看JDK的安装路径
    Cannot set a credential for principal 'sa'. (Microsoft SQL Server,错误: 15535)
    记一次Linux服务器上查杀木马经历
    Linux NetHogs监控工具介绍
    Linux make: g++: Command not found
    Linux的NTP配置总结
    Linux内核的文件预读readahead
  • 原文地址:https://www.cnblogs.com/purple-sweet-pottoes/p/5482088.html
Copyright © 2011-2022 走看看