iOS 动画 一 CABasicAnimation和CAKeyFrameAnimation

文章来源声明: 原文作者:用户218169704930; 来源站点:掘金; 原文链接:https://juejin.cn/post/7687077659596800041; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

适合iOS开发者系统理解Core Animation动画原理与API选型,快速掌握基础/关键帧/组动画/转场实现,并规避常见坑;尤其适合需要落地交互动画与转场效果的中高级开发者。

本文目录
CAAnimation -----------

动画类的继承关系。

  • CAAnimation
    • CAAnimationGroup
    • CATransition
    • CAPropertyAnimtion
      • CABasicAnimation
      • CAKeyFrameAnimation

UIView和CALayer的关系

CALayer只负责渲染,手势等其它内容由UIView负责。

那么,在实际动画的过程中,CALayer的frame是怎么变化的。

- (<span>void</span>)viewDidLoad {
    [<span>super</span> viewDidLoad];
    <span>// Do any additional setup after loading the view.</span>
    <span>UIView</span> *view = [[<span>UIView</span> alloc] initWithFrame:<span>CGRectMake</span>(<span>0</span>, <span>0</span>, <span>100</span>, <span>100</span>)];
    view.backgroundColor = [<span>UIColor</span> redColor];
    [<span>self</span>.view addSubview:view];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<span>2</span> * <span>NSEC_PER_SEC</span>)), dispatch_get_main_queue(), ^{
        [<span>UIView</span> animateWithDuration:<span>5</span> animations:^{
            view.frame = <span>CGRectMake</span>(<span>500</span>, <span>200</span>, <span>100</span>, <span>100</span>);
            view.backgroundColor = [<span>UIColor</span> greenColor];
        }];
        
        [<span>NSTimer</span> scheduledTimerWithTimeInterval:<span>1</span> repeats:<span>YES</span> block:^(<span>NSTimer</span> * _Nonnull timer) {
            
            <span>NSLog</span>(<span>@"layerframe1 %@"</span>, <span>NSStringFromCGRect</span>(view.layer.frame));
            <span>NSLog</span>(<span>@"layerframe2 %@"</span>, <span>NSStringFromCGRect</span>(view.layer.presentationLayer.frame));
        }];
    });
    
}


image.png

可以看到。View的Layer保留的是最终状态,动画过程是有一个叫做presentationLayer的在改变。

在 iOS 中,Core Animation 的渲染架构与 Mac OS X 高度一致,同样由三棵树构成:模型树(Layer Tree)呈现树(Presentation Tree)渲染树(Render Tree) 。这三者协同工作,确保动画流畅、高效且与 UI 逻辑解耦。

模型树(Layer Tree)

  • 这是开发者直接操作的图层结构,存储所有图层的“目标值”。
  • 例如,当你设置 layer.position = CGPoint(x: 100, y: 200) 时,这个值就存储在模型树中。
  • 模型树的结构与 UIView 的层级结构完全对应,是 UI 布局和内容管理的核心。

呈现树(Presentation Tree)

  • 这是动画执行时的“实时状态”树,存储的是当前帧的“显示值”。
  • 例如,当一个图层从位置 A 动画到位置 B 时,呈现树会记录每一帧的中间位置,而模型树仍保留最终目标值 B。
  • 呈现树是只读的,可通过 layer.presentationLayer 访问,常用于获取动画过程中的实时状态(如拖拽跟随、碰撞检测等)。

渲染树(Render Tree)

  • 这是 Core Animation 的私有内部结构,负责实际的 GPU 渲染和合成。
  • 它不对外暴露,由系统自动管理,根据呈现树的值生成最终的像素输出。
  • 渲染过程在独立的渲染服务进程(Render Server)中完成,与 App 主线程分离,确保 UI 响应不被阻塞。

iOS 特有的优化机制

  • 双进程架构:App 进程只负责构建和更新图层树,真正的渲染由独立的 Render Server 进程完成,通过 IPC(进程间通信)传递数据,极大提升了性能和稳定性。
  • 事务提交机制:所有对图层属性的修改都会被包装进 CATransaction,并在 RunLoop 即将休眠时统一提交,避免频繁触发渲染。
  • 硬件加速:Core Animation 默认使用 GPU 进行图层合成和动画插值,只有在特定情况下(如自定义绘制、阴影、圆角等)才会触发 CPU 离屏渲染。

