iOS转场动画基础讲解与实战

基于这个标题,可能会有部分小伙伴感觉比较陌生,但是其实在我们日常工作开发中,每时每刻都在跟它打交道,只不过我们平时使用的是系统原生的API,没有过多的去注意而已.没错,我指的就是Present,Dissmiss和Push,Pop动画.

开始我们今天的话题

平时我们开发中视图切换方法我在这里就不多赘述了,在没有特殊需求的情况下,系统的弹出和收回已经能够满足我们的要求了,可是如果你想要实现自己期望的动画效果,就有必要了解一下自定义转场动画了.


其实自定义转场动画跟我们之前调用原生的方法没有太大的区别,但是我们需要它在执行执行转场动画之前告诉它用我们自定义的转场动画而非系统本身的转场动画,而在开发中,要达到类似的目的,delegate再合适不过了.

本篇动画以Push动画为例,Present动画和Push类似,依葫芦画瓢,实例暂时没有牵扯到交互,如果后面有更合适的例子,我再更新交互动画

自定义非交互转场动画步骤大体分以下几步:

  • 1 . 设置动画的代理
  • 2 . 实现代理方法,在代理方法中提供实现动画逻辑的具体类
  • 3 . 在该类中实现具体的动画逻辑

点击查看大图这种切换方式在需求中算是比较常见的,特别是IM功能中,下面我就以这种切换方式做具体的分析:


iOS转场动画基础讲解与实战_第1张图片
渣渣效果图.gif

效果图有点渣渣,大家凑合一下,如果你迫不及待想看真实的运行效果,请点击这里

  • 1 . 开始我们点击的时候:
 cell.tapCallBack = ^(UIImage *image){
        
        ShowLargePicViewController *largePicVC = [[ShowLargePicViewController alloc] initWithLargeImage:image tapHandle:^UIImageView *{
            
            SenderTableViewCell * currentCell = [tableView cellForRowAtIndexPath:indexPath];
            
            return currentCell.showingImageView;
        }];
        //设置代理
        self.navigationController.delegate = largePicVC;
        
        [self.navigationController pushViewController:largePicVC animated:true];
    };

这里我们需要让成为代理的对象遵守一下UINavigationControllerDelegate这个协议,在不算复杂的情况下,我们直接让下个目标控制器成为代理对象,在这也就是largePicVC

  • 2 . 接着我们实现代理方法-(id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC在这个方法中提供实现动画逻辑的具体类
 -(id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
    if (operation == UINavigationControllerOperationPush) {
        
        return [[Animator alloc] initWithNaviTransitionType:NaviTransitionTypePush tapHandle:self.tapHandle];
        
    } else {
        
        return [[Animator alloc] initWithNaviTransitionType:NaviTransitionTypePop tapHandle:self.tapHandle];
    }
}

这里创建该类的时候有一个枚举变量,区分是push操作还是pop操作,后面会用到.

  • 3 . 来到动画逻辑实现的类里(也就是这里的Animator)
/**
 返回动画执行时间
 */
-(NSTimeInterval)transitionDuration:(id)transitionContext
/**
 执行动画的具体逻辑
 */
-(void)animateTransition:(id)transitionContext

这两个方法就是核心了,一个返回动画时长,一个是动画的具体逻辑实现
为了区分push和pop动画,我们需要实现两个动画逻辑,刚才那个枚举就起作用了

-(void)animateTransition:(id)transitionContext
{
    if (self.type == NaviTransitionTypePush) {
        //push动画
        [self beginPushAnimationWithTransitionContext:transitionContext];
        
    }else{
        //pop动画
        [self beginPopAnimationWithTransitionContext:transitionContext];
    }
}

下面这些代码是具体的动画逻辑实现,注释很清楚,需要注意的就是坐标转换,fromVC和toVC,模拟器快照白屏和最后completion需要调用completeTransition这个方法

-(void)animateTransition:(id)transitionContext
{
    if (self.type == NaviTransitionTypePush) {
        
        //push动画
        [self beginPushAnimationWithTransitionContext:transitionContext];
        
    }else{
        
        //pop动画
        [self beginPopAnimationWithTransitionContext:transitionContext];
    }
}

- (void)beginPushAnimationWithTransitionContext:(id)transitionContext
{

    ShowLargePicViewController *toVC = (ShowLargePicViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    //获取当前点击的View
    UIImageView *animationIv = self.tapHandle();
    
    //生成截图,这句在模拟器上生成的是空白的截图,需要注意
    UIView *snapShotAnimationView = [animationIv snapshotViewAfterScreenUpdates:false];
    
    //动画容器视图,所有动画都在里面执行
    UIView *containerView = [transitionContext containerView];
    
    //原始坐标系转化到container坐标系
    snapShotAnimationView.frame = [animationIv convertRect:animationIv.bounds toView:containerView];
    //假如动画容器中
    [containerView addSubview:toVC.view];
    [containerView addSubview:snapShotAnimationView];
    //设置原始状态
    animationIv.hidden = true;
    toVC.resultIv.hidden = true;
    toVC.view.alpha = 0.0;
    //计算container坐标最终坐标
    CGRect resultRect = [toVC.resultIv convertRect:toVC.resultIv.bounds toView:containerView];
    //执行动画
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        
        snapShotAnimationView.frame = resultRect;
        toVC.view.alpha = 1.0;
        
    } completion:^(BOOL finished) {
    
        snapShotAnimationView.hidden = true;
        toVC.resultIv.hidden= false;
        //这个一定要写,避免动画过程中出现未知的bug
        [transitionContext completeTransition:true];
    }];
    
}

- (void)beginPopAnimationWithTransitionContext:(id)transitionContext
{
    ShowLargePicViewController *fromVC = (ShowLargePicViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    ViewController *toVc = (ViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    UIView *containerView = [transitionContext containerView];
    // 取出container中的刚才push做动画的截图
    __block UIView *snapShotAnimationView = containerView.subviews.lastObject;
    
    snapShotAnimationView.hidden = false;
    
    CGRect snapShotAnimationViewRect = snapShotAnimationView.bounds;
    
    snapShotAnimationViewRect.size = fromVC.resultIv.bounds.size;
    
    snapShotAnimationView.bounds = snapShotAnimationViewRect;
    //由于是从container中直接取出来的,所以不需要再加进去,直接转换坐标系
    [snapShotAnimationView convertRect:snapShotAnimationView.bounds toView:containerView];
    
    fromVC.resultIv.hidden = true;
    //插入是为了防止挡住执行动画的View
    [containerView insertSubview:toVc.view atIndex:0];
    //获取最终的View
    UIImageView *resultView = self.tapHandle();
    
    CGRect resultRect = [resultView convertRect:resultView.bounds toView:containerView];
    
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        
        snapShotAnimationView.frame = resultRect;
        
        fromVC.view.alpha = 0.0;
        
    } completion:^(BOOL finished) {
        
        [snapShotAnimationView removeFromSuperview];
        snapShotAnimationView = nil;
        
        resultView.hidden = false;
        //这里也必须执行这句,原因跟上面是一样的
        [transitionContext completeTransition:true];
    }];

}

最后再放一下源代码地址,如有错误,欢迎指正.

你可能感兴趣的:(iOS转场动画基础讲解与实战)