前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【iOS】今日头条的转场动画设置+手势控制

【iOS】今日头条的转场动画设置+手势控制

作者头像
MapleYe
发布2020-03-31 11:01:33
1.8K0
发布2020-03-31 11:01:33
举报
文章被收录于专栏:MapleYe

前言

最近公司有个需求,做一个今日头条的用户动态的进入和退出的动画效果,并且退场时,可以自己点击退出,也可以手势下滑退出。头条的效果如下:

今日头条效果
今日头条效果

分析

1、动画转场的实现

首先我们需要实现UINavigationDelegate

代码语言:javascript
复制
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC

此方法返回一个遵守UIViewControllerAnimatedTransitioning的class,在里面书写我们要实现的动画效果

2、触发pop的手势处理

同样的需要实现UINavigationDelegate

代码语言:javascript
复制
- (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController

此方法返回一个遵守UIViewControllerInteractiveTransitioning的class,一般会用UIPercentDrivenInteractiveTransition。这个percent手势处理转场的方式,只要按时机调用以下三个方法

代码语言:javascript
复制
/// 返回这个转场完成的百分比 0~1
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
/// 取消转场
- (void)cancelInteractiveTransition;
/// 完成转场
- (void)finishInteractiveTransition;

而如果我们需要实现下滑退出的话,就需要配合UIPanGestureRecognizer进行使用了,Demo核心的手势处理代码如下:

代码语言:javascript
复制
- (CGFloat)percentForGesture:(UIPanGestureRecognizer *)gesture{
    // 最多只能移动SL_SCREEN_HEIGHT * 0.5
    CGFloat maxOffset = ZFPlayer_ScreenHeight * 0.5;
    CGFloat y = [gesture locationInView:[UIApplication sharedApplication].keyWindow].y;
    // 移动的距离
    CGFloat distance = y - self.startOffsetY;
    distance = MIN(maxOffset, distance);
    double degree = (distance / maxOffset) * M_PI_2;
    // 为增量实现一个曲线变化的效果
    double x = 1 - (sin(degree));
    // 计算增量
    CGFloat delta = distance - self.lastOffsetY;
    self.lastOffsetY = self.lastOffsetY + x * delta;
    self.lastOffsetY = MAX(self.lastOffsetY, 0);
    CGFloat percent = self.lastOffsetY / maxOffset;
    return percent;
}


- (void)panAction: (UIPanGestureRecognizer *)gestureRecognizer
{
    switch (gestureRecognizer.state){
        case UIGestureRecognizerStateBegan:
        {
            self.startOffsetY = [gestureRecognizer locationInView:[UIApplication sharedApplication].keyWindow].y;
            [self.navigationController popViewControllerAnimated:YES];
            break;
        }
        case UIGestureRecognizerStateChanged:
            // 调用updateInteractiveTransition来更新动画进度
            // 里面嵌套定义 percentForGesture 方法计算动画进度
            [self.interactiveGes updateInteractiveTransition:[self percentForGesture:gestureRecognizer]];
            break;
        case UIGestureRecognizerStateEnded:
            //判断手势位置,要大于一般,就完成这个转场,要小于一半就取消
            if ([self percentForGesture:gestureRecognizer] >= 0.4) {
                self.transition.isComplete = YES;
                // 完成交互转场
                [self.interactiveGes finishInteractiveTransition];
            }else {
                // 取消交互转场
                [self.interactiveGes cancelInteractiveTransition];
            }
            break;
        default:
            [self.interactiveGes cancelInteractiveTransition];
            break;
    }
}

要注意的是,在pan手势触发的时候,需要先调用[self.navigationController popViewControllerAnimated:YES];,告诉导航控制器,我要执行pop操作

3、手势退出和点击back退出的处理

我们可以仔细观察一下今日头条的Gif,不难发现他点击返回键退出,以及手势退出时,转场动画时不一样的。

  • 点击返回键退出时:直接中间一个大的圆形头像,回到上个列表头像位置
  • 手势退出时:整个页面下滑,背景透明度改变,松开时,再进入点击返回键退出时的动画效果

因为这里产生了两种动画执行的方式,我这里声明了一个属性,继续用户是点击退出,然后手势退出的

代码语言:javascript
复制
@property (nonatomic, assign) BOOL isInteracting;

那么在点击退出时,设置为NO,请他情况皆为YES,然后在对应的地方做处理即可

代码语言:javascript
复制
/// 若不是手势退出,直接返回nil则不会调用手势操作的相关方法
- (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
    return self.isInteracting ? self.interactiveGes : nil;
}

同时,在转场动画也要做相应的处理,转场动画需要标记手势是否完成,然后再去做对应的动画

代码语言:javascript
复制
/// 关注的用户动态转场
@interface MPUserDynamicTransition : NSObject<UIViewControllerAnimatedTransitioning, CAAnimationDelegate>
/// 是否手势退出
@property (nonatomic, assign) BOOL isInteracting;
/// 是否手势完成
@property (nonatomic, assign) BOOL isComplete;

pop动画的核心动画代码

代码语言:javascript
复制
- (void)startPopAnimation: (nonnull id<UIViewControllerContextTransitioning>)transitionContext
{
    UIView *contentView = [transitionContext containerView];
    // 获取 fromView 和 toView
    UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
    UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    UIView *whiteCoverView = [[UIView alloc] init];
    whiteCoverView.backgroundColor = [UIColor blackColor];
    whiteCoverView.frame = CGRectMake(0, 0, ZFPlayer_ScreenWidth, ZFPlayer_ScreenHeight);
    whiteCoverView.alpha = 0;
    [contentView addSubview:toView];
    [contentView addSubview:whiteCoverView];
    [contentView addSubview:fromView];
    
    UIImageView *imageView = [[UIImageView alloc] init];
    imageView.contentMode = UIViewContentModeScaleAspectFill;
    imageView.clipsToBounds = YES;
    imageView.image = self.startImage;
    imageView.layer.cornerRadius = ZFPlayer_ScreenWidth * 0.5;
    CGFloat top = (ZFPlayer_ScreenHeight - ZFPlayer_ScreenWidth) * 0.5;
    CGRect winFrame = CGRectMake(0, top, ZFPlayer_ScreenWidth, ZFPlayer_ScreenWidth);
    imageView.frame = winFrame;
    imageView.hidden = YES;
    [contentView addSubview:imageView];
    
    CGFloat targetCorner = 0;
    CGRect targetFrame = CGRectZero;
    if (self.startView) {
        targetFrame = [self.startView convertRect:self.startView.bounds toView:nil];
        targetFrame = CGRectMake(self.endX, targetFrame.origin.y, targetFrame.size.width, targetFrame.size.height);
        targetCorner = self.startView.bounds.size.width * 0.5;
    }
    dispatch_block_t block = dispatch_block_create(0, ^{
        imageView.hidden = NO;
        toView.alpha = 1.0f;
        fromView.transform = CGAffineTransformIdentity;
        fromView.alpha = 0.0f;
        whiteCoverView.alpha = 0.4;
        [UIView animateWithDuration:self.duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
            whiteCoverView.alpha = 0;
            imageView.frame = targetFrame;
            imageView.layer.cornerRadius = targetCorner;
        } completion:^(BOOL finished) {
            [imageView removeFromSuperview];
            [whiteCoverView removeFromSuperview];
            // 结束动画
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        }];
    });
    if (self.isInteracting) {
        whiteCoverView.alpha = 1;
        [UIView animateWithDuration:self.duration animations:^{
            whiteCoverView.alpha = 0.4;
            fromView.transform = CGAffineTransformScale(fromView.transform, 0.9, 0.9);
            fromView.transform = CGAffineTransformTranslate(fromView.transform, 0, ZFPlayer_ScreenHeight * 0.5);
        } completion:^(BOOL finished) {
            if (self.isComplete) {
                block();
            }else {
                [imageView removeFromSuperview];
                [whiteCoverView removeFromSuperview];
                // 结束动画
                [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
            }
        }];
    }else {
        block();
    }
}

注意self.isInteractingself.isComplete这两个Bool控制显示的动画即可

4、完成的效果如下

手势退出转场演示
手势退出转场演示

5、总结

这个Demo只是在演示如何用一个Transition,处理点击退出和手势退出时,执行不一样的转场效果。这里还需要完善的地方有

  • 用户详情页做成头条的列表页面时,退出pan的手势和tableView的触发时机
  • 侧滑处理,这个红色页面是不能侧滑退出的

关于转场动画的书写,可以看以下链接 https://blog.devtang.com/2016/03/13/iOS-transition-guide/

6、Demo地址

https://github.com/maple1994/MPPlayerDemo

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 分析
    • 1、动画转场的实现
      • 2、触发pop的手势处理
      • 3、手势退出和点击back退出的处理
      • 4、完成的效果如下
      • 5、总结
      • 6、Demo地址
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档