05-iOS 多媒体技术| Core Animation要点回顾2【UIKit动画、Core Animation动画、粒子动画、经典第三方动画库等】

前言

我们在前面,首先进行了针对 iOS中的多媒体技术相关几个框架概述:

  1. 进而 用 两篇文章 对 其中的 UIKit相关要点 进行了分述:
  2. 我们 在此篇文章 ,将 针对 Core Animation框架的要点 进一步展开分述:

一、 iOS 中 动画实现的几种方式

  • UIKit动画
    • 普通动画
    • block动画
    • 关键帧动画
  • Core Animation动画
    • CAAnimationGroup
    • CATransaction
    • CAPropertyAnimation
      • CAKeyframeAnimation
      • CABasicAnimation
        • CASpringAnimation
  • UIImageView帧动画
  • 经典第三方动画库

二、UIKit动画

1. 可动画属性:

UIView动画能够设置的动画属性有:

  • frame
  • bounds
  • center
  • transform
  • alpha
  • backgroundColor
  • contentStretch

UIView动画支持几种动画类型,一般 对View的可动画属性的修改添加动画,直接用 Block动画就足够了

2. 普通动画

开始动画语句:

objc 复制代码
// 第一个参数: 动画标识
// 第二个参数: 附加参数,在设置代理情况下,此参数将发送到setAnimationWillStartSelector和setAnimationDidStopSelector所指定的方法,大部分情况,设置为nil.
[UIView beginAnimations:(nullable NSString *) context:(nullable void *)];

结束动画语句:

objc 复制代码
[UIView commitAnimations];

动画参数的属性设置:

objc 复制代码
//动画持续时间
[UIView setAnimationDuration:(NSTimeInterval)];
//动画的代理对象 
[UIView setAnimationDelegate:(nullable id)];
//设置动画将开始时代理对象执行的SEL
[UIView setAnimationWillStartSelector:(nullable SEL)];
//设置动画延迟执行的时间
[UIView setAnimationDelay:(NSTimeInterval)];
//设置动画的重复次数
[UIView setAnimationRepeatCount:(float)];
//设置动画的曲线
/*
UIViewAnimationCurve的枚举值:
UIViewAnimationCurveEaseInOut,         // 慢进慢出(默认值)
UIViewAnimationCurveEaseIn,            // 慢进
UIViewAnimationCurveEaseOut,           // 慢出
UIViewAnimationCurveLinear             // 匀速
*/
[UIView setAnimationCurve:(UIViewAnimationCurve)];
//设置是否从当前状态开始播放动画
/*假设上一个动画正在播放,且尚未播放完毕,我们将要进行一个新的动画:
当为YES时:动画将从上一个动画所在的状态开始播放
当为NO时:动画将从上一个动画所指定的最终状态开始播放(此时上一个动画马上结束)*/
[UIView setAnimationBeginsFromCurrentState:YES];
//设置动画是否继续执行相反的动画
[UIView setAnimationRepeatAutoreverses:(BOOL)];
//是否禁用动画效果(对象属性依然会被改变,只是没有动画效果)
[UIView setAnimationsEnabled:(BOOL)];
//设置视图的过渡效果
/* 第一个参数:UIViewAnimationTransition的枚举值如下
     UIViewAnimationTransitionNone,              //不使用动画
     UIViewAnimationTransitionFlipFromLeft,      //从左向右旋转翻页
     UIViewAnimationTransitionFlipFromRight,     //从右向左旋转翻页
     UIViewAnimationTransitionCurlUp,            //从下往上卷曲翻页
     UIViewAnimationTransitionCurlDown,          //从上往下卷曲翻页
 第二个参数:需要过渡效果的View
 第三个参数:是否使用视图缓存,YES:视图在开始和结束时渲染一次;NO:视图在每一帧都渲染*/
[UIView setAnimationTransition:(UIViewAnimationTransition) forView:(nonnull UIView *) cache:(BOOL)];

更详细的API说明:

objc 复制代码
/** 动画的曲线枚举 */
typedef NS_ENUM(NSInteger, UIViewAnimationCurve) {
    UIViewAnimationCurveEaseInOut,  //!< 慢进慢出(默认值).
    UIViewAnimationCurveEaseIn,     //!< 慢进.
    UIViewAnimationCurveEaseOut,    //!< 慢出.
    UIViewAnimationCurveLinear,     //!< 匀速.
};

/** UIView动画过渡效果 */
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,          //!< 无效果.
    UIViewAnimationTransitionFlipFromLeft,  //!< 沿视图垂直中心轴左到右移动.
    UIViewAnimationTransitionFlipFromRight, //!< 沿视图垂直中心轴右到左移动.
    UIViewAnimationTransitionCurlUp,        //!< 由底部向上卷起.
    UIViewAnimationTransitionCurlDown,      //!< 由顶部向下展开.
};

@interface UIView(UIViewAnimation)
 
/** 开始动画 */
+ (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context;
/** 提交动画 */
+ (void)commitAnimations;
 
/** 设置动画代理, 默认nil */
+ (void)setAnimationDelegate:(nullable id)delegate;
/** 动画将要开始时执行方法(必须要先设置动画代理), 默认NULL */
+ (void)setAnimationWillStartSelector:(nullable SEL)selector;
/** 动画已结束时执行方法(必须要先设置动画代理), 默认NULL */
+ (void)setAnimationDidStopSelector:(nullable SEL)selector;
/** 设置动画时长, 默认0.2秒 */
+ (void)setAnimationDuration:(NSTimeInterval)duration;
/** 动画延迟执行时间, 默认0.0秒 */
+ (void)setAnimationDelay:(NSTimeInterval)delay;
/** 设置在动画块内部动画属性改变的开始时间, 默认now ([NSDate date]) */
+ (void)setAnimationStartDate:(NSDate *)startDate;
/** 设置动画曲线, 默认UIViewAnimationCurveEaseInOut */
+ (void)setAnimationCurve:(UIViewAnimationCurve)curve;
/** 动画的重复播放次数, 默认0 */
+ (void)setAnimationRepeatCount:(float)repeatCount;
/** 设置是否自定翻转当前的动画效果, 默认NO */
+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses;
/** 设置动画从当前状态开始播放, 默认NO */
+ (void)setAnimationBeginsFromCurrentState:(BOOL)fromCurrentState;
 
/** 在动画块中为视图设置过渡动画 */
+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;
 
/** 设置是否激活动画 */
+ (void)setAnimationsEnabled:(BOOL)enabled;
/** 返回一个布尔值表示动画是否结束 */
#if UIKIT_DEFINE_AS_PROPERTIES
@property(class, nonatomic, readonly) BOOL areAnimationsEnabled;
#else
+ (BOOL)areAnimationsEnabled;
#endif
/** 先检查动画当前是否启用,然后禁止动画,执行block内的方法,最后重新启用动画,而且这个方法不会阻塞基于CoreAnimation的动画 */
+ (void)performWithoutAnimation:(void (NS_NOESCAPE ^)(void))actionsWithoutAnimation NS_AVAILABLE_IOS(7_0);
 
/** 当前动画的持续时间 */
#if UIKIT_DEFINE_AS_PROPERTIES
@property(class, nonatomic, readonly) NSTimeInterval inheritedAnimationDuration NS_AVAILABLE_IOS(9_0);
#else
+ (NSTimeInterval)inheritedAnimationDuration NS_AVAILABLE_IOS(9_0);
#endif
 
@end
 

Demo示例1:

objc 复制代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    UITouch *tuch = touches.anyObject;
    CGPoint point = [tuch locationInView:self.view];
    
    [UIView beginAnimations:@"testAnimation" context:nil];
    [UIView setAnimationDuration:3.0];
    [UIView setAnimationDelegate:self];
    //设置动画将开始时代理对象执行的SEL
    [UIView setAnimationWillStartSelector:@selector(animationDoing)];
    
    //设置动画延迟执行的时间
    [UIView setAnimationDelay:0];
    
    [UIView setAnimationRepeatCount:MAXFLOAT];
    [UIView setAnimationCurve:UIViewAnimationCurveLinear];
    //设置动画是否继续执行相反的动画
    [UIView setAnimationRepeatAutoreverses:YES];
    self.redView.center = point;
    self.redView.transform = CGAffineTransformMakeScale(1.5, 1.5);
    self.redView.transform = CGAffineTransformMakeRotation(M_PI);
    
    [UIView commitAnimations];
}

Demo示例2:

objc 复制代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    UITouch *tuch = touches.anyObject;
    CGPoint point = [tuch locationInView:self.view];
    
    [UIView beginAnimations:@"testAnimation" context:nil];
    [UIView setAnimationDuration:3.0];
    [UIView setAnimationDelegate:self];
    //设置动画将开始时代理对象执行的SEL
    [UIView setAnimationWillStartSelector:@selector(animationDoing)];
    
    //设置动画延迟执行的时间
    [UIView setAnimationDelay:0];
    
    [UIView setAnimationRepeatCount:MAXFLOAT];
    [UIView setAnimationCurve:UIViewAnimationCurveLinear];
    //设置动画是否继续执行相反的动画
    [UIView setAnimationRepeatAutoreverses:YES];
    self.redView.center = point;
    self.redView.transform = CGAffineTransformMakeScale(1.5, 1.5);
    self.redView.transform = CGAffineTransformMakeRotation(M_PI);
    
    [UIView commitAnimations];
}

Demo示例3:

objc 复制代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    // 转成动画 (flip)
    [UIView beginAnimations:@"imageViewTranslation" context:nil];
    [UIView setAnimationDuration:2.0];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationWillStartSelector:@selector(startAnimation)];
    [UIView setAnimationDidStopSelector:@selector(stopAnimation)];
    [UIView setAnimationRepeatCount:1.0];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationRepeatAutoreverses:YES];
    [UIView setAnimationRepeatCount:MAXFLOAT];
    [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.imageView cache:YES];
    if (++count % 2 ==0) {
        self.imageView.image = [UIImage imageNamed:@"yh_detial_ty"];
    }else{
        self.imageView.image = [UIImage imageNamed:@"yh_detial_bz"];
    }
    [UIView commitAnimations];
    
}

3. block动画

iOS4.0以后增加了Block动画块,提供了更简洁的方式来实现动画.日常开发中一般也是使用Block形式创建动画。

最简洁的Block动画:包含时间动画:

