016-iOS 动画机制

2017-11-26  本文已影响63人  Yasic

iOS 动画大致有以下几类

UIView

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

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

一个动画的基本实现有方法和 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

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,大致可以分为以下几类

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

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

fillMode 支持以下选项

timingFunction 支持以下几种常见的

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 支持的属性有

例如对于 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"];

设置了 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 有四种

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

subtype

表示过渡方向,有四种

综合使用的例子如下

    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 方法中启动重绘。

上一篇下一篇

猜你喜欢

热点阅读