浅谈 iOS UI 动画

一、定时任务

  • 方法1:performSelector
// 1.5s后自动调用self的hideHUD方法
[self performSelector:@selector(hideHUD) withObject:nil afterDelay:1.5];
  • 方法2:GCD
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 1.5s后自动执行这个block里面的代码
    self.hud.alpha = 0.0;
});
  • 方法3:NSTimer
// 1.5s后自动调用self的hideHUD方法
[NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(hideHUD) userInfo:nil repeats:NO];
// repeats如果为YES,意味着每隔1.5s都会调用一次self的hidHUD方法

NSTimer「定时器」
作用:

  • 指定时间 执行指定任务
  • 每隔一段时间 执行指定任务

使用:

// 开启一个定时器任务
// 每隔 ti 秒,调用 1次 aTarget 的 aSelector方法,yesOrNo 是否重复调用 aSelector方法
+(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInerval)ti target:(id)aTarget selector:(SEL)aSelector userInfor:(id)userInfor repeats:(BOOL)yesOrNo;

// 停止定时器工作
// 一旦定时器停止,不能再次执行任务
// 只能创建新的定时器,执行新任务
- (void)invalidate;
[self.timer invalidate];
self.timer = nil;

// 解决定时器在主线不工作问题
NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(functionName) userInfor:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

二、Quartz 2D

简介

  • Quartz 2D 是一个二维绘图引擎,同时支持 iOS 和 Mac 系统
  • Quartz2D 的 API 是纯 C 语言
  • Quartz2D 的 API 来自于 Core Graphics 框架
  • 数据类型 和 函数 基本都以 CG 作为前缀

作用

  • 绘制图形 : 线条 \ 三角形 \ 矩形 \ 圆 \ 弧 等
  • 绘制文字 \ 绘制\生成图片(图像)
  • 读取\生成PDF
  • 截图\裁剪图片
  • 自定义UI 控件

1. 图形上下文「Graphics Context」

简介

  • 图形上下文是一个 CGContextRef 类型的数据
  • 绘制好的图形 → 保存到 图形上下文 → 显示到 输出目标「Window / Layer / Bitmap / PDF / Printer」

作用

  • 保存绘图信息、绘图状态
  • 决定绘制的输出目标「绘制到什么地方去?」
    「输出目标可以是 显示器窗口 Window / Layer / Bitmap / PDF / Printer」
  • 相同的一套绘图序列,指定不同的 Graphics Context,就可将相同的图像绘制到不同的目标上

种类

  • Window Graphics Context
  • Layer Graphics Context 图层上下文,在 view 的 drawRect 方法中获取
  • Bitmap Graphics Context 绘制图片到新的图片上
  • PDF Graphics Context
  • Printer Graphics Context

2. 自定义 View 的具体步骤

  1. 新建一个类,继承自 UIView
  2. 实现 - (void)drawRect:(CGRect)rect 方法
    然后在这个方法中
    • 取得跟当前view相关联的图形上下文
    • 绘制相应的图形内容
    • 利用 图形上下文 将绘制的所有内容渲染显示到 view 上面

具体操作

- (void)drawRect:(CGRect)rect {
    // 0. 获取上下文 CGContextRef 是结构体,这里面已表明是指针,所以是ctx而非*ctx
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    // 1. 描述路径
    //    设置 起点
    CGContextMoveToPoint(ctx, 50, 50);
    //    设置 下一个点的位置,并和上一个点连线
    CGContextAddLineToPoint(ctx, 200, 200);
    // 2. 渲染上下文,stroke 空心的形式显示「圆圈、线段都属于空心」
    CGContextStrokePath(ctx);
}

3. drawRect 方法

作用

  • 只能由系统在创建好上下文后自动调用,别人无法调用
  • 能取得跟 view 相关联的 图形上下文
  • 利用 图形上下文 将绘制的所有内容渲染显示到 view 上面

何时 drawRect 被调用

  • 当view第一次显示到屏幕上时「被加到 UIWindow上显示出来」
  • 调用 view 的 setNeedsDisplay 或者 setNeedsDisplayInRect:
    setNeedsDisplay:刷新布局
    setNeedsDisplayInRect:刷新矩形范围内的布局

4. 图形上下文栈

简介:将当前的上下文copy一份,保存到栈顶「那个栈叫做图形上下文栈
作用

  • 将不同的绘制状态「颜色、形状、裁剪状态」,保存在栈中
  • 使同一视图上方便的存在多种不同的状态

代码示例