objc 复制代码
[UIView animateWithDuration:(NSTimeInterval)  //动画持续时间
              animations:^{
              //执行的动画
 }];

带有动画提交回调的Block动画

objc 复制代码
 [UIView animateWithDuration:(NSTimeInterval)  //动画持续时间
              animations:^{
            //执行的动画
 }                completion:^(BOOL finished) {
            //动画执行提交后的操作
 }];

可以设置延时时间和过渡效果的Block动画

objc 复制代码
[UIView animateWithDuration:(NSTimeInterval) //动画持续时间
                   delay:(NSTimeInterval) //动画延迟执行的时间
                 options:(UIViewAnimationOptions) //动画的过渡效果
              animations:^{
               //执行的动画
 }                completion:^(BOOL finished) {
               //动画执行提交后的操作
 }];

UIViewAnimationOptions的枚举值如下,可组合使用:

objc 复制代码
 UIViewAnimationOptionLayoutSubviews            //进行动画时布局子控件
 UIViewAnimationOptionAllowUserInteraction      //进行动画时允许用户交互
 UIViewAnimationOptionBeginFromCurrentState     //从当前状态开始动画
 UIViewAnimationOptionRepeat                    //无限重复执行动画
 UIViewAnimationOptionAutoreverse               //执行动画回路
 UIViewAnimationOptionOverrideInheritedDuration //忽略嵌套动画的执行时间设置
 UIViewAnimationOptionOverrideInheritedCurve    //忽略嵌套动画的曲线设置
 UIViewAnimationOptionAllowAnimatedContent      //转场:进行动画时重绘视图
 UIViewAnimationOptionShowHideTransitionViews   //转场:移除(添加和移除图层的)动画效果
 UIViewAnimationOptionOverrideInheritedOptions  //不继承父动画设置

 UIViewAnimationOptionCurveEaseInOut            //时间曲线,慢进慢出(默认值)
 UIViewAnimationOptionCurveEaseIn               //时间曲线,慢进
 UIViewAnimationOptionCurveEaseOut              //时间曲线,慢出
 UIViewAnimationOptionCurveLinear               //时间曲线,匀速

 UIViewAnimationOptionTransitionNone            //转场,不使用动画
 UIViewAnimationOptionTransitionFlipFromLeft    //转场,从左向右旋转翻页
 UIViewAnimationOptionTransitionFlipFromRight   //转场,从右向左旋转翻页
 UIViewAnimationOptionTransitionCurlUp          //转场,下往上卷曲翻页
 UIViewAnimationOptionTransitionCurlDown        //转场,从上往下卷曲翻页
 UIViewAnimationOptionTransitionCrossDissolve   //转场,交叉消失和出现
 UIViewAnimationOptionTransitionFlipFromTop     //转场,从上向下旋转翻页
 UIViewAnimationOptionTransitionFlipFromBottom  //转场,从下向上旋转翻页

Spring动画

iOS7.0以后新增了Spring动画(IOS系统动画大部分采用Spring Animation, 适用所有可被添加动画效果的属性)

objc 复制代码
 [UIView animateWithDuration:(NSTimeInterval)//动画持续时间
                   delay:(NSTimeInterval)//动画延迟执行的时间
  usingSpringWithDamping:(CGFloat)//震动效果,范围0~1,数值越小震动效果越明显
   initialSpringVelocity:(CGFloat)//初始速度,数值越大初始速度越快
                 options:(UIViewAnimationOptions)//动画的过渡效果
              animations:^{
                 //执行的动画
 }
                  completion:^(BOOL finished) {
                 //动画执行提交后的操作
 }];

更详细的API说明:

objc 复制代码
/** UIView动画选项 */
typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions) {
    UIViewAnimationOptionLayoutSubviews            = 1 <<  0, //!< 动画过程中保证子视图跟随运动.
    UIViewAnimationOptionAllowUserInteraction      = 1 <<  1, //!< 动画过程中允许用户交互.
    UIViewAnimationOptionBeginFromCurrentState     = 1 <<  2, //!< 所有视图从当前状态开始运行.
    UIViewAnimationOptionRepeat                    = 1 <<  3, //!< 重复运行动画.
    UIViewAnimationOptionAutoreverse               = 1 <<  4, //!< 动画运行到结束点后仍然以动画方式回到初始点.
    UIViewAnimationOptionOverrideInheritedDuration = 1 <<  5, //!< 忽略嵌套动画时间设置.
    UIViewAnimationOptionOverrideInheritedCurve    = 1 <<  6, //!< 忽略嵌套动画速度设置.
    UIViewAnimationOptionAllowAnimatedContent      = 1 <<  7, //!< 动画过程中重绘视图(注意仅仅适用于转场动画).
    UIViewAnimationOptionShowHideTransitionViews   = 1 <<  8, //!< 视图切换时直接隐藏旧视图、显示新视图,而不是将旧视图从父视图移除(仅仅适用于转场动画).
    UIViewAnimationOptionOverrideInheritedOptions  = 1 <<  9, //!< 不继承父动画设置或动画类型.
    
    UIViewAnimationOptionCurveEaseInOut            = 0 << 16, //!< 动画先缓慢,然后逐渐加速.
    UIViewAnimationOptionCurveEaseIn               = 1 << 16, //!< 动画逐渐变慢.
    UIViewAnimationOptionCurveEaseOut              = 2 << 16, //!< 动画逐渐加速.
    UIViewAnimationOptionCurveLinear               = 3 << 16, //!< 动画匀速执行,默认值.
    
    UIViewAnimationOptionTransitionNone            = 0 << 20, //!< 没有转场动画效果.
    UIViewAnimationOptionTransitionFlipFromLeft    = 1 << 20, //!< 从左侧翻转效果.
    UIViewAnimationOptionTransitionFlipFromRight   = 2 << 20, //!< 从右侧翻转效果.
    UIViewAnimationOptionTransitionCurlUp          = 3 << 20, //!< 向后翻页的动画过渡效果.
    UIViewAnimationOptionTransitionCurlDown        = 4 << 20, //!< 向前翻页的动画过渡效果.
    UIViewAnimationOptionTransitionCrossDissolve   = 5 << 20, //!< 旧视图溶解消失显示下一个新视图的效果.
    UIViewAnimationOptionTransitionFlipFromTop     = 6 << 20, //!< 从上方翻转效果.
    UIViewAnimationOptionTransitionFlipFromBottom  = 7 << 20, //!< 从底部翻转效果.
    
    UIViewAnimationOptionPreferredFramesPerSecondDefault     = 0 << 24, //!< 默认的帧每秒.
    UIViewAnimationOptionPreferredFramesPerSecond60          = 3 << 24, //!< 60帧每秒的帧速率.
    UIViewAnimationOptionPreferredFramesPerSecond30          = 7 << 24, //!< 30帧每秒的帧速率.
    
} NS_ENUM_AVAILABLE_IOS(4_0);

 
@interface UIView(UIViewAnimationWithBlocks)
 
/** 用于对一个或多个视图的改变的持续时间、延时、选项动画完成时的操作 */
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);
 
/** 用于对一个或多个视图的改变的持续时间、选项动画完成时的操作,默认:delay = 0.0, options = 0 */
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);
 
/** 用于对一个或多个视图的改变的持续时间内动画完成时的操作,默认:delay = 0.0, options = 0, completion = NULL */
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations NS_AVAILABLE_IOS(4_0);
 
/** 使用与物理弹簧运动相对应的定时曲线执行视图动画 */
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
 
/** 为指定的容器视图创建转换动画 */
+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);
 
/** 使用给定的参数在指定视图之间创建转换动画 */
+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // toView added to fromView.superview, fromView removed from its superview
 
/** 在一个或多个视图上执行指定的系统提供的动画,以及定义的可选并行动画 */
+ (void)performSystemAnimation:(UISystemAnimation)animation onViews:(NSArray<__kindof UIView *> *)views options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))parallelAnimations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
 
@end

Demo示例1

objc 复制代码
[UIView animateWithDuration:3.0 animations:^{
        
        self.redView.center = point;
        self.redView.transform = CGAffineTransformMakeScale(1.5, 1.5);
        self.redView.transform = CGAffineTransformMakeRotation(M_PI);
    } completion:^(BOOL finished) {
        [UIView animateWithDuration:2.0 animations:^{
            self.redView.frame = CGRectMake(100, 100, 100, 100);
            self.redView.transform = CGAffineTransformMakeScale(1 / 1.5,1 / 1.5);
            self.redView.transform = CGAffineTransformMakeRotation(M_PI);
        }];
    }];

Demo示例2

objc 复制代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    self.redView.alpha = 0;
/*
animateWithDuration 动画持续时间
delay 动画延迟执行的时间
usingSpringWithDamping 震动效果,范围0~1,数值越小震动效果越明显
initialSpringVelocity 初始速度,数值越大初始速度越快
options 动画的过渡效果
*/
    [UIView animateWithDuration:3.0  delay:1.0 usingSpringWithDamping:0.3 initialSpringVelocity:1 options:UIViewAnimationOptionAllowUserInteraction animations:^{
        self.redView.alpha = 1.0;
        self.redView.frame = CGRectMake(200, 350, 140, 140);
    } completion:^(BOOL finished) {
        [self.redView removeFromSuperview];
    }];
}

4. 关键帧动画

IOS7.0后新增了关键帧动画,支持属性关键帧,不支持路径关键帧

objc 复制代码
 [UIView animateKeyframesWithDuration:(NSTimeInterval)//动画持续时间
                            delay:(NSTimeInterval)//动画延迟执行的时间
                          options:(UIViewKeyframeAnimationOptions)//动画的过渡效果
                       animations:^{
                     //执行的关键帧动画
 }
                       completion:^(BOOL finished) {
                     //动画执行提交后的操作
 }];

UIViewKeyframeAnimationOptions的枚举值如下,可组合使用:

objc 复制代码
UIViewAnimationOptionLayoutSubviews           //进行动画时布局子控件
UIViewAnimationOptionAllowUserInteraction     //进行动画时允许用户交互
UIViewAnimationOptionBeginFromCurrentState    //从当前状态开始动画
UIViewAnimationOptionRepeat                   //无限重复执行动画
UIViewAnimationOptionAutoreverse              //执行动画回路
UIViewAnimationOptionOverrideInheritedDuration //忽略嵌套动画的执行时间设置
UIViewAnimationOptionOverrideInheritedOptions //不继承父动画设置

