如何实现UICollectionViewCell选中时内容展开的丝滑动画效果
实现UICollectionViewCell选中卡片展开动画(类App Store效果)
现有代码问题分析
- 总动画时长长达2.1s,远超系统交互动画的常规时长(一般0.3~0.6s),拖沓感明显
- 连续嵌套的UIView基础动画没有使用弹性曲线/关键帧动画API,动画节奏不符合物理直觉
- 动画没有和原UICollectionViewCell的位置、尺寸做绑定,没有从对应单元格展开的连贯感,自然和App Store效果有差距
- present调用时机放在动画之后,逻辑顺序错误,会出现视图先闪再动画的问题
优化实现方案
1. 自定义转场动画实现(推荐,最贴合App Store效果)
App Store的卡片展开效果本质是自定义模态转场,需要遵守UIViewControllerAnimatedTransitioning协议,把原cell的位置作为动画的起点,实现无缝衔接:
首先在didSelectItemAtIndexPath方法内获取选中cell的位置,给弹出控制器配置转场参数:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { // 获取选中的cell,转换坐标得到动画起始frame UICollectionViewCell *selectedCell = [collectionView cellForItemAtIndexPath:indexPath]; CGRect originFrame = [collectionView convertRect:selectedCell.frame toView:self.view]; AnimateStaticImageViewController *animateImageVC = [[AnimateStaticImageViewController alloc] init]; animateImageVC.modalPresentationStyle = UIModalPresentationFullScreen; animateImageVC.modelImage = [UIImage imageNamed:text]; // 给AnimateStaticImageViewController新增两个属性,存储动画起始frame和对应的cell图片 animateImageVC.originFrame = originFrame; // 设置转场代理,当前控制器需遵守UIViewControllerTransitioningDelegate协议 animateImageVC.transitioningDelegate = self; [self presentViewController:animateImageVC animated:YES completion:nil]; }
实现转场代理方法,返回自定义动画器:
// 入场动画代理 - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return [[CardPresentAnimator alloc] init]; } // 退场动画代理,如果需要收回时也有连贯效果可实现对应的CardDismissAnimator - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed { return [[CardDismissAnimator alloc] init]; }
入场动画器CardPresentAnimator实现:
@implementation CardPresentAnimator - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext { return 0.5; // 时长和系统交互动画对齐,避免违和感 } - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *containerView = transitionContext.containerView; [containerView addSubview:toVC.view]; AnimateStaticImageViewController *targetVC = (AnimateStaticImageViewController *)toVC; CGRect startFrame = targetVC.originFrame; CGRect endFrame = containerView.bounds; // 初始状态和原cell完全对齐,做到无缝衔接 toVC.view.frame = startFrame; toVC.view.layer.cornerRadius = 12; // 和cell的圆角参数保持一致 toVC.view.clipsToBounds = YES; // 用弹性动画模拟App Store的回弹效果 [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.8 initialSpringVelocity:0.5 options:UIViewAnimationOptionCurveEaseInOut animations:^{ toVC.view.frame = endFrame; toVC.view.layer.cornerRadius = 0; // 全屏后圆角归零 } completion:^(BOOL finished) { [transitionContext completeTransition:!transitionContext.transitionWasCancelled]; }]; } @end
2. 简化优化方案(无需自定义转场)
如果暂时不需要退场联动、手势交互等能力,可以直接优化现有动画的参数,大幅提升流畅度:
AnimateStaticImageViewController *animateImageVC = [[AnimateStaticImageViewController alloc] init]; animateImageVC.modalPresentationStyle = UIModalPresentationFullScreen; animateImageVC.modelImage = [UIImage imageNamed:text]; // 先设置初始状态 animateImageVC.view.transform = CGAffineTransformMakeScale(0.92, 0.92); animateImageVC.view.alpha = 0; // 先弹出控制器再执行动画,避免闪屏 [self presentViewController:animateImageVC animated:NO completion:nil]; // 单次弹性动画替代三层嵌套动画 [UIView animateWithDuration:0.5 delay:0 usingSpringWithDamping:0.85 initialSpringVelocity:0.3 options:UIViewAnimationOptionCurveEaseInOut animations:^{ animateImageVC.view.transform = CGAffineTransformIdentity; animateImageVC.view.alpha = 1; } completion:nil];
效果优化小贴士
- 确保cell内的图片和弹出控制器内的图片裁剪规则、显示比例完全一致,避免动画过程中出现图片跳变
- 如果当前页面有导航栏、标签栏,可以在动画过程中同步做隐藏过渡,提升整体连贯感
- 圆角、阴影的变化要和尺寸变化同步,避免出现生硬的视觉突变
内容的提问来源于stack exchange,提问作者Imran Rony
相关产品推荐
相关产品推荐