- (void)drawRect:(CGRect)rect
{
    // 获得上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 1. 将ctx拷贝一份放到栈中
    CGContextSaveGState(ctx);
    
    // 设置绘图状态
    CGContextSetLineWidth(ctx, 10);
    [[UIColor redColor] set];
    CGContextSetLineCap(ctx, kCGLineCapRound);
    
    // 第1根线
    CGContextMoveToPoint(ctx, 50, 50);
    CGContextAddLineToPoint(ctx, 120, 190);
    // 将绘制的图形渲染到上下文中
    CGContextStrokePath(ctx);
    
    // 2. 将栈顶的上下文出栈,替换当前的上下文
    CGContextRestoreGState(ctx);
    
    // 第2根线
    CGContextMoveToPoint(ctx, 10, 70);
    CGContextAddLineToPoint(ctx, 220, 290);
    CGContextStrokePath(ctx);
    // 以下代码和 CGContextStrokePath 效果一样,但更加通用
    CGContextDrawPath(ctx, kCGPathStroke);
}

三、图层动画

1. CALayer

简介

  • 因为 UIView 内部有一个 CALayer 图层,UIView 才能显示出来
  • 在创建 UIView 对象时,UIView 内部会自动创建一个图层「即 CALayer 对象」
    通过 UIView 的 layer 属性可以访问这个层
    @property(nonatomic,readonly,retain) CALayer *layer;
  • 当 UIView 需要显示到屏幕上时,会
    调用 drawRect: 方法进行绘图,并且会将所有内容绘制在自己的图层上
    绘图完毕后,系统会将图层拷贝到屏幕上,于是就完成了 UIView 的显示

作用

  • 调整 UIView 的一些外观属性
  • 性给图层添加动画
  • <span style = "color:red">图层动画都是假象,并不会改变图层的属性值</span>
    「CALayer 在不需要与用户交互时使用,例:做转场动画的时候用」

属性

// 边框宽度
@property CGFloat borderWidth;
// 圆角半径
@property CGFloat cornerRadius;

// 宽度和高度
@property CGRect bounds;

// 位置:坐标系是 父layer的坐标系,决定了本类中 anchorPoint点的位置在父layer坐标系的位置
@property CGPoint position;
// 锚点: 坐标系是layer自己的坐标系,决定了layer绕哪个点旋转「x,y的范围都是 0 ~ 1」
@property CGPoint anchorPoint;

// 边框颜色「CGColorRef类型」
@property CGColorRef borderColor;
// 内容「比如设置为图片CGImageRef」
@property(retain) id contents;
// 背景颜色「CGColorRef类型」
@property CGColorRef backgroundColor;

// 形变属性
@property CATransform3D transform;

方法

// CATransform3DMakeRotation(旋转角度, x, y, z)
// 旋转的轴心是 原点到传入的点(x, y, z) 的连线
self.iconView.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);

// 可以传递哪些key path, 在官方文档搜索 "CATransform3D key paths"
[self.iconView.layer setValue:@(-100) forKeyPath:@"transform.translation.x"];

2. CALayer 中的数据类型转换

框架的归属不同

  • CALayer 是定义在 QuartzCore 框架 中的
  • CGImageRef、CGColorRef 两种数据类型是定义在 CoreGraphics 框架 中的
  • UIColor、UIImage 是定义在 UIKit 框架 中的

框架的使用范围不同

  • UIKit 框架 只能在 iOS 中使用
  • QuartzCore 框架CoreGraphics 框架 是可以跨平台在 iOS 和 Mac OS X 上使用

为了保证可移植性

  • QuartzCore 不能使用 UIImage、UIColor,只能使用 CGImageRef、CGColorRef
  • 代码中的转换使用方法
/**
 *  CG:Core Graphics框架
 *  CA:Core Animation框架
 *  Ref:引用
 *  转换:UIKit 框架转换为 CG框架的东西,直接 .CG** 就可以
 
 *  图片一共三层
 *  1. 本类的层,用来接收触摸手势
 *  2. UIImageView 绘制层
 *  3. UIImage 变化层
 */

// 新建图层
//    方法1:[[CALayer alloc] init];
//    方法2:[CALayer layer];
CALayer *layer = [CALayer layer];

// UIKit 框架转换为 CG框架的 CGColor
layer.backgroundColor = [UIColor redColor].CGColor;
layer.bounds = CGRectMake(0, 0, 100, 100);
layer.position = CGPointMake(200, 100);
layer.cornerRadius = 10;
// 将画的形状剪裁掉
layer.masksToBounds = YES;