这种三树分离的设计,使得 iOS 能够在保持高帧率动画的同时,让开发者专注于 UI 逻辑,而无需关心底层渲染细节。

动画和触摸事件

在iOS动画过程中,手势是根据当前动画的视图区域确定的。

如果添加了动画,且设置了animation.removedOnCompletion = NO;,则还是动画的视图区域能相应事件,这个情况下,只有删除动画,才能让之前的视图区域接收触摸响应事件。

- (void)viewDidLoad {
    <span>[super viewDidLoad]</span><span>;</span>
    // Do any additional setup after loading the view.
    UIView *<span>view</span> = [[UIView alloc] initWithFrame:CGRectMake(<span>0</span>, <span>100</span>, <span>100</span>, <span>100</span>)]<span>;</span>
    <span>view.backgroundColor</span> = [UIColor redColor]<span>;</span>
    <span>view.tag</span> = <span>100</span><span>;</span>
    <span>[self.view addSubview:view]</span><span>;</span>
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    UIView *<span>redView</span> = [self.view viewWithTag:<span>100</span>]<span>;</span>
    
    CABasicAnimation *<span>animation</span> = [CABasicAnimation animation]<span>;</span>
    <span>animation.keyPath</span> = @<span>"position.y"</span><span>;</span>
    <span>animation.fromValue</span> = @<span>0</span><span>;</span>
    <span>animation.toValue</span> = @<span>300</span><span>;</span>
    <span>animation.duration</span> = <span>5</span><span>;</span>
    <span>animation.beginTime</span> = CACurrentMediaTime() + <span>2</span><span>;// 延迟2秒看到效果</span>
    
    <span>animation.removedOnCompletion</span> = <span>NO</span><span>;</span>
    // 设置fillMode必须搭配removedOnCompletion使用
    // kCAFillModeRemoved 默认值,直接根据元素的layer属性显示
    // kCAFillModeForwards 保留最后一帧的画面
    // kCAFillModeBackwards 动画开始之前,先显示第一帧的动画。动画结束后,恢复原来的状态。需要配合延时动画才有效果
    // kCAFillModeBoth 开始动画之前,先显示第一帧,结束后,显示最后一帧
    <span>animation.fillMode</span> = kCAFillModeBoth<span>;</span>
    
    <span>[redView.layer addAnimation:animation forKey:@"animation1"]</span><span>;</span>
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"begin test touch")<span>;</span>
        <span>[redView.layer removeAnimationForKey:@"animation1"]</span><span>;</span>
    })<span>;</span>
}

CALayer的隐式动画

修改CALayer的属性,会有一个默认的动画。UIView的layer没有这个动画。

    // self.view.layer不会有隐式动画。
    // 只有添加的Layer才会有隐式动画
    
    CALayer *<span>layer1</span> = [CALayer layer]<span>;</span>
    <span>layer1.frame</span> = CGRectMake(<span>100</span>, <span>100</span>, <span>200</span>, <span>200</span>)<span>;</span>
    <span>layer1.backgroundColor</span> = [UIColor greenColor].CGColor<span>;</span>
    <span>[self.view.layer addSublayer:layer1]</span><span>;</span>
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        <span>layer1.backgroundColor</span> = [UIColor purpleColor].CGColor<span>;</span>
        <span>layer1.frame</span> = CGRectMake(<span>0</span>, <span>300</span>, <span>300</span>, <span>300</span>)<span>;</span>
    })<span>;</span>


CAKeyFrameAnimation

CAPropertyAnimation有CABasicAnimation和CAKeyFrameAnimation这2个子类。

  • 通过values修改动画路径。
  • 通过path修改动画路径。
