浅谈 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 的具体步骤
- 新建一个类,继承自 UIView
- 实现
- (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 的详细显示过程
-
view 内部的 Layer 会准备好一个
CGContextRef「图层类型上下文」
-
调用 delegate「这里就是 UIView」的
drawLayer:inContext:
方法
传入已经准备好的CGContextRef 对象
-
view 在
drawLayer:inContext:
方法中又会调用自己的drawRect:
方法 -
view 就可以在
drawRect:
方法中实现绘图代码, 所有东西最终都绘制到 view.layer 上面 -
系统再将 view.layer 的内容拷贝到屏幕, 于是完成了 view 的显示
CGContextRef 对象是当前的上下文
由 layer 传入的 CGContextRef 对象
- 是在
drawRect:
中通过UIGraphicsGetCurrentContext()
获取的上下文 - 在
drawRect:
中完成的所有绘图都会填入层的 CGContextRef 中,然后被拷贝至屏幕
四、核心动画
Core Animation 框架简介
- 使用少量的代码就可以实现非常强大的功能
- Core Animation 可以用在 MacOSX 和 iOS 平台
- 执行过程都是在后台操作的,不会阻塞主线程
- Core Animation 是直接作用在 CALayer 上的,并非 UIView
使用步骤
- 初始化一个 CAAnimation 对象,并设置一些动画相关属性
- 通过调用 CALayer 的
addAnimation:forKey:
方法增加 CAAnimation 对象到 CALayer 中,这样就能开始执行动画了 - 通过调用 CALayer 的
removeAnimationForKey:
方法可以停止 CALayer 中的动画
1. CAAnimation 抽象类
想执行动画,就必须初始化一个 CAAnimation 对象
- 所有动画对象的父类,负责控制动画的持续时间和速度
-
CAAnimation 的继承结构
I. CAMediaTiming 协议属性
-
duration
:动画的持续时间 -
repeatCount
:动画的重复次数 -
repeatDuration
:动画的重复时间 -
fillMode
:决定当前对象在 非 active 时间段的行为
比如动画开始之前,动画结束之后 -
beginTime
:可以用来设置动画延迟执行时间
若想延迟 2s,就设置为CACurrentMediaTime()+2
,CACurrentMediaTime()
为图层的当前时间
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」
简介
- 要想创建动画对象,使用它的两个子类:
CABasicAnimation
和CAKeyframeAnimation
- 拥有属性: 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
设置的或者按关键帧平分时间,此时keyTimes
和timingFunctions
无效 -
kCAAnimationCubic
对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算
主要目的是使得运行的轨迹变得圆滑 -
kCAAnimationCubicPaced
在kCAAnimationCubic
的基础上使得动画运行变得均匀
就是系统时间内运动的距离相同,此时keyTimes
和timingFunctions
无效
示例代码
- 让图层跟着路径动
// 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
使用步骤
- 创建 物理仿真器「设置仿真范围」
物理仿真器「Dynamic Animator」
让物理仿真元素执行具体的物理仿真行为
- 创建 物理仿真行为「在添加 物理仿真元素」
物理仿真元素「Dynamic Item」
谁要进行物理仿真?
- 将 物理仿真行为 添加到 物理仿真器 中
**物理仿真行为「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;