// UIKit 框架转换为 CG框架的 CGImage
// contents 的属性类型是 id,所以这里要进行强行转换
layer.contents = (id)[UIImage imageNamed:@"catImgName"].CGImage;
[self.view.layer addSublayer:layer];

3. UIView 和 CALayer 的比较

性能比较

  • CALayer 不能处理用户的触摸事件,而 UIView 可以
  • CALayer 的性能比 UIView 会高一些,它少了事件处理的功能,更加轻量级

结构比较

  • UIView 的 subviews属性:访问所有的子视图
    CALayer 的 sublayers属性:访问所有的子层
  • UIView 的 superview 属性:访问父视图
    CALayer 的 superlayer 属性:访问父层
  • 如果两个 UIView 是父子关系,那么它们内部的 CALayer 也是父子关系

4. 非 Root Layer 的隐式动画

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

简介

  • 当对非 Root Layer 的 部分属性 进行修改时,默认 会自动产生一些动画效果
    这些属性称为 Animatable Properties「可动画属性」
  • UIView 控件动画必须通过修改属性的值,才会有隐式动画效果

常见的 Animatable Properties「可动画属性」列举

  • 属性后面有 Animatable 注释的都可以做隐式动画
  • bounds:用于设置 CALayer 的宽度和高度
    修改这个属性会产生缩放动画
  • backgroundColor:用于设置 CALayer 的背景色
    修改这个属性会产生背景色的渐变动画
  • position:用于设置 CALayer 的位置
    修改这个属性会产生平移动画

通过动画事务开关隐式动画

// 开启 动画事务
[CATransaction begin];
// 隐式动画效果关闭
[CATransaction setDisableActions:YES];
self.testView.layer.position = CGPointMake(10, 10); // 没有隐式动画效果了
// 关闭 动画事务
[CATransaction commit];

5. 自定义 Layer

I. 创建 CALayer 子类

步骤

  • 创建一个 CALayer 的子类
  • 覆盖 drawInContext: 方法
  • 使用 Quartz2D API 进行绘图

注意

  • 需要调用 setNeedsDisplay 这个方法,才会触发 drawInContext: 方法的调用,然后进行绘图
MyLayer *layer = [MyLayer layer];
// 设置层的宽高
layer.bounds = CGRectMake(0, 0, 100, 100);
// 设置层的位置
layer.position = CGPointMake(100, 100);
// 开始绘制图层
[layer setNeedsDisplay];
// 添加子层
[self.view.layer addSublayer:layer];

II. 设置 CALayer 代理

步骤

  • 创建新的层,设置 delegate,然后添加到控制器的 view 的 layer 中
  • delegate 实现 drawLayer:inContext: 方法
  • 当 CALayer 需要绘图时,会调用 delegate 的 drawLayer:inContext: 方法进行绘图
    这里的 delegate 不需要遵守协议,因为这里的代理方法 drawLayer:inContext: 是 NSObject 对象都有的方法

注意

  • 不能将某个 UIView 设置为 CALayer 的 delegate
  • 因为 UIView 对象已经是它内部根层的 delegate
  • 再次设置为其他层的 delegate 会出问题
// 创建图层
CALayer *layer = [CALayer layer];

// 设置 delegate,self 指控制器
// 这里的 delegate 不需要遵守协议,因为这里的代理方法drawLayer:inContext:是 NSObject 对象都有的方法
layer.delegate = self;

// 设置层的宽高
layer.bounds = CGRectMake(0, 0, 100, 100);
// 设置层的位置
layer.position = CGPointMake(100, 100);

// 开始绘制图层
// 调用这个方法,才会通知 delegate 进行绘图
[layer setNeedsDisplay];

[self.view.layer addSublayer:layer];

6. UIView 的详细显示过程

  1. view 内部的 Layer 会准备好一个 CGContextRef「图层类型上下文」

  2. 调用 delegate「这里就是 UIView」的 drawLayer:inContext: 方法
    传入已经准备好的 CGContextRef 对象

  3. view 在 drawLayer:inContext: 方法中又会调用自己的 drawRect: 方法

  4. view 就可以在 drawRect: 方法中实现绘图代码, 所有东西最终都绘制到 view.layer 上面

  5. 系统再将 view.layer 的内容拷贝到屏幕, 于是完成了 view 的显示

CGContextRef 对象是当前的上下文

由 layer 传入的 CGContextRef 对象

  • 是在 drawRect: 中通过 UIGraphicsGetCurrentContext() 获取的上下文
  • drawRect: 中完成的所有绘图都会填入层的 CGContextRef 中,然后被拷贝至屏幕

四、核心动画