- (void)viewDidLoad {
    <span>[super viewDidLoad]</span><span>;</span>
    // Do any additional setup after loading the view.
    
    
    CALayer *<span>layer</span> = [CALayer layer]<span>;</span>
    <span>layer.backgroundColor</span> = [UIColor redColor].CGColor<span>;</span>
    <span>layer.frame</span> = CGRectMake(<span>100</span>, <span>100</span>, <span>100</span>, <span>100</span>)<span>;</span>
    <span>[self.view.layer addSublayer:layer]</span><span>;</span>
    
    CAKeyframeAnimation *<span>animation</span> = [CAKeyframeAnimation animation]<span>;</span>
    <span>animation.keyPath</span> =  @<span>"transform.rotation"</span><span>;</span>
    
    <span>[CATransaction begin]</span><span>;</span>
    <span>[CATransaction setCompletionBlock:^{
        // 当前事务(addSublayer)执行完成,layer已经渲染到屏幕
        CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]</span><span>;</span>
        <span>animation.keyPath</span> = @<span>"transform.rotation"</span><span>;</span>
        CGFloat <span>p3</span> = <span>3.0</span> / <span>180.0</span> * M_PI<span>;</span>
        <span>animation.values</span> = @[@<span>0</span>,@(p3),@<span>0</span>,@(-p3),@<span>0</span>]<span>;</span>
        <span>animation.repeatCount</span> = MAXFLOAT<span>;</span>
        <span>animation.duration</span> = <span>0.3</span><span>;</span>
        <span>[layer addAnimation:animation forKey:@"animation1"]</span><span>;</span>
    }]<span>;</span>
    <span>[CATransaction commit]</span><span>;</span>
    //    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    //        // 这里的单位是弧度。3.14就是180度。
    //        <span>animation.values</span> = @[@<span>0</span>,@(<span>3</span> / <span>180.0</span> * M_PI),@<span>"0"</span>,@(-<span>3</span>/<span>180.0</span> * M_PI),@<span>0</span>]<span>;</span>
    ////        <span>animation.autoreverses</span> = <span>YES</span><span>;</span>
    //        <span>animation.repeatCount</span> = MAXFLOAT<span>;</span>
    //        <span>animation.duration</span> = <span>0.3</span><span>;</span>
    //        <span>[layer addAnimation:animation forKey:@"animation1"]</span><span>;</span>
    //    })<span>;</span>
    
}

path动画

- (void)viewDidLoad {
    <span>[super viewDidLoad]</span><span>;</span>
    // Do any additional setup after loading the <span>view.
    
    
    self.view.backgroundColor</span> = [UIColor whiteColor]<span>;</span>
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        UIImage *<span>image</span> =  [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@<span>"https://img14.360buyimg.com/pop/jfs/t1/65536/18/25365/187701/66b48584F7e4e95b8/283d6ab2b046244f.png?x-oss-process=image%2Fformat%2Cjpg%2Fresize%2Cw_500%2Fquality%2Cq_80"</span>]]]<span>;</span>
        dispatch_async(dispatch_get_main_queue(), ^{
            UIBezierPath *<span>path</span> = [UIBezierPath bezierPath]<span>;</span>
            <span>[path moveToPoint:CGPointMake(20, 300)]</span><span>;</span>
            <span>[path addCurveToPoint:CGPointMake(300, 300) controlPoint1:CGPointMake(100, 100) controlPoint2:CGPointMake(200, 400)]</span><span>;</span>
            
            
            CAShapeLayer *<span>layer1</span> = [CAShapeLayer layer]<span>;</span>
            <span>layer1.path</span> = path.CGPath<span>;</span>
            <span>layer1.fillColor</span> = nil<span>;</span>
            <span>layer1.strokeColor</span> = [UIColor blueColor].CGColor<span>;</span>
            <span>[self.view.layer addSublayer:layer1]</span><span>;</span>
            
            
            CALayer *<span>animationLayer</span> = [CALayer layer]<span>;</span>
            <span>animationLayer.contents</span> = (id )image.CGImage<span>;</span>
            <span>animationLayer.frame</span> = CGRectMake(<span>20</span>-<span>30</span>, <span>300</span>-<span>60</span>, <span>60</span>, <span>60</span>)<span>;</span>
            <span>animationLayer.anchorPoint</span> = CGPointMake(<span>0.5</span>, <span>0.9</span>)<span>;</span>
            <span>[self.view.layer addSublayer:animationLayer]</span><span>;</span>
            
            CAKeyframeAnimation *<span>animation</span> = [CAKeyframeAnimation animation]<span>;</span>
            <span>animation.keyPath</span> = @<span>"position"</span><span>;</span>
            <span>animation.path</span> = path.CGPath<span>;</span>
            <span>animation.duration</span> = <span>3</span><span>;</span>
            <span>animation.rotationMode</span> = kCAAnimationRotateAuto<span>;</span>
            <span>animation.repeatCount</span> = MAXFLOAT<span>;</span>
            
            <span>[animationLayer addAnimation:animation forKey:nil]</span><span>;;</span>
        })<span>;</span>
    })<span>;</span>
}

