016-iOS 动画机制

iOS 动画大致有以下几类

  • UIView
  • Transform 仿射变换
  • KeyFrame
  • CoreAnimation

UIView

UIView 是最基础的动画,类似于 Android 中的属性动画,可以对 view 的视觉属性进行插值变换,比如旋转、缩放、平移、颜色变换等。

UIView 支持的变换属性大致分为三类

  • 坐标尺寸
    • bounds
    • frame
    • center
  • 视图显示
    • backgroundColor
    • alpha
    • hidden
  • 形态变化
    • transform

一个动画的基本实现有方法和 block 两种。

方法实现

首先定义方法的属性

    // 第一个参数: 动画标识
    // 第二个参数: 附加参数,在设置代理情况下,此参数将发送到setAnimationWillStartSelector和setAnimationDidStopSelector所指定的方法,大部分情况,设置为nil.
    [UIView beginAnimations:@"myAnimation" context:nil];
    
    //动画持续时间
    [UIView setAnimationDuration:2.0];
    
    //动画的代理对象
    [UIView setAnimationDelegate:nil];
    
    //设置动画开始前和结束后调用的方法
    [UIView setAnimationWillStartSelector:nil];
    [UIView setAnimationDidStopSelector:nil];
    
    //设置动画重复次数,HUGE_VALF 会无限重复
    [UIView setAnimationRepeatCount:HUGE_VALF];
    
    //设置是否从当前状态开始播放动画
    /*假设上一个动画正在播放,且尚未播放完毕,我们将要进行一个新的动画:
     当为YES时:动画将从上一个动画所在的状态开始播放
     当为NO时:动画将从上一个动画所指定的最终状态开始播放(此时上一个动画马上结束)*/
    [UIView setAnimationBeginsFromCurrentState:YES];
    
    //设置动画插值器属性
    //UIViewAnimationCurveEaseInOut,         // slow at beginning and end
    //UIViewAnimationCurveEaseIn,            // slow at beginning
    //UIViewAnimationCurveEaseOut,           // slow at end
    //UIViewAnimationCurveLinear,
    [UIView setAnimationCurve:UIViewAnimationCurveLinear];
    
    //设置动画是否反方向进行
    [UIView setAnimationRepeatAutoreverses:YES];
    
    /* 动画内容 */
    
    // 结束动画
    [UIView commitAnimations];

block 实现

iOS4 以后增加的 block 动画,使用更简洁的语法实现动画效果。

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

[UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionRepeat |UIViewAnimationOptionAutoreverse animations:^{
//动画效果
    } completion:^(BOOL finished) {
    //完成后的操作
    }];

UIViewAnimationOptions的枚举值有

 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 动画,可以实现类似弹簧的效果。

+ (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);

使用如下

    [UIView animateWithDuration: 2 delay: 0 usingSpringWithDamping: 0.6 initialSpringVelocity: 0 options: UIViewAnimationOptionCurveLinear animations: ^{
        self.label1.center = self.view.center;
    } completion: nil];

Tips

  • bounds 和 frame 都能实现平移动画,但是不能实现缩放动画,bounds 和 frame 的平移坐标系也不一样,frame 是相对父 view 的坐标系,而 bounds 是 view 自身的坐标系,一般要实现的动画应该是用 frame 实现。
  • UILabel 设置 backgroundColor 是没有动画效果的,必须对 label 的 layer 设置 backgroundColor 才可以。
  • 如果在 block 选项中设置动画无限重复,则 completion 代码块永远不会执行。

Transform 动画

CoreGraphics 框架中的 CGAffineTransform 主要是用于处理形变变换,例如平移、缩放、旋转等,采用二维坐标系,在屏幕上向右为 X 轴正方向,向下为 Y 轴正方向。

平移

基于初始位置的平移

CGAffineTransformMakeTranslation(CGFloat tx,
  CGFloat ty) 

基于指定位置的平移

CGAffineTransformTranslate(CGAffineTransform t,
  CGFloat tx, CGFloat ty) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0)

缩放