Core Animation 框架简介

  • 使用少量的代码就可以实现非常强大的功能
  • Core Animation 可以用在 MacOSX 和 iOS 平台
  • 执行过程都是在后台操作的,不会阻塞主线程
  • Core Animation 是直接作用在 CALayer 上的,并非 UIView

使用步骤

  1. 初始化一个 CAAnimation 对象,并设置一些动画相关属性
  2. 通过调用 CALayer 的 addAnimation:forKey: 方法增加 CAAnimation 对象到 CALayer 中,这样就能开始执行动画了
  3. 通过调用 CALayer 的 removeAnimationForKey: 方法可以停止 CALayer 中的动画

1. CAAnimation 抽象类

想执行动画,就必须初始化一个 CAAnimation 对象

  • 所有动画对象的父类,负责控制动画的持续时间和速度
  • CAAnimation 的继承结构


I. CAMediaTiming 协议属性

  • duration:动画的持续时间
  • repeatCount:动画的重复次数
  • repeatDuration:动画的重复时间
  • fillMode:决定当前对象在 非 active 时间段的行为
    比如动画开始之前,动画结束之后
  • beginTime:可以用来设置动画延迟执行时间
    若想延迟 2s,就设置为 CACurrentMediaTime()+2CACurrentMediaTime()为图层的当前时间

fillMode 属性值
要想 fillMode 有效,最好设置 removedOnCompletion = NO

  • kCAFillModeRemoved「默认值」
    当动画开始前和动画结束后,动画对 layer 都没有影响
    动画结束后,layer 会恢复到之前的状态
  • kCAFillModeForwards
    当动画结束后,layer 会一直保持着动画最后的状态
  • kCAFillModeBackwards
    在动画开始前,只要动画中加入了 layer,layer 便进入动画的初始状态并等待动画开始
  • kCAFillModeBoth
    动画加入后开始之前,layer 便处于动画初始状态
    动画结束后 layer 保持动画最后的状态

父图层 和 图层本地的时间 换算公式

  • t = (tp - beginTime) * speed + timeOffset
  • beginTime = tp - (t - timeOffset)/speed
  • t:active local time「图层的本地时间」
  • tp:parent layer time「父图层的时间」

CALayer 上动画的暂停和恢复

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

// 恢复 CALayer 的动画
- (void)resumeLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = layer.timeOffset;
    layer.speed = 1.0;      // 让CALayer的时间继续行走
    layer.timeOffset = 0.0; // 取消上次记录的停留时刻
    layer.beginTime = 0.0;  // 取消上次设置的时间
    
    // 计算暂停的时间「用 CACurrentMediaTime() - pausedTime 也一样」
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    // 设置相对于父坐标系的开始时间「往后退timeSincePause」
    layer.beginTime = timeSincePause;
}

II. CAAnimation 本身的属性

  • removedOnCompletion:默认为 YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。
    如果想让图层保持显示动画执行后的状态,那就设置为 NO,不过还要设置 fillMode 为 kCAFillModeForwards
  • timingFunction:速度控制函数,控制动画运行的节奏
  • delegate:动画代理,用来监听动画的执行过程

timingFunction 可选的值有:

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

代理需要实现的方法有:

// 动画开始执行的时候触发这个方法
- (void)animationDidStart:(CAAnimation *)anim;
// 动画执行完毕的时候触发这个方法
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;

注意:以上所有属性和方法都属于 CAAnimation 抽象类,所以它的子类都能使用

2. CAPropertyAnimation 抽象类「继承自 CAAnimation」

简介

  • 要想创建动画对象,使用它的两个子类:CABasicAnimationCAKeyframeAnimation
  • 拥有属性: keyPath「NSString 类型」
    可以指定 CALayer 的某个属性名为 keyPath,并修改这个属性,达到动画效果

3. CABasicAnimation 类「继承自 CAPropertyAnimation」

简介

  • CABasicAnimation 能够做很多基本的动画效果
  • 有局限性,只能让 CALayer 的属性 从某个值渐变到另一个值「仅仅是在 2 个值之间渐变」
  • <a>图层动画执行完毕后,并没有改变 CALayer 的属性的值</a>

示例:平移动画

  • 方法 1

// 1. 说明这个动画对象要对 CALayer的position属性 执行动画
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];

//    fromValue 到 toValue 的动画持续 1.5s
anim.duration = 1.5; 
//    position属性值从(50, 80)渐变到(300, 350)
//    fromValue 默认是当前的值
anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(50, 80)];
//    toValue:达到多少值
//    byValue:增加多少值
anim.toValue = [NSValue valueWithCGPoint:CGPointMake(300, 350)];