UIViewKeyframeAnimationOptionCalculationModeLinear     //运算模式 :连续
UIViewKeyframeAnimationOptionCalculationModeDiscrete   //运算模式 :离散
UIViewKeyframeAnimationOptionCalculationModePaced      //运算模式 :均匀执行
UIViewKeyframeAnimationOptionCalculationModeCubic      //运算模式 :平滑
UIViewKeyframeAnimationOptionCalculationModeCubicPaced //运算模式 :平滑均匀

各种运算模式的直观比较如下图:

增加关键帧方法:

objc 复制代码
[UIView addKeyframeWithRelativeStartTime:(double)//动画开始的时间(占总时间的比例)
                     relativeDuration:(double) //动画持续时间(占总时间的比例)
                           animations:^{
                         //执行的动画
 }];

转场动画:

a.从旧视图到新视图的动画效果

objc 复制代码
[UIView transitionFromView:(nonnull UIView *) toView:(nonnull UIView *) duration:(NSTimeInterval) options:(UIViewAnimationOptions) completion:^(BOOL finished) {
                 //动画执行提交后的操作
 }];

在该动画过程中,fromView 会从父视图中移除,并将 toView 添加到父视图中,注意转场动画的作用对象是父视图(过渡效果体现在父视图上)。调用该方法相当于执行下面两句代码:

objc 复制代码
[fromView.superview addSubview:toView];
[fromView removeFromSuperview];

单个视图的过渡效果

objc 复制代码
[UIView transitionWithView:(nonnull UIView *)
               duration:(NSTimeInterval)
                options:(UIViewAnimationOptions)
             animations:^{
             //执行的动画
 }
             completion:^(BOOL finished) {
             //动画执行提交后的操作
 }];

更详细的API说明:

objc 复制代码
typedef NS_OPTIONS(NSUInteger, UIViewKeyframeAnimationOptions) {
    UIViewKeyframeAnimationOptionLayoutSubviews            = UIViewAnimationOptionLayoutSubviews, //!< 动画过程中保证子视图跟随运动.
    UIViewKeyframeAnimationOptionAllowUserInteraction      = UIViewAnimationOptionAllowUserInteraction, //!< 动画过程中允许用户交互.
    UIViewKeyframeAnimationOptionBeginFromCurrentState     = UIViewAnimationOptionBeginFromCurrentState, //!< 所有视图从当前状态开始运行.
    UIViewKeyframeAnimationOptionRepeat                    = UIViewAnimationOptionRepeat, //!< 重复运行动画.
    UIViewKeyframeAnimationOptionAutoreverse               = UIViewAnimationOptionAutoreverse, //!< 动画运行到结束点后仍然以动画方式回到初始点.
    UIViewKeyframeAnimationOptionOverrideInheritedDuration = UIViewAnimationOptionOverrideInheritedDuration, //!< 忽略嵌套动画时间设置.
    UIViewKeyframeAnimationOptionOverrideInheritedOptions  = UIViewAnimationOptionOverrideInheritedOptions, //!< 不继承父动画设置或动画类型.
    
    UIViewKeyframeAnimationOptionCalculationModeLinear     = 0 << 10, //!< 连续运算模式, 默认.
    UIViewKeyframeAnimationOptionCalculationModeDiscrete   = 1 << 10, //!< 离散运算模式.
    UIViewKeyframeAnimationOptionCalculationModePaced      = 2 << 10, //!< 均匀执行运算模式.
    UIViewKeyframeAnimationOptionCalculationModeCubic      = 3 << 10, //!< 平滑运算模式.
    UIViewKeyframeAnimationOptionCalculationModeCubicPaced = 4 << 10  //!< 平滑均匀运算模式.
} NS_ENUM_AVAILABLE_IOS(7_0);

/** UIView的关键帧动画 */
@interface UIView (UIViewKeyframeAnimations)
 
/** 创建一个动画块对象,可用于为当前视图设置基于关键帧的动画 */
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
/** 添加指定开始时间、持续时间的关键帧动画(起始和持续时间是0.0和1.0之间的值) */
+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations NS_AVAILABLE_IOS(7_0);
 
@end

三、Core Animation动画

说到核心动画,那就不得不先说下 CALayer

  • 在iOS系统中,你能看得见摸得着的东西基本上都是UIView,比如一个按钮、一个文本标签、一个文本输入框、一个图标等等,这些都是UIView。
  • 其实UIView之所以能显示在屏幕上,完全是因为它内部的一个layer对象。
  • 在创建UIView对象时,UIView内部会自动创建一个层(即CALayer对象),通过UIView的layer属性可以访问这个层。当UIView需要显示到屏幕上时,会调用drawRect:方法进行绘图,并且会将所有内容绘制在自己的层上,绘图完毕后,系统会将层拷贝到屏幕上,于是就完成了UIView的显示。
  • 换句话说,UIView本身不具备显示的功能,是它内部的层才有显示功能。

上面已经说过了,UIView之所以能够显示,完全是因为内部的CALayer对象。因此,通过操作这个CALayer对象,可以很方便地调整UIView的一些界面属性,比如: 阴影、圆角大小、边框宽度和颜色等。

1. CALayer的属性回顾

objc 复制代码
//下面是CALayer的一些属性介绍
//宽度和高度
@property CGRect bounds;

//位置(默认指中点,具体由anchorPoint决定)
@property CGPoint position;

//锚点(x,y的范围都是0-1),决定了position的含义
@property CGPoint anchorPoint;

//背景颜色(CGColorRef类型)
@propertyCGColorRefbackgroundColor;

//形变属性
@property CATransform3D transform;

//边框颜色(CGColorRef类型)
@property  CGColorRef  borderColor;

//边框宽度
@property CGFloat borderWidth;

//圆角半径
@property CGFloat cornerRadius;

//内容(比如设置为图片CGImageRef)
@property(retain) id contents;

2. 给CALayercontents赋值

说明:可以通过设置contents属性给UIView设置背景图片,注意必须是CGImage才能显示,我们可以在UIImage对象后面加上.CGImage直接转换,转换之后还需要在前面加上(id)进行强转。

objc 复制代码
// 跨框架赋值需要进行桥接
self.view.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"123"].CGImage); 

值得注意的是,UIView的CALayer对象(层)通过layer属性可以访问这个层。要注意的是,这个默认的层不允许重新创建,但可以往层里面添加子层。UIView可以通过addSubview:方法添加子视图,类似地,CALayer可以通过addSublayer:方法添加子层

3. position和anchorPoint

CALayer对象有两个比较重要的属性,那就是position和anchorPoint。

  • position和anchorPoint属性都是CGPoint类型的
  • position可以用来设置CALayer在父层中的位置,它是以父层的左上角为坐标原点(0, 0)
  • anchorPoint称为"锚点",它决定着CALayer身上的哪个点会在position属性所指的位置。它的x、y取值范围都是0~1,默认值为(0.5, 0.5)
  1. 创建一个CALayer,添加到控制器的view的layer中

    objc 复制代码
     CALayer *myLayer = [CALayer layer];
    // 设置层的宽度和高度(100x100)
    myLayer.bounds = CGRectMake(0, 0, 100, 100);
    // 设置层的位置
    myLayer.position = CGPointMake(100, 100);
    // 设置层的背景颜色:红色
    myLayer.backgroundColor = [UIColor redColor].CGColor;
    
    // 添加myLayer到控制器的view的layer中
    [self.view.layer addSublayer:myLayer];

    第5行设置了myLayer的position为(100, 100),又因为anchorPoint默认是(0.5, 0.5),所以最后的效果是:myLayer的中点会在父层的(100, 100)位置

注意,蓝色线是我自己加上去的,方便大家理解,并不是默认的显示效果。两条蓝色线的宽度均为100。

  1. 若将anchorPoint改为(0, 0),myLayer的左上角会在(100, 100)位置
    myLayer.anchorPoint = CGPointMake(0, 0);

  2. 若将anchorPoint改为(1, 1),myLayer的右下角会在(100, 100)位置
    myLayer.anchorPoint = CGPointMake(1, 1);

  3. 将anchorPoint改为(0, 1),myLayer的左下角会在(100, 100)位置
    myLayer.anchorPoint = CGPointMake(0, 1);

我想,你应该已经大概明白anchorPoint的用途了吧,它决定着CALayer身上的哪个点会在position所指定的位置上。它的x、y取值范围都是0~1,默认值为(0.5, 0.5),因此,默认情况下,CALayer的中点会在position所指定的位置上。当anchorPoint为其他值时,以此类推。
anchorPoint是视图的中心点,position是视图的位置,位置会和中心点重叠。所以我们在开发中可以通过修改视图的layer.anchorPoint或者layer.position实现特定的动画效果。

下面举个两个例子: 两份代码,上面那个是anchorPoint为(0.5, 0.5)也就是默认情况下,下面那个是(0, 0)。

代码如下:

objc 复制代码
self.redView.layer.anchorPoint = CGPointMake(0.5, 0.5);
[UIView animateWithDuration:3.0 animations:^{
    self.redView.transform = CGAffineTransformMakeRotation(M_PI);
} completion:^(BOOL finished) {

}];

代码如下:

objc 复制代码
self.redView.layer.anchorPoint = CGPointMake(0, 0);
    [UIView animateWithDuration:3.0 animations:^{
        
        self.redView.transform = CGAffineTransformMakeRotation(M_PI);
    } completion:^(BOOL finished) {
        
    }];

4. CATransaction事务类|隐式动画

注意 CATransaction 不是 CATransition 根层与非根层:

  • 每一个UIView内部都默认关联着一个CALayer,我们可以称这个Layer为Root Layer(根层)
  • 所有的非Root Layer,也就是手动创建的CALayer对象,都存在着隐式动画

当对非Root Layer的部分属性进行修改时,默认会自动产生一些动画效果,而这些属性称为Animatable Properties(可动画属性)。

常见的几个可动画属性:

objc 复制代码
bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画
backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画
position:用于设置CALayer的位置。修改这个属性会产生平移动画
borderColor:边框颜色
opacity:不透明度

可以通过事务关闭隐式动画:

objc 复制代码
[CATransaction begin];
// 关闭隐式动画
[CATransaction setDisableActions:YES];