CAAnimationGroup

就是可以将各种动画进行组合。

核心属性

  • animations:这是 CAAnimationGroup 唯一的专属属性,类型是一个数组(NSArray[CAAnimation])。你需要将所有要并发执行的动画对象放入这个数组中。

  • 时间控制属性:动画组会统一控制其内部动画的时间空间。常用的属性包括:

    • duration:动画组的总持续时间。
    • repeatCount:重复播放的次数。
    • autoreverses:动画结束后是否反向播放回初始状态。
    • timingFunction:设置动画的缓动效果(如加速、减速)。

⚠️ 关键注意事项(极易踩坑)

  • 动画会被裁剪(Clipped) :动画组中的动画不会被自动压缩或拉伸以适应动画组的时长。如果组内某个动画的时长超过了 CAAnimationGroupduration,超出的部分将被直接剪掉(只显示前段动画)。
  • 忽略子动画的特定属性:动画组中子动画的 delegate(代理)和 isRemovedOnCompletion(完成后是否移除)属性会被忽略。你需要在 CAAnimationGroup 本身上设置这些属性,由动画组的代理来接收动画开始和结束的消息。
  • 保持最终状态:默认情况下,动画结束后图层会恢复到动画前的状态。为了让动画停留在结束时的状态,需要在动画组上设置 removedOnCompletion = NO 以及 fillMode = kCAFillModeForwards
// 创建动画组
CAAnimationGroup *<span>group</span> = [CAAnimationGroup animation]<span>;</span>

// 创建平移和缩放动画
CABasicAnimation *<span>anim</span> = [CABasicAnimation animation]<span>;</span>
<span>anim.keyPath</span> = @<span>"position.y"</span><span>;</span>
<span>anim.toValue</span> = @<span>400</span><span>;</span>

CABasicAnimation *<span>scaleAnim</span> = [CABasicAnimation animation]<span>;</span>
<span>scaleAnim.keyPath</span> = @<span>"transform.scale"</span><span>;</span>
<span>scaleAnim.toValue</span> = @<span>0.5</span><span>;</span>

// 将动画加入组中
<span>group.animations</span> = @[anim, scaleAnim]<span>;</span>
<span>group.duration</span> = <span>1.0</span><span>;</span>

// 【关键】在动画组上设置填充模式,防止回弹
<span>group.removedOnCompletion</span> = <span>NO</span><span>;</span>
<span>group.fillMode</span> = kCAFillModeForwards<span>;</span>

// 添加到图层
<span>[self.redView.layer addAnimation:group forKey:nil]</span><span>;</span>

通过 CAAnimationGroup,你可以极大地简化代码逻辑,不需要为每个动画单独设置时长和完成回调,只需统一管理动画组即可实现复杂的协同动画效果。


CATransition

CATransition 是 Core Animation 中专门用于处理视图/图层内容切换(如换图、切换子控制器)的过渡动画类。

基础用法

CATransition 通常添加到容器视图(如 UIImageView 或父 UIView)的 layer 上。当容器内的内容发生变化时,系统会自动执行过渡动画。