//    设置动画的代理「self指控制器」
anim.delegate = self;

// 2. 让图层保持动画执行后的状态「这里以下两行代码虽然这么保持了状态,但实质上图层的属性值没有改变」
//    动画执行完毕后不删除动画
anim.removedOnCompletion = NO; // 默认是 YES
//    保持最新的状态

anim.fillMode = kCAFillModeForwards;

// 3. 添加动画对象到图层上
[_myView.layer addAnimation:anim forKey:@"translate"];
  • 方法 2
// 1. 说明 动画执行对象属性
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"];
anim.duration = 1;

// 2. 设置 3D 图形运动
CATransform3D form = CATransform3DMakeTranslation(350, 350, 0);
anim.toValue = [NSValue valueWithCATransform3D:form];

// 3. 添加动画对象到图层上
[_myView.layer addAnimation:anim forKey:nil];

4. CAKeyframeAnimation 类「继承自 CAPropertyAnimation」

简介

  • CAKeyframeAnimation 针动画,能保存任意多个值,在这些值之间变化

属性解析

  • values:NSArray 对象,里面的元素称为 keyframe「关键帧」
    动画对象会在指定的时间「duration」内,依次显示 values 数组中的每一个关键帧
  • <span style="color:red">path:可以设置一个 CGPathRef\CGMutablePathRef,让层跟着路径移动</span>
    path 只对 CALayer 的 anchorPoint 和 position 起作用
    如果设置了 path,那么 values 将被忽略
  • keyTimes:可以为对应的关键帧指定对应的时间点「范围为 0 ~ 1.0」
    keyTimes 中的每一个时间值都对应 values 中的每一帧
    没有设置 keyTimes,各个关键帧的时间是平分的

重要属性:calculationMode「计算模式」

  • 其主要针对的是每一帧的内容为一个座标点的情况,也就是对 anchorPoint 和 position 进行的动画
  • 当在平面座标系中有多个离散的点的时候,可以是离散的,也可以直线相连后进行插值计算,也可以使用圆滑的曲线将他们相连后进行插值计算

CalculationMode 目前提供如下几种模式:

  • kCAAnimationLinear「默认值」
    表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算;
  • kCAAnimationDiscrete离散的
    就是不进行插值计算,所有关键帧直接逐个进行显示;
  • kCAAnimationPaced使得动画均匀进行
    不是按 keyTimes 设置的或者按关键帧平分时间,此时 keyTimestimingFunctions 无效
  • kCAAnimationCubic对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算
    主要目的是使得运行的轨迹变得圆滑
  • kCAAnimationCubicPacedkCAAnimationCubic 的基础上使得动画运行变得均匀
    就是系统时间内运动的距离相同,此时 keyTimestimingFunctions 无效

示例代码

  • 让图层跟着路径动
// 1. 创建帧动画
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];

anim.keyPath = @"position";
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeForwards;
anim.duration = 2.0;

// 2. 设置图层移动路径
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, NULL, CGRectMake(100, 100, 200, 200));
anim.path = path;
CGPathRelease(path);

// 3. 设置动画的执行节奏
//    kCAMediaTimingFunctionEaseInEaseOut : 一开始比较慢, 中间会加速,  临近结束的时候, 会变慢
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

// 代理不需要遵守任何协议「因为这些协议是任何对象都遵守的」
// 可以执行的协议方法:
//    - (void)animationDidStart:(CAAnimation *)anim 动画开始时执行
//    - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag 动画结束时执行
anim.delegate = self;

// 4. 添加动画
[self.redView.layer addAnimation:anim forKey:nil];
  • 让图层有多个动作

CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
anim.keyPath = @"transform.rotation";

anim.values = @[@(Angle2Radian(-5)),  @(Angle2Radian(5)), @(Angle2Radian(-5))];
anim.duration = 0.25;
// 动画的重复执行次数
anim.repeatCount = MAXFLOAT;

// 保持动画执行完毕后的状态
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeForwards;

// 给 UI 控件的图层添加动画,图层动,UI 控件跟着动「因为控件的显示靠图层」
// shake 是和 动画对象绑定的键值对的 key 值
[self.iconView.layer addAnimation:anim forKey:@"shake"];

// 通过 key 值 shake 删除动画对象
[self.iconView.layer removeAnimationForKey:@"shake"];

5. CATransition 类「继承自 CAAnimation」

简介

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

属性解析

  • type:动画过渡类型「NSString 类型」