self.myview.layer.position = CGPointMake(10, 10);
[CATransaction commit];
  • CATransaction事务类可以对多个layer的属性同时进行修改,它分隐式事务显式事务
  • 当我们向图层添加显式或隐式动画时,Core Animation都会自动创建隐式事务。
  • 但是,我们还可以创建显式事务以更精确地管理这些动画。
  • 区分隐式动画和隐式事务:
    隐式动画通过隐式事务实现动画 。
  • 区分显式动画和显式事务:
    显式动画有多种实现方式,显式事务是一种实现显式动画的方式。
  • 除显式事务外,任何对于CALayer属性的修改,都是隐式事务.

隐式事务

swift 复制代码
//创建layer
let layer = CALayer()
layer.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
layer.position = CGPoint(x: 100, y: 350)
layer.backgroundColor = UIColor.red.cgColor
layer.borderColor = UIColor.black.cgColor
layer.opacity = 1.0
view.layer.addSublayer(layer)

//触发动画

// 设置变化动画过程是否显示,默认为true不显示
CATransaction.setDisableActions(false)
layer.cornerRadius = (layer.cornerRadius == 0.0) ? 30.0 : 0.0
layer.opacity = (layer.opacity == 1.0) ? 0.5 : 1.0

显式事务:通过明确的调用begin,commit来提交动画

swift 复制代码
CATransaction.begin()
layer.zPosition = 200.0
layer.opacity = 0.0
CATransaction.commit()

使用事务CATransaction的主要原因:

  • 在显式事务的范围内,我们可以更改持续时间,计时功能和其他参数。
  • 还可以为整个事务分配完成块,以便在动画组完成时通知应用。

例如,将动画的默认持续时间更改为8秒,使用setValue:forKey:方法进行修改,目前支持的属性包括: "animationDuration", "animationTimingFunction","completionBlock", "disableActions".

swift 复制代码
CATransaction.begin()
CATransaction.setValue(8.0, forKey: "animationDuration")
//执行动画
CATransaction.commit()

嵌套事务:

  • 当我们要为不同动画集提供不同默认值的情况下可以使用嵌套事务
  • 要将一个事务嵌套在另一个事务中,只需再次调用begin,且每个begin调用必须一一对应一个commit方法。
  • 只有在为最外层事务提交更改后,Core Animation才会开始关联的动画。

嵌套显式事务代码

swift 复制代码
//事务嵌套
CATransaction.begin()   // 外部transaction
CATransaction.setValue(2.0, forKey: "animationDuration")
layer.position = CGPoint(x: 140, y: 140)
        
CATransaction.begin()   // 内部transaction
CATransaction.setValue(5.0, forKey: "animationDuration")
layer.zPosition = 200.0
layer.opacity = 0.0
        
CATransaction.commit()  // 内部transaction
CATransaction.commit()  // 外部transaction

5. Core Animation动画简介

  • Core Animation可以用在Mac OS X和iOS平台。
  • Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。
  • 要注意的是,Core Animation是直接作用在CALayer上的,并非UIView。
  • 乔帮主在2007年的WWDC大会上亲自为你演示Core Animation的强大:点击查看视频

6. 核心动画开发步骤

  1. 使用它需要先添加QuartzCore.framework框架和引入主头文件<QuartzCore/QuartzCore.h> (如果是xcode5之前的版本,使用它需要先添加QuartzCore.framework和引入对应的框架<QuartzCore/QuartzCore.h>)
  2. 初始化一个CAAnimation对象,并设置一些动画相关属性
  3. 通过调用CALayer的addAnimation:forKey:方法增加CAAnimation对象到CALayer中,这样就能开始执行动画了
  4. 通过调用CALayer的removeAnimationForKey:方法可以停止CALayer中的动画

7. CAAnimation------所有动画对象的父类

是所有动画对象的父类,负责控制动画的持续时间和速度,是个抽象类,不能直接使用,应该使用它具体的子类

属性说明:(带*号代表来自CAMediaTiming协议的属性)

  • *duration:动画的持续时间
  • *repeatCount:重复次数,无限循环可以设置HUGE_VALF或者MAXFLOAT
  • *repeatDuration:重复时间
  • removedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards
  • *fillMode:决定当前对象在非active时间段的行为。比如动画开始之前或者动画结束之后
  • *beginTime:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间
  • timingFunction:速度控制函数,控制动画运行的节奏
  • delegate:动画代理

8. CAAnimation------动画填充模式

  • fillMode属性值(要想fillMode有效,最好设置removedOnCompletion = NO)
  • kCAFillModeRemoved 这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态
  • kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态
  • kCAFillModeBackwards 在动画开始前,只需要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始。
  • kCAFillModeBoth 这个其实就是上面两个的合成.动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态

9. CAAnimation------速度控制函数

速度控制函数(CAMediaTimingFunction)

  • kCAMediaTimingFunctionLinear(线性):匀速,给你一个相对静态的感觉
  • kCAMediaTimingFunctionEaseIn(渐进):动画缓慢进入,然后加速离开
  • kCAMediaTimingFunctionEaseOut(渐出):动画全速进入,然后减速的到达目的地
  • kCAMediaTimingFunctionEaseInEaseOut(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。这个是默认的动画行为。

设置动画的执行节奏

anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];

10. CAAnimation------动画代理方法

CAAnimation在分类中定义了代理方法,是给NSObject添加的分类,所以任何对象,成为CAAnimation的代理都可以

objc 复制代码
@interface NSObject (CAAnimationDelegate)

/* Called when the animation begins its active duration. */
动画开始的时候调用
- (void)animationDidStart:(CAAnimation *)anim;
动画停止的时候调用
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;

@end

11. CALayer上动画的暂停和恢复

objc 复制代码
#pragma mark 暂停CALayer的动画
-(void)pauseLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
    //让CALayer的时间停止走动
    layer.speed = 0.0;
    //让CALayer的时间停留在pausedTime这个时刻
    layer.timeOffset = pausedTime;
}

12. CALayer上动画的恢复

objc 复制代码
#pragma mark 恢复CALayer的动画

-(void)resumeLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = layer.timeOffset;
    // 1. 让CALayer的时间继续行走
    layer.speed = 1.0;
    // 2. 取消上次记录的停留时刻
    layer.timeOffset = 0.0;
    // 3. 取消上次设置的时间
    layer.beginTime = 0.0;
    // 4. 计算暂停的时间(这里也可以用CACurrentMediaTime()-pausedTime)
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    // 5. 设置相对于父坐标系的开始时间(往后退timeSincePause)
    layer.beginTime = timeSincePause;
}

13. CAPropertyAnimation

是CAAnimation的子类,也是个抽象类,要想创建动画对象,应该使用它的两个子类:

  • CABasicAnimation
  • CAKeyframeAnimation

属性说明:

keyPath:通过指定CALayer的一个属性名称为keyPath(NSString类型),并且对CALayer的这个属性的值进行修改,达到相应的动画效果。比如,指定@"position"为keyPath,就修改CALayer的position属性的值,以达到平移的动画效果

14. CABasicAnimation------基本动画

基本动画,是CAPropertyAnimation的子类

属性说明:

  • keyPath:要改变的属性名称(传字符串)
  • fromValue:keyPath相应属性的初始值
  • toValue:keyPath相应属性的结束值

动画过程说明:

  • 随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值从fromValue渐渐地变为toValue
  • keyPath内容是CALayer的可动画Animatable属性

如果fillMode=kCAFillModeForwards同时removedOnComletion=NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。

objc 复制代码
    //创建动画
    CABasicAnimation *anim = [CABasicAnimation animation];;
    //    设置动画对象
    // keyPath决定了执行怎样的动画,调用layer的哪个属性来执行动画
    //     position:平移
    anim.keyPath = @"position";
    //    包装成对象
    anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];;
    anim.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 300)];
    anim.duration = 2.0;
    
    //    让图层保持动画执行完毕后的状态
    //    执行完毕以后不要删除动画
    anim.removedOnCompletion = NO;
    //    保持最新的状态
    anim.fillMode = kCAFillModeForwards;
    
    //    添加动画
    [self.layer addAnimation:anim forKey:nil];

举个例子: 代码如下:

objc 复制代码
//创建动画对象
    CABasicAnimation *anim = [CABasicAnimation animation];
    
    //设置动画属性
    anim.keyPath = @"position.y";
    anim.toValue = @300;
    
    //动画提交时,会自动删除动画
    anim.removedOnCompletion = NO;
    //设置动画最后保持状态
    anim.fillMode = kCAFillModeForwards;
    
    //添加动画对象
    [self.redView.layer addAnimation:anim forKey:nil];

15. CAKeyframeAnimation------关键帧动画

关键帧动画,也是CAPropertyAnimation的子类,与CABasicAnimation的区别是:

  • CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue)
  • 而CAKeyframeAnimation会使用一个NSArray保存这些数值

属性说明:

  • values: 上述的NSArray对象。里面的元素称为"关键帧"(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
  • path: 代表路径可以设置一个CGPathRef、CGMutablePathRef,让图层按照路径轨迹移动。path只对CALayer的

anchorPoint和position起作用。如果设置了path,那么values将被忽略

  • keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧。如果没有设置keyTimes,各个关键帧的时间是平分的

CABasicAnimation可看做是只有2个关键帧的CAKeyframeAnimation

objc 复制代码
//    创建动画
  CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];;
//    设置动画对象
//  keyPath决定了执行怎样的动画,调整哪个属性来执行动画
  anim.keyPath = @"position";
  NSValue *v1 = [NSValue valueWithCGPoint:CGPointMake(100, 0)];
  NSValue *v2 = [NSValue valueWithCGPoint:CGPointMake(200, 0)];
  NSValue *v3 = [NSValue valueWithCGPoint:CGPointMake(300, 0)];
  NSValue *v4 = [NSValue valueWithCGPoint:CGPointMake(400, 0)];
  
  anim.values = @[v1,v2,v3,v4];
  anim.duration = 2.0;
//    让图层保持动画执行完毕后的状态
//    状态执行完毕后不要删除动画
  anim.removedOnCompletion = NO;
//    保持最新的状态
  anim.fillMode = kCAFillModeForwards;
  
//    添加动画
  [self.layer addAnimation:anim forKey:nil];