基于初始位置的缩放

CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)

基于指定位置的平移

CGAffineTransformScale(CGAffineTransform t,
  CGFloat sx, CGFloat sy)

旋转

基于初始位置的缩放

CGAffineTransformMakeRotation(CGFloat angle)

基于指定位置的缩放

CGAffineTransformRotate(CGAffineTransform t,
  CGFloat angle)

还原状态

CGAffineTransformIdentity

合并转换

CGAffineTransformConcat(CGAffineTransform t1,
  CGAffineTransform t2)

可以将两种仿射变换合并为一个 transform,其本质是对变换矩阵的计算。

KeyFrame 动画

关键帧动画的主要思路是将动画过程拆分为几个小过程,也称为关键帧,以相对时间点、相对时间段作为参考传入关键帧,设置好插值系数,就可以便捷的将多个小变换按顺序执行。

创建一个关键帧动画

[UIView animateKeyframesWithDuration:10 delay:0 options:UIViewKeyframeAnimationOptionCalculationModeCubic animations:^{}

传入参数包括动画持续时间,延迟,动画插值系数选项和动画,这里选项主要有

UIViewKeyframeAnimationOptionCalculationModeLinear      // 连续运算模式,线性
UIViewKeyframeAnimationOptionCalculationModeDiscrete    // 离散运算模式,只显示关键帧
UIViewKeyframeAnimationOptionCalculationModePaced       // 均匀执行运算模式,线性
UIViewKeyframeAnimationOptionCalculationModeCubic       // 平滑运算模式
UIViewKeyframeAnimationOptionCalculationModeCubicPaced  // 平滑均匀运算模式

接下来在 animations 代码块中加入动画关键帧

[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:0.2 animations:^{
            self.label1.center = CGPointMake(200, 30);
        }];

这里传入的第一个参数是关键帧开始的相对时间点,第二个参数是关键帧持续的相对时间段,最后一个就是具体的动画了,所以时间点和时间段都是小于 1 的。

CoreAnimation

CoreAnimation 提供了一组在 iOS 和 MACOSX 上的通用动画 API,大致可以分为以下几类

  • CABasicAnimation 基本动画
  • CATransition 转场动画
  • CAKeyframeAnimation 关键帧动画
  • CAAnimationGrup 动画组

首先要明确在 iOS 中任何 UIView 中都有一个内部图层 CALayer,UIView 会调用 drawRect 方法进行图层绘制,绘制结束将图层拷贝到屏幕上,从而完成显示。所以我们可以通过调整 Layer 上的属性来实现动画效果。当然 CALayer 与 UIView 也有区别,UIView 可以响应事件,而 CALayer 必须手动进行事件响应。

CAAnimation 是个抽象类,不能直接使用。CABaseAnimation 可以实现前面提到的大多数属性动画,它有以下几个设置选项

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

fillMode 支持以下选项

  • kCAFillModeRemoved 默认值,动画开始前和动画结束后对 layer 都没有影响,动画结束后,layer 会恢复到之前的状态
  • kCAFillModeForwards 当动画结束后,layer 会保持动画最后的状态
  • kCAFillModeBackwards 当动画加入了一个 layer 时就立即进入初始状态并等待动画开始
  • kCAFillModeBoth 动画加入后开始之前,layer 便处于动画初始状态,动画结束后 layer 保持动画最后的状态

timingFunction 支持以下几种常见的

  • kCAMediaTimingFunctionLinear(线性) 匀速
  • kCAMediaTimingFunctionEaseIn(渐进) 缓慢进入,加速离开
  • kCAMediaTimingFunctionEaseOut(渐出) 全速进入,减速到达
  • kCAMediaTimingFunctionEaseInEaseOut(渐进渐出) 缓慢进入,中间加速,减速到达,默认选项

CABaseAnimaition

使用 CABaseAnimation 基本思路就是设置好要调整的属性、初始值、结束值、插值模式等,然后将动画加入到相应的层 layer 上去。

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(16, 16)];
    animation.toValue = [NSValue valueWithCGPoint:self.view.center];
    animation.duration = 1.0;
    animation.fillMode = kCAFillModeBoth;
    animation.removedOnCompletion = NO;//动画结束后不回到原始状态
    animation.autoreverses = YES;//允许反向动画
    animation.repeatCount = HUGE_VALF;//无限循环
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    [self.label1.layer addAnimation:animation forKey:@"myposition"];