fade     // 交叉淡化过渡 kCATransitionFade 「不支持过渡方向」
push     // 新视图把旧视图推出去 kCATransitionPush
moveIn   // 新视图移到旧视图上面 kCATransitionMoveIn
reveal   // 将旧视图移开,显示下面的新视图 kCATransitionReveal
cube     // 立方体翻滚效果
oglFlip  // 上下左右翻转效果
suckEffect   // 收缩效果,如一块布被抽走「不支持过渡方向」
rippleEffect // 滴水效果「不支持过渡方向」
pageCurl     // 向上翻页效果
pageUnCurl   // 向下翻页效果
cameraIrisHollowOpen  // 相机镜头打开效果「不支持过渡方向」
cameraIrisHollowClose // 相机镜头关上效果「不支持过渡方向」
  • subtype:动画过渡方向
kCATransitionFromRight // 从右 → 左

kCATransitionFromLeft // 从左 → 右

kCATransitionFromBottom  // 从下 → 上

kCATransitionFromTop // 从下 → 下
  • startProgress:动画起点「占整体动画的百分比」
  • endProgress:动画终点「占整体动画的百分比」

示例代码

// 创建转场动画对象
CATransition *anim = [CATransition animation];
    
anim.type = @"pageCurl"; // 转场类型
anim.subtype = kCATransitionFromRight; // 转场方向
anim.duration = 0.5;
anim.startProgress = 0.0;
anim.endProgress = 0.5;

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

6. CAAnimationGroup 类「继承自 CAAnimation」

简介

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

属性解析

  • animations:用来保存一组动画对象的 NSArray
  • 默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的 beginTime 属性来更改动画的开始时间
// 1.创建旋转动画对象
CABasicAnimation *rotate = [CABasicAnimation animation];
rotate.keyPath = @"transform.rotation";
rotate.toValue = @(M_PI);

// 2.创建缩放动画对象
CABasicAnimation *scale = [CABasicAnimation animation];
scale.keyPath = @"transform.scale";
scale.toValue = @(0.0);

// 3.平移动画
CABasicAnimation *move = [CABasicAnimation animation];
move.keyPath = @"transform.translation";
move.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];

// 4.将所有的动画添加到动画组中
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = @[rotate, scale, move];
group.duration = 2.0;
group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;

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

7. UIView 动画

简介

  • UIKit 直接将动画集成到 UIView 类中,当内部的一些属性发生改变时,UIView 将为这些改变提供动画支持
  • <a>图层动画执行完毕后,并没有改变 CALayer 的属性的值</a>
  • <span style="color:red">UIView 动画执行完毕后,改变了 CALayer 的属性的值</span>
  • 要在执行动画时通知视图,执行视图动画,需要将改变属性的代码放在
    [UIView beginAnimations:nil context:nil]
    [UIView commitAnimations]之间

示例代码

[UIView beginAnimations:nil context:nil];

// 添加修改的属性「产生动画」
self.myview.center = CGPointMake(200, 300);

// 动画执行完毕后, 会自动调用self的animateStop方法
//    [UIView setAnimationDelegate:self];
//    [UIView setAnimationDidStopSelector:@selector(animateStop)];
[UIView commitAnimations];

I. UIView 的常用方法

// 设置动画代理对象,当动画**开始**或者**结束**时会发消息给代理对象
+ (void)setAnimationDelegate:(id)delegate;

// 当动画即将开始时,执行代理对象的selector,并且把beginAnimations:context:中传入的参数传进selector
+ (void)setAnimationWillStartSelector:(SEL)selector;

// 当动画结束时,执行代理对象的selector,并且把beginAnimations:context:中传入的参数传进selector
+ (void)setAnimationDidStopSelector:(SEL)selector;

+ (void)setAnimationDuration:(NSTimeInterval)duration; // 动画的持续时间,秒为单位
+ (void)setAnimationDelay:(NSTimeInterval)delay; // 动画延迟delay秒后再开始
+ (void)setAnimationStartDate:(NSDate *)startDate; // 动画的开始时间,默认为now
+ (void)setAnimationCurve:(UIViewAnimationCurve)curve; // 动画的节奏控制,具体看下面的”备注”
+ (void)setAnimationRepeatCount:(float)repeatCount; // 动画的重复次数

// 如果设置为YES,代表动画每次重复执行的效果会跟上一次相反
+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses; 

// 设置视图view的过渡效果,transition指定过渡类型,cache设置YES代表使用视图缓存,性能较好
+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache

II. UIView 的 Block 动画