- (void)viewDidLoad {
    <span>[super viewDidLoad]</span><span>;</span>
    // Do any additional setup after loading the <span>view.
    self.view.backgroundColor</span> = [UIColor redColor]<span>;</span>
    
    
    dispatch_group_t <span>group</span> = dispatch_group_create()<span>;</span>
    dispatch_queue_t <span>queue</span> = dispatch_queue_create(<span>0</span>,
                                                   DISPATCH_QUEUE_CONCURRENT)<span>;</span>
    
    __block NSData *image1<span>;</span>
    __block NSData *image2<span>;</span>
    dispatch_group_async(group,
 queue,
                         ^{
        <span>image1</span> = [NSData dataWithContentsOfURL:[NSURL URLWithString:@<span>"https://img1.baidu.com/it/u=786478812,1495962150&fm=253&app=120&f=JPEG?w=800&h=1422"</span>]]<span>;</span>
    })<span>;</span>
    dispatch_group_async(group,
 queue,
                         ^{
        <span>image2</span> = [NSData dataWithContentsOfURL:[NSURL URLWithString:@<span>"https://b0.bdstatic.com/ugc/nZYH1k449ZTPOGj4-Mkb_we832996869186f05c0af4661f2cf074c.jpg"</span>]]<span>;;</span>
    })<span>;</span>
    dispatch_group_notify(group,
 dispatch_get_main_queue(),
 ^{
        // 1. 创建动画对象
        CATransition *<span>transition</span> = [CATransition animation]<span>;</span>

        // 2. 设置动画类型 (核心)
        // 公开类型: kCATransitionFade, kCATransitionMoveIn, kCATransitionPush, kCATransitionReveal
        // 私有类型(截图中的用法): @"pageCurl", @"pageUnCurl", @"rippleEffect", @"suckEffect"
        <span>transition.type</span> = kCATransitionMoveIn<span>;</span>

        // 3. 设置动画方向 (可选)
        // kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
        <span>transition.subtype</span> = kCATransitionFromRight<span>;</span>

        // 4. 设置时长
        <span>transition.duration</span> = <span>3</span><span>;</span>
        <span>transition.delegate</span> = self<span>;</span>

        // 5. 设置进度范围 (高级用法,截图重点)
        // startProgress: 动画开始的进度 (0.0 - 1.0)
        // endProgress:   动画结束的进度 (0.0 - 1.0)
        // 截图代码中 <span>start</span>=<span>0.2</span>, end=<span>0.5</span> 意味着只显示翻页动作的中间一段
        <span>transition.startProgress</span> = <span>0.2</span><span>;</span>
        <span>transition.endProgress</span> = <span>0.5</span><span>;</span>

        // 6. 添加到图层并立即改变内容,和先后顺序无关。下面集中方式都会动画效果。
        //
        //        UIImageView *<span>imageView</span> = [[UIImageView alloc] initWithFrame:CGRectMake(<span>100</span>, <span>100</span>, <span>100</span>, <span>100</span>)]<span>;</span>
        //        <span>[imageView.layer addAnimation:transition forKey:@"myTransition"]</span><span>;</span>
        //        <span>imageView.image</span> = [UIImage imageWithData:image1]<span>;</span>
        //        <span>[self.view addSubview:imageView]</span><span>;</span>
        
        //
        //        UIImageView *<span>imageView</span> = [[UIImageView alloc] initWithFrame:CGRectMake(<span>100</span>, <span>100</span>, <span>100</span>, <span>100</span>)]<span>;</span>
        //        <span>[self.view addSubview:imageView]</span><span>;</span>
        //        <span>[imageView.layer addAnimation:transition forKey:@"myTransition"]</span><span>;</span>
        //        <span>imageView.image</span> = [UIImage imageWithData:image1]<span>; // 必须在 addAnimation 之后或同时执行</span>
        
        UIImageView *<span>imageView</span> = [[UIImageView alloc] initWithFrame:CGRectMake(<span>100</span>,
                                                                               <span>100</span>,
                                                                               <span>100</span>,
                                                                               <span>100</span>)]<span>;</span>
        <span>imageView.image</span> = [UIImage imageWithData:image1]<span>; // 必须在 addAnimation 之后或同时执行</span>
        <span>[self.view addSubview:imageView]</span><span>;</span>
        
        <span>[imageView.layer addAnimation:transition forKey:@"myTransition"]</span><span>;</span>
        
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            // 这样没有动画效果。
            <span>imageView.image</span> = [UIImage imageWithData:image2]<span>;</span>
        })<span>;</span>
    })<span>;</span>
}

- (void)animationDidStart:(CAAnimation *)anim{
    NSLog(@"start")<span>;</span>
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    NSLog(@"stop")<span>;</span>
}