//  根据路径创建动画
//    创建动画
  CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];;

  anim.keyPath = @"position";
  anim.removedOnCompletion = NO;
  anim.fillMode = kCAFillModeForwards;
  anim.duration = 2.0;
  
//    创建一个路径
  CGMutablePathRef path = CGPathCreateMutable();
//    路径的范围
  CGPathAddEllipseInRect(path, NULL, CGRectMake(100, 100, 200, 200));
//    添加路径
  anim.path = path;
//    释放路径(带Create的函数创建的对象都需要手动释放,否则会内存泄露)
  CGPathRelease(path);
//    添加到View的layer
  [self.redView.layer addAnimation:anim forKey];

举个例子:

代码如下:

objc 复制代码
//帧动画
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    anim.keyPath = @"transform.rotation";
    anim.values = @[@(angle2Radio(-5)),@(angle2Radio(5)),@(angle2Radio(-5))];
    
    anim.repeatCount = MAXFLOAT;
    
    //自动反转
    //anim.autoreverses = YES;
    
    [self.imageV.layer addAnimation:anim forKey:nil];

再举个例子:

代码如下:

objc 复制代码
#import "ViewController.h"

@interface ViewController ()

/** 注释*/
@property (nonatomic ,weak) CALayer *fistLayer;

@property (strong, nonatomic)  NSMutableArray *imageArray;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //设置背景
    self.view.layer.contents = (id)[UIImage imageNamed:@"bg"].CGImage;
    
    CALayer *fistLayer = [CALayer layer];
    fistLayer.frame = CGRectMake(100, 288, 89, 40);
    //fistLayer.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:fistLayer];
    self.fistLayer = fistLayer;
    
    //fistLayer.transform = CATransform3DMakeRotation(M_PI, 0, 0, 1);
    
    
    //加载图片
    NSMutableArray *imageArray = [NSMutableArray array];
    for (int i = 0; i < 10; i++) {
       UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"fish%d",i]];
        [imageArray addObject:image];
    }
    self.imageArray = imageArray;
    //添加定时器
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(update) userInfo:nil repeats:YES];
    
    //添加动画
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    anim.keyPath = @"position";
    

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(100, 200)];
    [path addLineToPoint:CGPointMake(350, 200)];
    [path addLineToPoint:CGPointMake(350, 500)];
    [path addQuadCurveToPoint:CGPointMake(100, 200) controlPoint:CGPointMake(150, 700)];
    
    //传入路径
    anim.path = path.CGPath;
    
    anim.duration  = 5;
    
    anim.repeatCount = MAXFLOAT;
    
    anim.calculationMode = @"cubicPaced";
    
    anim.rotationMode = @"autoReverse";
    
    [fistLayer addAnimation:anim forKey:nil];

}

static int _imageIndex = 0;
- (void)update {
    //从数组当中取出图片
    UIImage *image = self.imageArray[_imageIndex];
    self.fistLayer.contents = (id)image.CGImage;
    _imageIndex++;
    if (_imageIndex > 9) {
        _imageIndex = 0;
    }
}

@end

16. 转场动画------CATransition

CATransition是CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点

UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果

动画属性:(有的属性是具备方向的,详情看下图)

type:动画过渡类型

subtype:动画过渡方向

startProgress:动画起点(在整体动画的百分比)

endProgress:动画终点(在整体动画的百分比)

objc 复制代码
    CATransition *anim = [CATransition animation];
    转场类型
    anim.type = @"cube";
    动画执行时间
    anim.duration = 0.5;
    动画执行方向
    anim.subtype = kCATransitionFromLeft;
    添加到View的layer
    [self.redView.layer addAnimation:anim forKey];

举个例子:

objc 复制代码
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageV;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
 
    self.imageV.userInteractionEnabled = YES;
    //添加手势
    UISwipeGestureRecognizer *leftSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
    leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
    [self.imageV addGestureRecognizer:leftSwipe];
    
    UISwipeGestureRecognizer *rightSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
    
    rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
    [self.imageV addGestureRecognizer:rightSwipe];
    
}