// 方法 1
+ (void)animateWithDuration:(NSTimeInterval)duration             // 动画的持续时间
                      delay:(NSTimeInterval)delay                // 动画延迟delay秒后开始
                    options:(UIViewAnimationOptions)options      // 动画的节奏控制
                 animations:(void (^)(void))animations           // 将改变视图属性的代码放在这个block中
                 completion:(void (^)(BOOL finished))completion; // 动画结束后,自动调用这个block

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

// 方法 3 UIView 的转场动画
+ (void)transitionFromView:(UIView *)fromView                   // 要移除的视图
                    toView:(UIView *)toView                     // 要添加的视图
                  duration:(NSTimeInterval)duration             // 动画的持续时间
                   options:(UIViewAnimationOptions)options      // 转场动画的类型
                completion:(void (^)(BOOL finished))completion; // 动画结束后,自动调用这个block

// 方法 3 调用完毕后,相当于执行了下面两句代码
//   添加toView到父视图
[fromView.superview addSubview:toView];
//   把fromView从父视图中移除
[fromView.superview removeFromSuperview];

8. UIImageView 的帧动画

简介

  • UIImageView 可以让一系列的图片在特定的时间内按顺序显示

属性解析

  • animationImages:要显示的图片「一个装着 UIImage 的 NSArray」
  • animationDuration:完整地显示一次 animationImages 中的所有图片所需的时间
  • animationRepeatCount:动画的执行次数「默认为 0,代表无限循环」

方法解析

  • - (void)startAnimating;:开始动画
  • - (void)stopAnimating;:停止动画
  • - (BOOL)isAnimating;:是否正在运行动画

9. UIActivityIndicatorView

简介

  • UIActivityIndicatorView 是一个旋转进度轮,可以用来告知用户有一个操作正在进行中
  • 一般用 initWithActivityIndicatorStyle 初始化

方法解析

  • - (void)startAnimating;:开始动画
  • - (void)stopAnimating;:停止动画
  • - (BOOL)isAnimating;:是否正在运行动画

UIActivityIndicatorViewStyle 有 3 个值可供选择:

  • UIActivityIndicatorViewStyleWhiteLarge,大型白色指示器
  • UIActivityIndicatorViewStyleWhite,标准尺寸白色指示器
  • UIActivityIndicatorViewStyleGray,灰色指示器,用于白色背景

五、物理引擎

1. UIDynamic

简介

  • 从 iOS7 开始引入的新技术「属于 UIKit 框架」
  • 是一种物理引擎「模仿显示生活的物理现象」
  • 知名的 2D 物理引擎:Box2d「已停止更新」、Chipmunk

使用步骤

  1. 创建 物理仿真器「设置仿真范围」

物理仿真器「Dynamic Animator」
让物理仿真元素执行具体的物理仿真行为

  1. 创建 物理仿真行为「在添加 物理仿真元素

物理仿真元素「Dynamic Item」
谁要进行物理仿真?

  1. 物理仿真行为 添加到 物理仿真器

**物理仿真行为「Dynamic Behavior」 **
执行怎样的物理仿真效果?怎样的动画效果?

代码示例

// 1. 创建仿真器,实际使用中用懒加载的方法「这里的仿真器 必须是不易被销毁的属性」
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
// 2. 创建物理仿真行为「这里用重力行为来代表」
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.controlView]];
// 3. 添加 物理仿真行为
[self.animator addBehavior:gravity];

2. 三大元素

I. 物理仿真元素「Dynamic Item」

制作仿真元素

  • 不是 任何对象都能做物理仿真元素
  • 只有遵守了 UIDynamicItem 协议 的对象,都能做物理仿真元素

已经是仿真元素的控件

  • UIView:默认遵守了 UIDynamicItem 协议,因此 任何 UI 控件都能做物理仿真
  • UICollectionViewLayoutAttributes:默认遵守 UIDynamicItem 协议

II. 物理仿真行为「Dynamic Behavior」

特点

  • 所有 物理仿真行为 都继承自 UIDynamicBehavior
  • 所有 UIDynamicBehavior 都可以独立进行
  • 多种物理仿真行为,可以组合使用,可以实现一些比较复杂的效果

UIDynamic 提供的物理仿真行为


UIGravityBehavior     // 重力行为:给定重力方向、加速度,让物体朝着重力方向掉落
UICollisionBehavior   // 碰撞行为:通过添加边界「boundary」让物理碰撞局限在某个空间中,实现碰撞效果
UISnapBehavior        // 捕捉行为:让物体迅速冲到某个位置「捕捉位置」捕捉到位置之后会带有一定的震动
UIPushBehavior        // 推动行为
UIAttachmentBehavior  // 附着行为
UIDynamicItemBehavior // 动力元素行为

重力行为

  • 常见属性