动画设置了3秒,从start到结束就是3秒。不会因为从0.2到0.5就减少动画时间。

image.png

上面代码会阻塞至少2个线程。只是为了演示用。实际要用异步的方式。

    dispatch_group_t <span>group</span> = dispatch_group_create()<span>;</span>
    dispatch_queue_t <span>queue</span> = dispatch_queue_create(<span>0</span>,
                                                   DISPATCH_QUEUE_CONCURRENT)<span>;</span>
    
    __block NSData *image1<span>;</span>
    __block NSData *image2<span>;</span>
    
    dispatch_group_enter(group)<span>;</span>
    dispatch_async(queue,
 ^{
        NSString *<span>imageURL</span> = @<span>"https://img1.baidu.com/it/u=786478812,1495962150&fm=253&app=120&f=JPEG?w=800&h=1422"</span><span>;</span>
        <span>[[NSURLSession sharedSession]</span> dataTaskWithURL:<span>[NSURL URLWithString:imageURL]</span> completionHandler:^(NSData * _Nullable data,
                                                                                                         NSURLResponse * _Nullable response,
                                                                                                         NSError * _Nullable error) {
            <span>image1</span> = data          <span>;</span>
            dispatch_group_leave(group)<span>;</span>
        }]<span>;</span>
    })<span>;</span>
    dispatch_group_enter(group)<span>;</span>
    dispatch_async(queue,
 ^{
        NSString *<span>imageURL</span> = @<span>"https://b0.bdstatic.com/ugc/nZYH1k449ZTPOGj4-Mkb_we832996869186f05c0af4661f2cf074c.jpg"</span><span>;</span>
        <span>[[NSURLSession sharedSession]</span> dataTaskWithURL:<span>[NSURL URLWithString:imageURL]</span> completionHandler:^(NSData * _Nullable data,
                                                                                                         NSURLResponse * _Nullable response,
                                                                                                         NSError * _Nullable error) {
            <span>image1</span> = data          <span>;</span>
            dispatch_group_leave(group)<span>;</span>
        }]<span>;</span>
    })<span>;</span>
    
    dispatch_group_notify(group,
                          dispatch_get_main_queue(),
                          ^{
     

2. 关键属性解析
属性类型说明
`type`NSString动画效果名称。常用公开常量有 `kCATransitionFade` (淡入淡出)、`kCATransitionPush` (推挤)。
`subtype`NSString动画方向。仅对部分 type 有效(如 Push, MoveIn)。
`duration`CFTimeInterval动画持续时间。
`startProgress`float**起始进度**。默认为 0.0。设为 0.2 表示跳过前 20% 的动画过程。
`endProgress`float**结束进度**。默认为 1.0。设为 0.5 表示只播放到一半就强制结束。
`timingFunction`CAMediaTimingFunction节奏曲线,如 `kCAMediaTimingFunctionEaseInEaseOut`。
3. 避坑指南 (重要)
  • 内容变更时机addAnimation 只是告诉图层“准备做个动画”,真正的视觉变化依赖于你随后修改的属性(如 imagesubviews)。如果只加动画不改内容,什么都不会发生。
  • 私有 API 风险:截图中使用的 @"pageCurl" 等字符串属于 Apple 未公开的私有 API。虽然在很多 App 中能用且能过审,但理论上存在被拒风险。如果是上架应用,建议优先使用公开的 kCATransitionFade 或自定义转场。
  • start/endProgress 的妙用:这两个属性非常适合做交互式转场(Interactive Transition)。比如配合手势滑动,根据滑动的百分比动态修改这两个值,就能实现“跟手”的翻页效果。

常用动画类型速查表

  • 公开 (安全)

    • kCATransitionFade: 交叉淡入淡出。
    • kCATransitionPush: 新视图推入,旧视图移出。
    • kCATransitionMoveIn: 新视图覆盖在旧视图之上移入。
    • kCATransitionReveal: 旧视图移走,露出下方的新视图。
  • 私有 (炫酷但有风险)

    • @"pageCurl" / @"pageUnCurl": 翻页/合页。
    • @"rippleEffect": 水滴波纹。
    • @"suckEffect": 像被吸尘器吸走一样缩小消失。
    • @"oglFlip": 垂直翻转。