美文网首页iOS那些事iOS开发
App Store中Today转场动画实现

App Store中Today转场动画实现

作者: 8e750c8f0fae | 来源:发表于2019-07-26 17:19 被阅读159次

    先看动画,这是仿后的效果

    Untitled.gif

    这转场动画,乍看一下很复杂,其实是拆化细分之后是很简单的。大部分动画都只是位移,旋转,颜色变化这三分部叠加组合起来而已。

    原理

    所谓转场动画无非就是从A跳转到B的过程中,通过拦截重写

    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
    

    在其中我们可以获得三个视图:

    • fromView: 转场开始视图 (即A视图)
    • toView:转场结束视图 (即B视图)
    • containerView:转场视图容器,所有转场动画在里面完成

    这样一来,我们就能拿到AB视图里的所有元素和位置,把它放进containerView里,让A视图里的元素位移到B视图里对应的位置,完成转场,这样在视觉上就无缝衔接了

    思路

    有了理论基础我们再来谈思路,我们可以把这个动画先拆成两部分,即A到B,B到A两部分,然后A到B可以可以有 点击Cell变小 --> 松手Cell回复正常 --> 转场,A的元素位移到B里对应的位置 --> A到B转场完成。
    B到A差点多也是 模糊背景 --> 缩小A视图 --> 转场,B的元素位移到A李对应的位置 --> B到A转场完成,其实都大同小异。

    让cell变小再正常一是为了给用户增加反馈,二是这样才符合运动规律,具体可以百度运动规律,要做好动画对物体的运动规律还是要有所了解,不然动画会十分生硬

    实现

    • 页面

    先搭建出想要跳转的两个页面,这个比较简单,所以这里就不写了,重点还是放在动画上,不过要注意,做动画推荐用frame布局。

    20190726113746.jpg
    • 动画拆解
      根据上面拆解的动画步骤,一个个来实现
    点击Cell变小
    - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath  {
         _selectIndexPath = indexPath;
        UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
        [UIView animateWithDuration:0.3 animations:^{
            cell.transform = CGAffineTransformMakeScale(0.8, 0.8);
        }];
        return YES;
    }
    
    松开回复正常
    - (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
        if ([_selectIndexPath isEqual:indexPath]) {
            [UIView animateWithDuration:0.3 animations:^{
                cell.transform = CGAffineTransformMakeScale(1, 1);
                return;
            }];
        }
    }
    

    转场,A的元素位移到B里对应的位置(重点)

    我们先设置代理 self.navigationController.delegate = self; 实现代理

    -(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
        return self;
    }
    - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
        return 1.0f;
    }
    
    

    当点击跳转时,会进入animateTransition:方法

    // 修改转场动画
    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
        //获取的转场视图容器
        UIView *containerView = [transitionContext containerView];
        //获取的B控制器
        GSInfoViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        //获取b视图的背景图 这里需要注意,我是将b视图bg上lb的标签全部添加在bgImageView上,所以做动画时只拿到bgImageView就行了;
        UIView *toBgImageView = toVC.bgImageView;
        toBgImageView.hidden = YES;
        toVC.view.alpha = 0;
        //将B添加到转场视图容器里
        [containerView addSubview:toVC.view];
         
        //获取对应的A页面的cell
        GSTodayTableViewCell *cell = (GSTodayTableViewCell *)[self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]];
        //获取A页面的各个元素
        //创建A页面的cell的背景,设置frame
        UIView *fromBgImageView = [[UIImageView alloc]initWithImage:cell.bgImageView.image];
        fromBgImageView.frame = [containerView convertRect:cell.bgImageView.frame fromView:cell.bgImageView.superview];
        //创建A页面的cell的标签,设置frame
        UILabel *typeL = [self copyLabel:cell.typeL];
        [fromBgImageView addSubview:typeL];
        //创建A页面的cell的标题,设置frame
        UILabel *contentLabel =[self copyLabel:cell.contentL];
        [fromBgImageView addSubview:contentLabel];
        //创建关闭按钮,虽然A页面没有该元素,但动画时会出现,这里的frame可以根据自己感觉调整
        UIButton *closeBtn = [UIButton buttonWithType:0];
        closeBtn.x = cell.width - 2*kMargin - 60;
        closeBtn.centerY = kHeight(20)-10;
        closeBtn.size = CGSizeMake(40, 40);
        closeBtn.alpha = 0.6;
        [closeBtn setBackgroundImage:[UIImage imageNamed:@"close"] forState:UIControlStateNormal];
        [fromBgImageView addSubview:closeBtn];
        //将A添加到转场视图容器里,注意,需要先添加B在添加A,应该A是在前面做动画的
        [containerView addSubview:fromBgImageView];
        
        //这里可以设置动画时间,弹性等
        [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0f usingSpringWithDamping:0.6f initialSpringVelocity:1.0f options:UIViewAnimationOptionCurveLinear animations:^{
            [containerView layoutIfNeeded];
            toVC.view.alpha = 1.0f;
            //这里将B页面各个元素的frame给A页面对应的
            typeL.frame = toVC.typeL.frame ;
            closeBtn.frame = toVC.closeBtn.frame;
            contentLabel.frame = toVC.titleL.frame;
            fromBgImageView.frame = [containerView convertRect:toBgImageView.frame fromView:toBgImageView.superview];
        } completion:^(BOOL finished) {
            //都是为了动画弄出来的工具对象,全部删了隐藏了
            toBgImageView.hidden = NO;
            closeBtn.hidden = YES;
            [fromBgImageView removeFromSuperview];
            [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
        }];
    }
    

    上面用到的复制一个完整的label用方法

    - (UILabel *)copyLabel:(UILabel *)label {
        UILabel *newLabel = [[UILabel alloc] init];
        newLabel.text = label.text;
        newLabel.font = label.font;
        newLabel.alpha = label.alpha;
        newLabel.frame =  label.frame;
        newLabel.textColor = label.textColor;
        newLabel.textAlignment = label.textAlignment;
        return newLabel;
    }
    

    这样从A跳转到B的过程场动画就完成了


    接下做从B到A跳转的过程,其实差别并不大,一样的设置代理self.navigationController.delegate = self;,再加个背景毛玻璃

        // 背景毛玻璃
        UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
        UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
        effectView.frame = CGRectMake(0, 0, kScreenW, kScreenH);
        [self.view addSubview:effectView];
    

    实现代理

    -(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
        return self;
    }
    - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
        return 1.0f;
    }
    
    

    点击跳转进入animateTransition:

    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
        UIView *containerView = [transitionContext containerView];
        //获取跳转后的页面
        GSTodayViewController *toVC = (GSTodayViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
        //根据selectIndexPath获取对应的cell, selectIndexPath可以在跳转的时候传进来
        GSTodayTableViewCell *cell = (GSTodayTableViewCell *)[toVC.tableView cellForRowAtIndexPath:self.selectIndexPath];
        //获取cell的背景
        UIView *cellBgImageView = cell.bgImageView;
        cellBgImageView.hidden = YES;
        
        //获取当前页面
        GSInfoViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        //获取当前的背景图
        UIView *bgImageView = fromVC.bgImageView;
        bgImageView.hidden = YES;
        //创建背景图做动画用
        UIView *screenshotView = [bgImageView snapshotViewAfterScreenUpdates:NO];
        //圆角与cell保持一致
        screenshotView.layer.masksToBounds = YES;
        screenshotView.layer.cornerRadius = 15;
        screenshotView.frame = [containerView convertRect:bgImageView.frame fromView:bgImageView.superview];
        //创建标签
        UILabel *typeL = [self copyLabel:self.typeL];
        [screenshotView addSubview:typeL];
        //创建标题 到这里可以看出这个刚刚的一模一样
        UILabel *titleL =  [self copyLabel:self.titleL];
        [screenshotView addSubview:titleL];
        [containerView addSubview:screenshotView];
        [containerView insertSubview:toVC.view belowSubview:fromVC.view];
        //动画
        [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.5f initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
            [containerView layoutIfNeeded];
            //隐藏当前视图
            fromVC.view.alpha = 0.0f;
            //转场过程中保持视图圆角
            screenshotView.layer.cornerRadius = 15;
            self.tableView.frame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y, self.tableView.frame.size.width,kScreenW*1.3*0.8);
            self.tableView.layer.cornerRadius = 15;
            screenshotView.frame = [containerView convertRect:cellBgImageView.frame fromView:cellBgImageView.superview];
            //这里需要设置label的frame,与外层cell的位置保持一致就行,外层cell是里的元素frame是写死的,我这里也就一样写死
            typeL.x = kWidth(16);
            typeL.y = kHeight(20);
            titleL.x = kWidth(16);
            titleL.y = kHeight(20)+ typeL.height + 8;
            _closeBtn.alpha = 0;
        } completion:^(BOOL finished) {
            //一样,动画结束后该隐藏的隐藏,该删除的删除
            bgImageView.hidden = YES;
            [screenshotView removeFromSuperview];
            cellBgImageView.hidden = NO;
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        }];
    }
    

    这样一个完整的A跳B,B返A的过场动画就搞定了。

    相关文章

      网友评论

        本文标题:App Store中Today转场动画实现

        本文链接:https://www.haomeiwen.com/subject/pgyrrctx.html