// 添加到重力行为中的所有物理仿真元素
@property (nonatomic, readonly, copy) NSArray* items;

// 重力方向(是一个二维向量)
@property (readwrite, nonatomic) CGVector gravityDirection;

// 重力方向(是一个角度,以x轴正方向为0°,顺时针正数,逆时针负数)
@property (readwrite, nonatomic) CGFloat angle;

// 位移 = 初速度 * 时间 + (1/2)* 加速度 * 时间²
// 量级(用来控制加速度,1.0代表加速度是1000 points/second²)
@property (readwrite, nonatomic) CGFloat magnitude;
  • 常见方法

// 初始化
// item参数 :里面存放着物理仿真元素
- (instancetype)initWithItems:(NSArray *)items;
// 添加1个物理仿真元素
- (void)addItem:(id <UIDynamicItem>)item;
// 移除1个物理仿真元素
- (void)removeItem:(id <UIDynamicItem>)item;

碰撞行为

  • 常见属性
// 是否以参照视图的bounds为边界
@property (nonatomic, readwrite) BOOL translatesReferenceBoundsIntoBoundary;
// 设置参照视图的bounds为边界,并且设置内边距
- (void)setTranslatesReferenceBoundsIntoBoundaryWithInsets:(UIEdgeInsets)insets;

// 碰撞模式(分为3种,元素碰撞、边界碰撞、全体碰撞)
@property (nonatomic, readwrite) UICollisionBehaviorMode collisionMode;

// 代理对象(可以监听元素的碰撞过程)
@property (nonatomic, assign, readwrite) id <UICollisionBehaviorDelegate> collisionDelegate;
  • 碰撞行为边界相关方法

- (UIBezierPath*)boundaryWithIdentifier:(id <NSCopying>)identifier;

@property (nonatomic, readonly, copy) NSArray* boundaryIdentifiers;
- (void)removeBoundaryWithIdentifier:(id <NSCopying>)identifier;

- (void)removeAllBoundaries;

// 添加一条看不见的路径「自己绘制」作为边框「或在固定图形中碰撞」

- (void)addBoundaryWithIdentifier:(id <NSCopying>)identifier forPath:(UIBezierPath*)bezierPath;

// 添加一条看不见的线「由两个点确定」作为边框
- (void)addBoundaryWithIdentifier:(id <NSCopying>)identifier fromPoint:(CGPoint)p1 toPoint:(CGPoint)p2;

捕捉行为

  • 注意:如果要进行连续的捕捉行为,需要先把前面的捕捉行为从物理仿真器中移除

// 用于减幅、减震(取值范围是0.0 ~ 1.0,值越大,震动幅度越小)
@property (nonatomic, assign) CGFloat damping;

// 初始化
- (instancetype)initWithItem:(id <UIDynamicItem>)item snapToPoint:(CGPoint)point;

III. 物理仿真器「Dynamic Animator」

简介

  • 可以让 物理仿真元素 执行 物理仿真行为
  • UIDynamicAnimator 类型的对象

常见属性

// 参照视图:通过这个视图来确定 仿真范围
@property (nonatomic, readonly) UIView* referenceView;
// 添加到物理仿真器中的所有物理仿真行为
@property (nonatomic, readonly, copy) NSArray* behaviors;
// 是否正在进行物理仿真
@property (nonatomic, readonly, getter = isRunning) BOOL running;
// 代理对象「能监听物理仿真器的仿真过程,比如开始和结束」
@property (nonatomic, assign) id <UIDynamicAnimatorDelegate> delegate;

常见方法

// UIDynamicAnimator 的初始化
// view参数:是一个参照视图,表示物理仿真的范围
- (instancetype)initWithReferenceView:(UIView *)view;

// 添加1个物理仿真行为
- (void)addBehavior:(UIDynamicBehavior *)behavior;

// 移除1个物理仿真行为
- (void)removeBehavior:(UIDynamicBehavior *)behavior;

// 移除之前添加过的所有物理仿真行为
- (void)removeAllBehaviors;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,465评论 6 30
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,094评论 5 13
  • 目录 ** UIView 动画 ** ** Core Animation ** ** FaceBook POP动画...
    方向_4d0d阅读 1,585评论 0 3
  • 书写的很好,翻译的也棒!感谢译者,感谢感谢! iOS-Core-Animation-Advanced-Techni...
    钱嘘嘘阅读 2,289评论 0 6
  • 一.vi的基本概念文本编辑器有很多,图形模式下有gedit、kwrite等编辑器,文本模式下的编辑器有vi、vim...
    一线码农阅读 730评论 0 0