在这里,keyPath 支持的属性有

  • transform —— 3D变换,[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 0)],直接进行3D变换
  • transform.scale —— 缩放,@(1.5),放大1.5倍
  • transform.scale.x —— 宽度缩放,@(1.5),宽放大1.5倍
  • transform.scale.y —— 高度缩放,@(1.5),高放大1.5倍
  • transform.rotation.x —— 围绕x轴旋转,@(M_PI),x轴旋转180度
  • transform.rotation.y —— 围绕y轴旋转,@(M_PI),y轴旋转180度
  • transform.rotation.z —— 围绕z轴旋转,@(M_PI),z轴旋转180度
  • position —— 位置(中心点的改变),[NSValue valueWithCGPoint:CGPointMake(100, 100)],中心点变为(100,100)
  • opacity —— 透明度,@(0.5),透明度变为0.5
  • bounds —— 大小的改变,中心点保持不变,[NSValue valueWithCGRect:CGRectMake(0, 0, 300, 300)],大小变为(300,300)
  • cornerRadius —— 圆角的设置 ,@(5),圆角设置为5
  • backgroundColor —— 背景颜色变换,(id)[UIColor redColor].CGColor,背景改为红色
    contents,可以改变layer展示的图片,(id)[UIImage imageNamed:@"12.png"].CGImage,将UIView的展示图片改为12.png
  • strokeStart —— 从起始点开始变化,fromValue = 0,toValue = 1,为CAShapeLayer的属性
  • strokeEnd —— 从结束的位置开始变化,fromValue = 1,toValue = 0.5,为CAShapeLayer的属性
  • path —— 根据路径来改变
  • rotaion.x —— 旋转,弧度,X轴
  • rotaion.y —— 旋转,弧度,Y轴
  • rotaion.z —— 旋转,弧度,Z轴
  • rotaion —— 旋转,弧度,Z轴,完全等同于rotation.z
  • ···

例如对于 background 属性

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
    animation.fromValue = (id)[UIColor cyanColor].CGColor;
    animation.toValue = (id)[UIColor magentaColor].CGColor;
    animation.duration = 1.0;
    animation.fillMode = kCAFillModeBoth;
    animation.removedOnCompletion = NO;//动画结束后不回到原始状态
    animation.autoreverses = YES;//允许反向动画
    animation.repeatCount = HUGE_VALF;//无限循环
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    [self.label1.layer addAnimation:animation forKey:@"bgcolor"];

CAKeyFrameAnimation

核心动画也有关键帧动画的 API,可以设置多个控制状态.

values 控制

  • 路径移动
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    NSValue *key1 = [NSValue valueWithCGPoint:CGPointMake(16, 16)];
    NSValue *key2 = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
    NSValue *key3 = [NSValue valueWithCGPoint:CGPointMake(150, 50)];
    NSValue *key4 = [NSValue valueWithCGPoint:CGPointMake(300, 250)];
    animation.values = @[key1, key2, key3, key4];
    animation.duration = 5;
    animation.delegate = nil;
    animation.autoreverses = YES;//是否执行反方向动画
    animation.repeatCount = HUGE_VALF;//重复执行次数
    animation.removedOnCompletion = NO;//执行后移除动画
    animation.fillMode = kCAFillModeBoth;
    [self.label1.layer addAnimation:animation forKey:@"key_frame"];
  • 图标抖动
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
    anim.values = @[@(-M_PI_4 * 0.2),@(M_PI_4 * 0.2), @(-M_PI_4 * 0.2)];
    anim.duration = 0;
    anim.repeatCount = CGFLOAT_MAX;
    [self.label1.layer addAnimation:anim forKey:@"key_frame"];
  • path 控制