static int _imageIndex = 0;
- (void)swipe:(UISwipeGestureRecognizer *)swipe {
    
    
    //转场代码与转场动画必须得在同一个方法当中.
    NSString *dir = nil;
    if (swipe.direction == UISwipeGestureRecognizerDirectionLeft) {
        
        _imageIndex++;
        if (_imageIndex > 4) {
            _imageIndex = 0;
        }
        NSString *imageName = [NSString stringWithFormat:@"%d",_imageIndex];
        self.imageV.image = [UIImage imageNamed:imageName];
        
        dir = @"fromRight";
    }else if (swipe.direction == UISwipeGestureRecognizerDirectionRight) {

        _imageIndex--;
        if (_imageIndex < 0) {
            _imageIndex = 4;
        }
        NSString *imageName = [NSString stringWithFormat:@"%d",_imageIndex];
        self.imageV.image = [UIImage imageNamed:imageName];
        
        dir = @"fromLeft";
    }
    
    //添加动画
    CATransition *anim = [CATransition animation];
    //设置转场类型
    anim.type = @"cube";
    //设置转场的方向
    anim.subtype = dir;
    
    anim.duration = 0.5;
    //动画从哪个点开始
    //    anim.startProgress = 0.2;
    //    anim.endProgress = 0.3;
    
    [self.imageV.layer addAnimation:anim forKey:nil];
        
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

17. CAAnimationGroup------动画组

动画组,是CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行

属性说明:

animations:用来保存一组动画对象的NSArray

默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间

objc 复制代码
    CAAnimationGroup *group = [CAAnimationGroup animation];

//    创建旋转动画对象
    CABasicAnimation *retate = [CABasicAnimation animation];
//    layer的旋转属性
    retate.keyPath = @"transform.rotation";
//    角度
    retate.toValue = @(M_PI);
    
//    创建缩放动画对象
    CABasicAnimation *scale = [CABasicAnimation animation];
//    缩放属性
    scale.keyPath = @"transform.scale";
//    缩放比例
    scale.toValue = @(0.0);
//    添加到动画组当中
    group.animations = @[retate,scale];
//           执行动画时间
    group.duration = 2.0;
//    执行完以后不要删除动画
    group.removedOnCompletion = NO;
//          保持最新的状态
    group.fillMode = kCAFillModeForwards;

    [self.view.layer addAnimation:group forKey:nil];

举个🌰:

CAAnimationGroup.gif

代码如下:

objc 复制代码
#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *redView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    //移动
    CABasicAnimation *anim = [CABasicAnimation animation];
    anim.keyPath =  @"position.y";
    anim.toValue = @500;
//    anim.removedOnCompletion = NO;
//    anim.fillMode = kCAFillModeForwards;
//    [self.redView.layer addAnimation:anim forKey:nil];
//    
    //缩放
    CABasicAnimation *anim2 = [CABasicAnimation animation];
    anim2.keyPath =  @"transform.scale";
    anim2.toValue = @0.5;
//    anim2.removedOnCompletion = NO;
//    anim2.fillMode = kCAFillModeForwards;
//    [self.redView.layer addAnimation:anim2 forKey:nil];

    CAAnimationGroup *groupAnim = [CAAnimationGroup animation];
    //会执行数组当中每一个动画对象
    groupAnim.animations = @[anim,anim2];
    groupAnim.removedOnCompletion = NO;
    groupAnim.fillMode = kCAFillModeForwards;
    [self.redView.layer addAnimation:groupAnim forKey:nil];
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

三大动画:(不需要交互的时候可以选择以下动画)
CAAnimationGroup------动画组
CAKeyframeAnimation------关键帧动画
转场动画------CATransition

objc 复制代码
//参数说明:
 duration:动画的持续时间
 view:需要进行转场动画的视图
 options:转场动画的类型
 animations:将改变视图属性的代码放在这个block中
 completion:动画结束后,会自动调用这个block
 + (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;

使用UIView动画函数实现转场动画------双视图

objc 复制代码
参数说明:
duration:动画的持续时间
options:转场动画的类型
animations:将改变视图属性的代码放在这个block中
completion:动画结束后,会自动调用这个block

+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion;

转场动画

1.创建转场动画:[CATransition animation];

2.设置动画属性值

3.添加到需要专场动画的图层上 [layer addAimation:animation forKer:nil];

objc 复制代码
转场动画的类型(NSString *type)
fade : 交叉淡化过渡
push : 新视图把旧视图推出去
moveIn: 新视图移到旧视图上面
reveal: 将旧视图移开,显示下面的新视图
cube : 立方体翻滚效果
oglFlip : 上下左右翻转效果
suckEffect : 收缩效果,如一块布被抽走
rippleEffect: 水滴效果
pageCurl : 向上翻页效果
pageUnCurl : 向下翻页效果
cameraIrisHollowOpen : 相机镜头打开效果
cameraIrisHollowClos : 相机镜头关闭效果

注意:\

  • 核心动画只是修改了控件的图形树,换句话说就是只是修改了他的显示,并没有改变控件的真实位置!!!
  • 也就是说在动画的过程中点击控件是不能跟用户进行交互的,切记切记!!!
  • 当然,点击控件的起始位置是可以的。

四、粒子动画

  • CAEmitterLayer 是一个高性能的粒子引擎,被用来创建 实时粒子动画如: 烟雾火花等等这些效果
  • CAEmitterLayer 常与 CAEmitterCell 结合使用
    • 你将会为不同的例子效果定义一个或多个CAEmitterCell作为模版,
    • 同时CAEmitterLayer负责基于这些模版实例化一个粒子流。
    • 一个CAEmitterCell类似于一个CALayer:
      它有一个contents属性可以定义为一个CGImage,另外还有一些可设置属性控制着表现和行为。

1. CAEmitterLayer

1.1 CAEmitterLayer常用属性

objc 复制代码
@property(nullable, copy) NSArray<CAEmitterCell *> *emitterCells; // 用来装粒子的数组
@property float birthRate; // 粒子产生系数,默认1.0
@property float lifetime; // 粒子的生命周期系数, 默认1.0
@property CGPoint emitterPosition; // 决定了粒子发射形状的中心点
@property CGFloat emitterZPosition;
@property CGSize emitterSize; // 发射源的尺寸大小
@property CGFloat emitterDepth;
@property(copy) NSString *emitterShape; // 发射源的形状
@property(copy) NSString *emitterMode; // 发射模式
@property(copy) NSString *renderMode; // 渲染模式
@property BOOL preservesDepth;
@property float velocity; // 粒子速度系数, 默认1.0
@property float scale; // 粒子的缩放比例系数, 默认1.0
@property float spin; // 粒子的自旋转速度系数, 默认1.0
@property unsigned int seed; // 随机数发生器

CAEmitterLayer里面的API里面的所有属性都已经贴出来并作了说明,看看注释并调试一下就能理解大部分,接下来重点说说一些常用的属性

  • renderMode:渲染模式,控制着在视觉上粒子图片是如何混合的。

    objc 复制代码
    NSString * const kCAEmitterLayerUnordered;
    NSString * const kCAEmitterLayerOldestFirst;
    NSString * const kCAEmitterLayerOldestLast;
    NSString * const kCAEmitterLayerBackToFront;
    NSString * const kCAEmitterLayerAdditive;
  • emitterMode: 发射模式,这个字段规定了在特定形状上发射的具体形式是什么

    objc 复制代码
    kCAEmitterLayerPoints: 点模式,发射器是以点的形势发射粒子。
    kCAEmitterLayerOutline:这个模式下整个边框都是发射点,即边框进行发射
    kCAEmitterLayerSurface:这个模式下是我们边框包含下的区域进行抛洒
    kCAEmitterLayerVolume: 同上
  • emitterShape:规定了发射源的形状。

    objc 复制代码
    kCAEmitterLayerPoint:点形状,发射源的形状就是一个点,位置在上面position设置的位置
    kCAEmitterLayerLine:线形状,发射源的形状是一条线,位置在rect的横向的位于垂直方向中间那条
    kCAEmitterLayerRectangle:矩形状,发射源是一个矩形,就是上面生成的那个矩形rect
    kCAEmitterLayerCuboid:立体矩形形状,发射源是一个立体矩形,这里要生效的话需要设置z方向的数据,如果不设置就同矩形状
    kCAEmitterLayerCircle:圆形形状,发射源是一个圆形,形状为矩形包裹的那个圆,二维的
    kCAEmitterLayerSphere:立体圆形,三维的圆形,同样需要设置z方向数据,不设置则通二维一样
  • emitterSize:发射源的大小,这个emitterSize结合position构建了发射源的位置及大小的矩形区域rect

  • emitterPosition:发射点的位置。

  • lifetime:粒子的生命周期。

  • velocity:粒子速度。

  • scale:粒子缩放比例。

  • spin:自旋转速度。

  • seed:用于初始化产生的随机数产生的种子。

  • emitterCells:CAEmitterCell对象的数组,被用于把粒子投放到layer上

1.2 CAEmitterLayer决定粒子系数的属性

  • birthRate: 粒子产生系数,默认1.0;每个粒子cell的产生率乘以这个粒子产生系数,得出每一秒产生这个粒子的个数。 即:每秒粒子产生个数 = layer.birthRate * cell.birthRate ;
  • lifetime:粒子的生命周期系数,默认1.0。计算方式同上;
  • velocity:粒子速度系数, 默认1.0。计算方式同上;
  • scale:粒子的缩放比例系数, 默认1.0。计算方式同上;
  • spin:自旋转速度系数, 默认1.0。计算方式同上;

1.3 CAEmitterLayer决定粒子内容的属性

  • emitterCells:用来装粒子的数组。每种粒子就是一个CAEmitterCell。在API中可以看到CAEmitterCell是服从CAMediatiming协议的,可以通过beginTime来控制subCell的出现时机

2. CAEmitterCell

2.1 CAEmitterCell常用属性

objc 复制代码
@property(nullable, copy) NSString *name; // 粒子名字, 默认为nil
@property(getter=isEnabled) BOOL enabled; 
@property float birthRate; // 粒子的产生率,默认0
@property float lifetime; // 粒子的生命周期,以秒为单位。默认0
@property float lifetimeRange; // 粒子的生命周期的范围,以秒为单位。默认0
@property CGFloat emissionLatitude;// 指定纬度,纬度角代表了在x-z轴平面坐标系中与x轴之间的夹角,默认0: 
@property CGFloat emissionLongitude; // 指定经度,经度角代表了在x-y轴平面坐标系中与x轴之间的夹角,默认0:
@property CGFloat emissionRange; //发射角度范围,默认0,以锥形分布开的发射角度。角度用弧度制。粒子均匀分布在这个锥形范围内;
@property CGFloat velocity; // 速度和速度范围,两者默认0
@property CGFloat velocityRange;
@property CGFloat xAcceleration; // x,y,z方向上的加速度分量,三者默认都是0
@property CGFloat yAcceleration;
@property CGFloat zAcceleration;
@property CGFloat scale; // 缩放比例, 默认是1
@property CGFloat scaleRange; // 缩放比例范围,默认是0
@property CGFloat scaleSpeed; // 在生命周期内的缩放速度,默认是0
@property CGFloat spin; // 粒子的平均旋转速度,默认是0
@property CGFloat spinRange; // 自旋转角度范围,弧度制,默认是0
@property(nullable) CGColorRef color; // 粒子的颜色,默认白色
@property float redRange; // 粒子颜色red,green,blue,alpha能改变的范围,默认0
@property float greenRange;
@property float blueRange;
@property float alphaRange;
@property float redSpeed; // 粒子颜色red,green,blue,alpha在生命周期内的改变速度,默认都是0
@property float greenSpeed;
@property float blueSpeed;
@property float alphaSpeed;
@property(nullable, strong) id contents; // 粒子的内容,为CGImageRef的对象
@property CGRect contentsRect;
@property CGFloat contentsScale;
@property(copy) NSString *minificationFilter;
@property(copy) NSString *magnificationFilter;
@property float minificationFilterBias;
@property(nullable, copy) NSArray<CAEmitterCell *> *emitterCells; // 粒子里面的粒子
@property(nullable, copy) NSDictionary *style;

CAEmitterCell里面的API里面的大部分属性作了说明,看看注释并调试一下就能理解大部分,接下来重点说说一些常用的属性。CAEmitterLayer就是粒子的工厂,但是要实现效果就需要CAEmitterCell的帮助。

  • 粒子在X.Y.Z三个方向上的加速度。

    objc 复制代码
    @property CGFloat xAcceleration;
    @property CGFloat yAcceleration;
    @property CGFloat zAcceleration;
  • 粒子缩放比例、缩放范围及缩放速度。(0.0`1.0)

    objc 复制代码
    @property CGFloat scale;
    @property CGFloat scaleRange;
    @property CGFloat scaleSpeed;
  • 粒子自旋转速度及范围:

    objc 复制代码
    @property CGFloat spin;
    @property CGFloat spinRange;
  • 粒子RGB及alpha变化范围、速度。

    objc 复制代码
    //范围:
    @property float redRange;
    @property float greenRange;
    @property float blueRange;
    @property float alphaRange;
    //速度:
    @property float redSpeed;
    @property float greenSpeed;
    @property float blueSpeed;
    @property float alphaSpeed;
  • emitterCells:子粒子。

  • color:指定了一个可以混合图片内容颜色的混合色。

  • birthRate:粒子产生系数,默认1.0.

  • contents:是个CGImageRef的对象,即粒子要展现的图片;

  • emissionRange:值是2π(代码写成M_PI * 2.0f),这意味着粒子可以从360度任意位置反射出来。如果指定一个小一些的值,就可以创造出一个圆锥形。

  • 指定值在时间线上的变化,例如:alphaSpeed = 0.4,说明粒子每过一秒减小0.4。

2.2 CAEmitterCell决定生命状态的属性

  • lifetimelifetimeRange:粒子在系统上的生命周期,即存活时间,单位是秒。配合lifetimeRage来让粒子生命周期均匀变化,以便可以让粒子的出现和消失显得更加离散。
  • birthRate:每秒钟产生的粒子的数量,是浮点数。对于这个数量为浮点数,在测试的时候可以灵活使用它。比如你想看粒子的运动状态,但是太多了可能会很迷糊,这时候你把birthRate = 0.1f,其他参数不变,就能看到单个粒子的运动状态。

2.3 CAEmitterCell决定内容的属性

  • contents:为CGImageRef的对象。关于contents会联想到CALayer了,在CALayer中展示静态的图片是需要用到这个属性。提供一张图片,作为粒子系统的粒子。但是因为粒子系统可以给粒子上色,为了做出好的颜色变换效果,通常提供的图片为纯色的图片,一般为白色。
  • name:粒子的名字。初看没什么用,但是当CAEmitterLayer里面有很多个cell的时候,给每个cell设置好名字,要修改一些属性以达到动画效果的改变等,就可以通过KVC拿到这个cell的某个属性。在后面的几个demo中都用用到。

2.4 CAEmitterCell决定颜色状态的属性

粒子系统之所以能做出炫酷的效果,和它的色彩多样化有必不可上的关系,在CAEmitterCell中提供了较多的颜色控制属性这部分属性让你获得了控制粒子颜色,颜色变化范围和速度的能力,你可以凭借它来完成一些渐变的效果或其它构建在它之上的酷炫效果。接下来就看看这些颜色属性。

  • colorcolor是粒子的颜色属性,这个颜色属性的作用是给粒子上色,它的实现方法很简单,就是将contents自身的颜色的RGBA值 * color的RGBA值,就得到最终的粒子的颜色。为了很好的计算,通常用白色的图片作为contents,因为它的RGB都是255,转换为UIColor中的component就是1,用color乘上它就能得到color设置的颜色效果。
  • redRangegreenRangeblueRangealphaRange:这些是对应的color的RGBA的取值范围,取值范围为0~1,比如如下设置中
objc 复制代码
snowCell.color = [[UIColor colorWithRed:0.1 green:0.2 blue:0.3 alpha:0.5]CGColor];
snowCell.redRange = 0.1;
snowCell.greenRange = 0.1;
snowCell.blueRange = 0.1;
snowCell.alphaRange = 0.1;

对应的RGBA的取值范围就是:R(00.2)、G(0.1 0.3)、B(0.20.4)、A(0.40.6)。

  • redSpeedgreenSpeedblueSpeedalphaSpeed:这些是对应的是粒子的RGBA的变化速度,取值范围为0~1。表示每秒钟的RGBA的变化率。这个变化率的计算方式其实很简单,先看下面的几行代码:
objc 复制代码
snowCell.lifetime = 20.f;  // 粒子的生命周期
snowCell.color = [[UIColor colorWithRed:0.f green:1.f blue:1.f alpha:1.f]CGColor];
snowCell.redSpeed = 0.2;

这里设置了粒子颜色的RGBA,以及redSpeed,其他的没设置默认为0。粒子的生命周期(lifetime)为20秒,那么这个粒子从产生到消失的时间就是20秒。它的Red值为0,redSpeed为0.2,那么在粒子的这个生命周期内,粒子的每秒钟的Rde值就会增加0.2 * 255,表现在外观上的状态就是粒子颜色在不断变化,接近白色。最后粒子生命周期结束的时候,粒子的color正好是RGBA(1,1,1,1)。当然个变化的速度也可以负数,计算方式相同。比如要设置烟花的效果,那么要让在爆炸的过程中颜色变化,就是通过这样的设置达到的效果。

2.5 CAEmitterCell决定飞行轨迹的属性。

CAEmitterLayer虽然控制了粒子的发射位置和形状等,但是粒子的飞行同时也需要自身去决定,比如粒子发射的角度、发散的范围,自转属性等。那么接下来就说说这些属性。

  • emissionLongitude: 指定经度,经度角代表了在x-y轴平面坐标系中与x轴之间的夹角,默认0,弧度制。顺时针方向为正。这样解释看起来不好懂,画个图就明白了。
go 复制代码
粒子沿着X轴向右飞行,如果`emissionLongtitude = 0`那么粒子会沿着X轴向右飞行,如果想沿着Y轴向下飞行,那么可以设置`emissionLongtitude = M_PI_2`。
  • emissionLatitude:这个和emissionLongitude的原理是一样的,只不过是在三维平面上的x-z轴上与x轴的夹角。

  • emissionRange:发射角度范围,默认0,以锥形分布开的发射角度。角度用弧度制。粒子均匀分布在这个锥形范围内。在二维平面中,若想要以锥形的形式发射粒子,然粒子的发散范围不是一条线,而是一个锥形区域(也可以叫做扇形),那么可以通过emissionRange来设置一个范围。比如想沿Y轴向下成90度的锥形区域发散,那么可以通过如下代码设置:

objc 复制代码
snowCell.emissionLongitude = M_PI_2;
snowCell.emissionRange = M_PI_4;

实现的效果如下:

可以看到粒子是沿着Y轴向下成90度的一个发散角度。如果想实现火焰等效果。就可以这样,把角度调小一点即可。

  • velocityvelocityRangexAccelerationyAccelerationzAcceleration:前面两个是粒子的初速度和初速度的范围,后面是三个分别是在x、y、z轴方向的加速度,这个很好理解,初中就应该知道加速度的概念,也就是每秒钟速度的变化量。在放烟花的效果中,烟花飞上天的过程中,模拟一个收重力影响的效果,就可以通过yAcceleration模拟一个重力加速度的效果。
  • spinspinRange:这两个属性很重要,是粒子的自转属性。在粒子被发射出去之后,想要实现自转,就需要用到他们。**粒子的自转是以弧度制来计算的,表示每秒钟粒子自转的弧度数。spin为正数代表粒子是顺时针旋转的,为负数的话就是逆时针选转了。**举个🌰:粒子的生命周期就是20秒,那么你想让你的粒子这个生命周期内刚好自转12周,若spinRange为0,那么粒子的spin值就应该为((PI/180)*360 * 12)/20,就得到了每秒需要转动的弧度数。

2.6 CAEmitterCell子粒子的属性

  • emitterCells:看到CAEmitterCell的这个属性的时候或许会有些疑惑,不用惊讶,前面说过CAEmitterLayer可以产生cell,通用cell也可以产生cell。那么这个属性就和CAEmitterLayer中的emitterCells一样,也是一个数组。这里有几个需要注意的地方:

    1. 若给cell设置了subCell,若想控制subCell的方向,那么得考虑父cell的方向属性,也就是emissionLongtitudeemissionLatitude这两个属性的情况。

    2. 不管父粒子cell是从什么样的形状上发射出来的,若要发射subCell,subCell总是从kCAEmitterLayerPoint形状上由父粒子的中心发射出来的。

3. 注意

  • CAEmitterLayerCAEmitterCell 中 有相同的属性,他们控制相同的特性

  • 若是相同属性都各自设置了值,粒子发射引擎在工作的时候,会把两个值相乘。作为这个属性的最终值来控制显示效果

  • 相同属性如下:

    objc 复制代码
    @property float birthRate; // 每秒产生的粒子数量
    @property float lifetime; // 粒子的生命周期.单位是秒
    @property CGFloat scale; // 粒子的缩放比例

代码示例:

objc 复制代码
    UIView * containView = [[UIView alloc]initWithFrame:self.view.bounds];
    containView.center = self.view.center;
    containView.backgroundColor = self.view.backgroundColor;
    self.containView = containView;
    [self.view addSubview:self.containView];

    CAEmitterLayer *emitter = [CAEmitterLayer layer];
    emitter.frame = self.containView.bounds;
    [self.containView.layer addSublayer:emitter];
    emitter.renderMode = kCAEmitterLayerAdditive;//这会让重叠的地方变得更亮一些。
    emitter.emitterPosition = CGPointMake(emitter.frame.size.width / 2.0, emitter.frame.size.height / 2.0);

    CAEmitterCell *cell = [[CAEmitterCell alloc] init];
    cell.contents = (__bridge id)[UIImage imageNamed:@"star_yellow"].CGImage;
    cell.birthRate = 150;
    cell.lifetime = 5.0;
    cell.color = [UIColor colorWithRed:1 green:0.5 blue:0.1 alpha:1.0].CGColor;
    cell.alphaSpeed = -0.4;
    cell.velocity = 50;
    cell.velocityRange = 50;
    cell.emissionRange = M_PI * 2.0;

    emitter.emitterCells = @[cell];

案例2:瀑布飘洒效果

objc 复制代码
- (void)setupSubviews {
    self.layer.backgroundColor = [UIColor blackColor].CGColor;
    // 配置emitter
    [self emiterLayer].renderMode = kCAEmitterLayerAdditive; // 粒子如何混合, 这里是直接重叠
    [self emiterLayer].emitterPosition = CGPointMake(self.frame.size.width, 0); // 发射点的位置
    [self emiterLayer].emitterShape = kCAEmitterLayerPoint;
    

    NSMutableArray * mArr = @[].mutableCopy;
    int cellCount = 6;
    for (int i = 0; i<cellCount; i++) {
        CAEmitterCell * cell = [self getEmitterCellAction];
        [mArr addObject:cell];
    }
    [self emiterLayer].emitterCells = mArr; // 将粒子组成的数组赋值给CAEmitterLayer的emitterCells属性即可.
}

- (CAEmitterCell *)getEmitterCellAction {
    CAEmitterCell *cell = [[CAEmitterCell alloc] init];
    //    cell.contents = (__bridge id)[UIImage imageNamed:@"coin"].CGImage; // 粒子中的图片
    cell.contents = (__bridge id _Nullable)([self setRandomColorCircleImageSize:CGSizeMake(20, 20)].CGImage);
    cell.yAcceleration = arc4random_uniform(80);   // 粒子的初始加速度
    cell.xAcceleration = -cell.yAcceleration-10;
    cell.birthRate = 10.f;           // 每秒生成粒子的个数
    cell.lifetime = 6.f;            // 粒子存活时间
    cell.alphaSpeed = -0.1f;        // 粒子消逝的速度
    cell.velocity = 30.f;           // 粒子运动的速度均值
    cell.velocityRange = 100.f;      // 粒子运动的速度扰动范围
    cell.emissionRange = M_PI; // 粒子发射角度, 这里是一个扇形.
    
//    cell.scale = 0.2;
//    cell.scaleRange = 0.1;
//    cell.scaleSpeed = 0.02;
    
    CGFloat colorChangeValue  = 50.0f;
    cell.blueRange = colorChangeValue;
    cell.redRange =  colorChangeValue;
    cell.greenRange =  colorChangeValue;
    
    return cell;
}

emitterShape发射源形状取值不同时会有不同效果。

  • kCAEmitterLayerPoint:
  • kCAEmitterLayerLine: 线

五、UIImageView帧动画

1.相关的属性和方法:

objc 复制代码
//动画持续时间
@property (nonatomic) NSTimeInterval animationDuration;         // for one cycle of images. default is number of images * 1/30th of a second (i.e. 30 fps)
//动画持续次数.默认是0,代表无限循环
@property (nonatomic) NSInteger      animationRepeatCount;      // 0 means infinite (default is 0)
//开始动画
- (void)startAnimating;
//结束动画
- (void)stopAnimating;

2.gif动画/图片数组Demo

objc 复制代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    NSArray *imageArray = [self getImageArrayWithGIFName:@"aisi"];
    self.imageView.animationImages = imageArray;
    self.imageView.animationDuration = 3;
    self.imageView.animationRepeatCount = MAXFLOAT;
    [self.imageView startAnimating];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [_imageView stopAnimating];
    });
}

- (NSArray<UIImage *> *)getImageArrayWithGIFName:(NSString *)imageName {

    NSMutableArray *imageArray = [NSMutableArray array];
    NSString *path = [[NSBundle mainBundle] pathForResource:imageName ofType:@"gif"];
    NSData *data = [NSData dataWithContentsOfFile:path];

    if (!data) {
        NSLog(@"图片不存在!");
        return nil;
    }

    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    size_t count = CGImageSourceGetCount(source);

    if (count <= 1) {
        [imageArray addObject:[[UIImage alloc] initWithData:data]];
    }else {

        for (size_t i = 0; i < count; i++) {
            CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
            [imageArray addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
            CGImageRelease(image);
        }
    }

    CFRelease(source);
    return imageArray;
}

六、经典第三方动画库

  • Lottie
    • 描述: Lottie 是由 Airbnb 开发的一个用于实现高品质动画的库,支持将 Adobe After Effects 制作的动画导出为 JSON 文件,并在 iOS 上进行播放。
    • 特点: 可以轻松实现复杂的矢量动画效果,如路径动画、形状变换等。
    • GitHub 地址 : Lottie
  • Spruce iOS
    • 描述: Spruce iOS 是一个用于实现列表项动画的库,可以为 UITableView 和 UICollectionView 添加各种炫酷的列表项过渡效果。
    • 特点: 提供了多种内置的过渡效果,如平行、放大、淡入淡出等,也支持自定义过渡效果。
    • GitHub 地址 : Spruce iOS
  • ViewAnimator
    • 描述: ViewAnimator 是一个用于实现视图动画的库,支持为任何视图添加多种动画效果。
    • 特点: 提供了简单易用的 API,支持多种动画效果,如渐变、旋转、弹簧等。
    • GitHub 地址 : ViewAnimator
  • ......

七、自定义转场动画

1. 核心要点

  • 切换页面转场的几种方式:
    • 通过 UIViewController Modal出一个新VC的页面
    • 通过容器控制器 切换 页面
      • 通过 UINavigationController进行PushPop操作,作VC间的页面切换
      • 通过 UITabBarControllerselectIndex 重新赋值,,进行选中VC的切换
  • 转场方式:
    • 默认转场动画: 系统的 ModalPushPopselectVC切换
    • 自定义转场动画:
      • 交互性(实现动画的实例+手势交互)
      • 非交互形(实现动画的实例)
  • 注意:
    • 系统默认转场动画,是系统提供了默认实现动画实例
    • 因此,我们要自定义转场动画,也要
      • 提供自定义的实现动画实例
      • 在页面转场的时机,将 自定义的实现动画实例 提交 给系统API
        • 系统 通过 Delegate回调方法 把 页面切换的时机告诉我们

因此,接下来我们就要 重点介绍 转场动画 相关的 几个协议(OC、Swift版本的API基本一样.这里用OCAPI介绍)

2. 实现自定义动画对象|UIViewControllerAnimatedTransitioning

实现自定义动画步骤:

    1. 自定义动画对象:
      自定义Class,遵守UIViewControllerAnimatedTransitioning协议
    1. 实现协议中的核心API:
    • 动画执行时间:
      - transitionDuration:transitionContext
    • 动画具体实现
      - animateTransition:
    • 动画执行结束的回调
      - animationEnded:
    1. 在页面转场的时机回调方法中,返回给系统该自定义Class的实例,告诉系统动画实现的细节
  • 协议中的API介绍如下:

objc 复制代码
@protocol UIViewControllerAnimatedTransitioning <NSObject>
// 设置 转场动画的持续时间
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;

/*
 * @ param id <UIViewControllerContextTransitioning> 转场动画的上下文对象
 *    负责 提供 页面切换的上下文,也就是前后两个VC的View等信息
 *    自定义动画的本质,就是编写自定义动画代码,在这个回调中,对前后切换页面的View或layer 添加自定义的动画进行切换
 */
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;

@optional
// 动画结束的 回调
- (void)animationEnded:(BOOL) transitionCompleted;

@end

3. 页面转场上下文对象|UIViewControllerContextTransitioning

  • 协议定义了 在执行自定义转场动画时所需的一些方法属性
  • 遵守 该协议,并实现了协议中API的 实例对象由系统的回调方法提供
  • 该实例用于提供有关视图控制器之间转场动画的上下文信息(常用属性和方法介绍):
objc 复制代码
@protocol UIViewControllerContextTransitioning <NSObject>
// 容器视图,用于容纳转场过程中的View
@property(nonatomic, readonly) UIView *containerView;
...
@property(nonatomic, readonly) BOOL transitionWasCancelled;
...
// 用户标记转场动画是否完成,必须在动画执行完成之后 调用。入参用context实例的transitionWasCancelled属性值的相反值
- (void)completeTransition:(BOOL)didComplete;
// 通过该方法 获取 上下文 切换 的两个FromVC、ToVC
- (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key;
// 通过该方法 获取 上下文 切换 的两个FromView、ToView
- (nullable __kindof UIView *)viewForKey:(UITransitionContextViewKey)key API_AVAILABLE(ios(8.0));
...
// 通过该方法 获取 VC 的 最终frame,可以间接获得view的center,size。进行缩放,位移等动画
- (CGRect)finalFrameForViewController:(UIViewController *)vc;
@end

实战示例代码片段:

objc 复制代码
// This method can only be a no-op if the transition is interactive and not a percentDriven interactive transition.
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
    self.transitionContext = transitionContext;
    self.containerView = [transitionContext containerView];
    self.fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    self.toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    // iOS8之后才有
    if ([transitionContext respondsToSelector:@selector(viewForKey:)]) {
        self.fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
        self.toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    } else {
        self.fromView = self.fromViewController.view;
        self.toView = self.toViewController.view;
    }
    
    ...
    self.toView.frame = [self.transitionContext finalFrameForViewController:self.toViewController];
    // 在动画 执行完成的地方要 必须执行的代码:
    BOOL wasCancelled = [self.transitionContext transitionWasCancelled];
    [self.transitionContext completeTransition:!wasCancelled];
    ...
}

4. 自定义Modal转场动画|UIViewControllerTransitioningDelegate

这个协议规定了VC1Modal推出 VC2 和 从VC2 dismiss返回 VC1 的两套接口

  • 交互型
    • Modal推出: - animationControllerForPresentedController: presentingController: sourceController:
    • dismiss返回: - animationControllerForDismissedController:
  • 非交互型(一般添加pan手势进行交互)
    • Modal推出: - interactionControllerForPresentation:
    • dismiss返回: - interactionControllerForDismissal:

objc 复制代码
@protocol UIViewControllerTransitioningDelegate <NSObject>

@optional
// 非交互型: 我们直接把我们实现的 自定义动画实例,返回即可「present动画和dismiss动画可相同,也可不同」
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;

// 交互型: 我们要在此提供 实现了 协议`UIViewControllerInteractiveTransitioning`的实例,用于告诉系统,动画的执行进度(这依赖我们 编写的 交互代码,若是用手势交互,则是拖拽的x和参考系x值的百分比...)
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator;
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;
...
@end

5. 添加交互逻辑|UIViewControllerInteractiveTransitioning

通过 使用 遵守 该协议的 对象,可以获取 开始交互的时机 和 VC页面切换的 上下文对象,进而添加 交互 逻辑,经常用pan手势添加交互逻辑。编写交互逻辑要点如下:

    1. 在回调方法中,获取 开始交互的时机
    1. 给vc的view添加交互逻辑
    1. 根据交互逻辑 计算出 转场 动画 的 百分比,把百分比值percent 提交给 VC页面切换的 上下文对象。以达到,通过交互控制转场动画的效果
    1. 这依然依赖我们实现的自定义转场动画
    1. 我们可以用 继承系统的UIPercentDrivenInteractiveTransition类,专注于编写交互逻辑。并在合适的时机告知系统 动画执行的 情况(百分比进展、取消、结束)
    • - (void)updateInteractiveTransition:(CGFloat)percentComplete;
    • - (void)cancelInteractiveTransition;
    • - (void)finishInteractiveTransition;
objc 复制代码
@protocol UIViewControllerInteractiveTransitioning <NSObject>
- (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
...
@end

3. UIPercentDrivenInteractiveTransition

objc 复制代码
@interface UIPercentDrivenInteractiveTransition : NSObject <UIViewControllerInteractiveTransitioning>
@property (readonly) CGFloat duration;
....
// 这三个API底层都是调用 UIViewControllerContextTransitioning 上下文对象中的一样API
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
- (void)cancelInteractiveTransition;
- (void)finishInteractiveTransition;
@end

6. UINavigationController|自定义转场动画

注意区分:

  • VC1 通过UINavigationController push 推出 VC2; 或者 VC2 pop 返回 VC1 ,是在遵守并了协议 UINavigationControllerDelegate的转场动画方法中进行实现
  • 而不是 遵守了 UIViewControllerTransitioningDelegate 协议 的相关方法;
  • 对于 转场 动画的具体实现交互逻辑的具体实现, 是可以一致的。
  • 相关核心API如下:
objc 复制代码
@protocol UINavigationControllerDelegate <NSObject>
...
// 自定义交互逻辑实现接口
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                          interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController API_AVAILABLE(ios(7.0));
// 自定义转场动画接口
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC  API_AVAILABLE(ios(7.0));

@end

7. UITabBarController|自定义转场动画

注意区分:

  • UITabBarController select 一个新的 index 进行 页面切换,是在遵守并了协议 UITabBarControllerDelegate的转场动画方法中进行实现
  • 而不是 遵守了 UIViewControllerTransitioningDelegate 协议 的相关方法;
  • 对于 转场 动画的具体实现交互逻辑的具体实现, 是可以一致的。
  • 相关核心API如下:
objc 复制代码
@protocol UITabBarControllerDelegate <NSObject>
...
// 自定义交互逻辑实现接口
- (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController
                      interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController API_AVAILABLE(ios(7.0)) API_UNAVAILABLE(visionos);

// 自定义转场动画接口
- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
            animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                              toViewController:(UIViewController *)toVC  API_AVAILABLE(ios(7.0)) API_UNAVAILABLE(visionos);
@end

八、动画实战Demo

1. 自定义转场动画Demo

动画的具体实现主要用到UIView的Block动画、CATransition动画; github.com/VanZhang-CN...

2. 粒子动画Demo+CoreAnimation动画+几个layer的实战代码

github.com/VanZhang-CN...

相关推荐
chaosama1 小时前
禁止uni小程序ios端上下拉伸(橡皮筋效果)
ios·小程序
Zender Han1 小时前
Flutter自定义矩形进度条实现详解
android·flutter·ios
S0linteeH2 小时前
iOS 18.2 六大新功能外媒實測|ChatGPT進化版SIRI、自製Genmoji
ios
DisonTangor19 小时前
苹果发布iOS 18.2首个公测版:Siri接入ChatGPT、iPhone 16拍照按钮有用了
ios·chatgpt·iphone
- 羊羊不超越 -19 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
2401_865854881 天前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
HackerTom2 天前
iOS用rime且导入自制输入方案
ios·iphone·rime
良技漫谈2 天前
Rust移动开发:Rust在iOS端集成使用介绍
后端·程序人生·ios·rust·objective-c·swift
2401_852403552 天前
高效管理iPhone存储:苹果手机怎么删除相似照片
ios·智能手机·iphone
星际码仔3 天前
【动画图解】是怎样的方法,能被称作是 Flutter Widget 系统的核心?
android·flutter·ios