设置了 animation 的 path 属性后 values 属性就会失效。

    CGFloat SCREEN_WIDTH = [UIScreen mainScreen].bounds.size.width;
    CGFloat SCREEN_HEIGHT = [UIScreen mainScreen].bounds.size.height;
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(SCREEN_WIDTH/2-100, SCREEN_HEIGHT/2-100, 200, 200)];
    animation.path = path.CGPath;
    animation.duration = 2.0f;
    animation.autoreverses = YES;//是否执行反方向动画
    animation.repeatCount = HUGE_VALF;//重复执行次数
    animation.removedOnCompletion = NO;//执行后移除动画
    animation.fillMode = kCAFillModeBoth;

    [self.label1.layer addAnimation:animation forKey:@"pathAnimation"];

CAAnimationGroup 组动画

组动画顾名思义就是将一组动画放在一起并发执行,主要用到 CAAnimationGroup 这个类。

    CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    positionAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(16, 16)];
    positionAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/2)];
    
    CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    scaleAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(1, 1)];
    scaleAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(0.5, 1.5)];
    
    CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    rotationAnimation.fromValue = @(-M_PI_4 * 0.2);
    rotationAnimation.toValue = @(M_PI_4 * 0.2);
    
    CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
    animationGroup.animations = @[positionAnimation, scaleAnimation, rotationAnimation];
    animationGroup.duration = 2;
    animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    animationGroup.fillMode = kCAFillModeBoth;
    animationGroup.autoreverses = YES;
    animationGroup.repeatCount = HUGE_VALF;
    [self.label1.layer addAnimation:animationGroup forKey:@"AnimationGroup"];

用法大体与前面的类似,只要将所有生成的动画都放在 CAAnimationGroup 的 animations 数组里就可以了。

CATransition 转场动画

也是 CAAnimation 的子类,主要用于做过渡动画、转场动画等,重要的属性有 type 和 subtype 两种

type

表示过渡效果,公开 API 有四种

  • kCATransitionFade 渐变
  • kCATransitionMoveIn 进入覆盖
  • kCATransitionPush 推出
  • kCATransitionReveal 揭露离开

还有一些私有 API,不建议在上线开发中使用

subtype

表示过渡方向,有四种

  • kCATransitionFromRight 从右侧进入
  • kCATransitionFromLeft 从左侧进入
  • kCATransitionFromTop 从顶部进入
  • kCATransitionFromBottom 从底部进入

综合使用的例子如下

    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromRight;
    transition.duration = 2.0;
    self.presentationView.image = [UIImage imageNamed:@"picture2"];
    [self.presentationView.layer addAnimation:transition forKey:@"myTrainsition"];

CATrainsition 还有两个属性用于控制动画区间,startProgress 和 endProgress 分别表示在整体动画中的起始点占比和结束点占比,设置了两个属性后会从整体的动画段中截取相应比例对应的动画进行展示。

CALayer 绘制动画

类似 Android 和 HTML5 中的画布,iOS 中的图层 CALayer 也可以进行各种简单的绘制,当然直接复写 UIView 的 drawRect 方法也可以实现,但是实际上考虑到 CALayer 才是负责绘制的实体,基于内聚功能、降低耦合的原则放在 CALayer 进行绘制操作比较合适。

首先自定义一个 CALayer 继承自 CALayer,然后对下面的方法进行复写

- (void)drawInContext:(CGContextRef)ctx

具体绘制的 API 这里不再赘述,网上有详细的 API 可以查到,完成复写后在自定义的 UIView 中进行如下配置即可

    _customLayer = [YasicProgressLayer layer];
    _customLayer.frame = self.bounds;
    _customLayer.contentsScale = [UIScreen mainScreen].scale;
    [self.layer addSublayer:_customLayer];

要注意的是在使用 View 的时候绘制操作依赖于自定义 View 的 bounds 信息,所以要在 viewDidLayoutSubviews 方法中启动重绘。

你可能感兴趣的:(016-iOS 动画机制)