iOS动画合集

作者: 林风098 | 来源:发表于2018-01-17 11:30 被阅读2350次

iOS核心动画

核心动画框架

CoreAnimation框架是基于OpenGL与CoreGraphics图像处理框架的一个跨平台的动画框架。

在CoreAnimation中大部分的动画都是通过Layer层来实现的,通过CALayer,我们可以组织复杂的层级结构。

在CoreAnimation中,大多数的动画效果是添加在图层属性的变化上,例如,改变图层的位置,大小,颜色,圆角半径等。Layer层并不决定视图的展现,它只是存储了视图的几何属性状态。


核心动画类

核心动画中的虚类不能,而应该使用它子类中的实类。

下面介绍几个实类的简单用法

CABasicAnimation——基本动画

基本动画,是CAPropertyAnimation的子类。
🌰一个简单的动画效果

CABasicAnimation *moveAnimation = [CABasicAnimation animationWithKeyPath:@"position.y"];
moveAnimation.duration = 0.8;//动画时间
//动画起始值和终止值的设置
moveAnimation.fromValue = @(self.imageView.center.x);
moveAnimation.toValue = @(self.imageView.center.x-30);
//一个时间函数,表示它以怎么样的时间运行
moveAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
moveAnimation.repeatCount = HUGE_VALF;
moveAnimation.repeatDuration = 2;
moveAnimation.removedOnCompletion = NO;
moveAnimation.fillMode = kCAFillModeForwards;
//添加动画,后面有可以拿到这个动画的标识
[self.imageView.layer addAnimation:moveAnimationforKey:@"可以拿到这个动画的Key值"];

相关属性
keyPath :要改变的属性名称(传字符串)
fromValue:keyPath相应属性的初始值
toValue:keyPath相应属性的结束值
timingFunction:动画随时间运行的关系

动画相关属性

动画过程说明

  • 随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值fromValue渐渐地变为toValue
  • keyPath内容是CALayer的可动画Animation属性
  • 如果fillMode=kCAFillModeForwards同时removedOnComletion=NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。

在CAAnimation中可以实现代理方法 <CAAnimationDelegate>
- animationDidStart: //开始时调用'
- animationDidStop:finished: //结束时调用
在addAnimation:forKey:方法中,也可以给这个动画设置一个键,可以在其他地方将其取出来,进行一些操作,比如删除。这也充分体现了kvc的灵活。
用到CALayer的 removeAnimationForKey:方法。

CAKeyframeAnimation——关键帧动画

关键帧动画,也是CAPropertyAnimation的子类。

如果是简单的动画CABasicAnimation就能完成,CAKeyframeAnimation(关键帧动画)弥补了基本动画只能传入一对对应值的不足,关键帧动画支持传入一套数值或一个路径来完成动画。

⚠️与CABasicAnimation的区别是:
CABasicAnimation:只能从一个数值(fromValue)变到另一个数值(toValue)
CAKeyframeAnimation:会使用一个NSArray保存这些数值

🌰一个关键帧动画代码

CAKeyframeAnimation *animaiton = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];  
NSArray *rotationVelues = @[@(M_PI_4), @(-M_PI_4), @(M_PI_4)];  
animaiton.values = rotationVelues;  
animation.rotationMode = kCAAnimationRotateAuto;  //方向
animaiton.duration = 3.0f;  
animation.keyTimes = @[@0.2 ,@0.8 ,@1];
animation.path = bezierPath.CGPath;
animaiton.repeatCount = HUGE_VALF;     //   #define    HUGE_VALF    1e50f  
[self.imageView.layer addAnimation:animaiton forKey:nil];  

属性说明
values:上述的NSArray对象。里面的元素称为“关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
path:可以设置一个CGPathRef、CGMutablePathRef,让图层按照路径轨迹移动。path只对CALayer的anchorPoint和position起作用。如果设置了path,那么values将被忽略
keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧。如果没有设置keyTimes,各个关键帧的时间是平分的
bezierPath:贝赛尔曲线路径,为动画提供一个动画移动的路线。

UIBezierPath - 贝赛尔曲线
//创建路径
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:CGPointMake(0, 450)];
[bezierPath addCurveToPoint:CGPointMake(370, 500) controlPoint1:CGPointMake(350, 200) controlPoint2:CGPointMake(300, 600)]; //一个曲线     
//路径样式
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = bezierPath.CGPath;
shapeLayer.fillColor = [UIColor clearColor].CGColor; //填充色<默认黑色>
shapeLayer.strokeColor = [UIColor blueColor].CGColor; //线色
shapeLayer.lineWidth = 2;
[self.view.layer addSublayer:shapeLayer];

UIBezierPath 创建一个路径,画出一条曲线。栗子中 起点(0,450)、终点(370,500) 和 (350,200)、(370,500)来个点来确定线的路径。 模拟器 更多参考
CAShapeLayer 对上面的线进行属性上的设置。最后添加到一个layer上。
++CABasicAnimation可看做是只有2个关键帧的CAKeyframeAnimation++

CAAnimationGroup——动画组

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

属性说明
animations:用来保存一组动画对象的NSArray

CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = @[animation,basicAnimation];
animationGroup.duration = 4;
animation.repeatCount = 9;
[_imageLayer addAnimation:animationGroup forKey:@"changeColor"];

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

转场动画——CATransition

CATransition是CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点。
++UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果++

CATransition *caTransition = [CATransition animation];
caTransition.duration = 0.5;
caTransition.delegate = self;
caTransition.timingFunction = [CAMediaTimingFunction functionWithName:@"easeInEaseOut"];//切换时间函数
caTransition.type = kCATransitionReveal;//动画切换风格
caTransition.subtype = kCATransitionFromLeft;//动画切换方向

//    子视图交换位置
//[self.parentView exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
//动画在父视图
[self.parentView.layer addAnimation:caTransition forKey:@"Key"];

动画属性
type:动画过渡类型
subtype:动画过渡方向
startProgress:动画起点(在整体动画的百分比)
endProgress:动画终点(在整体动画的百分比)

//    导航栏切换
UIViewController *viewCtr = [[UIViewController alloc] init];
viewCtr.view.backgroundColor = [UIColor redColor];
[self.navigationController pushViewController:viewCtr animated:NO];// 动画设置 NO 效果比较好
[self.navigationController.view.layer addAnimation:caTransition forKey:@"animation"];

CALayer

它有一些方法和属性来做动画和变换,和UIView最大的不同是CALayer不处理用户的交互事件。

UIView和CALayer的关系 - 平行关系
  • 每个UIView都有一个CALayer实例的图层属性。
  • 视图的职责就是创建并管理这个图层。
  • UIView和CALyer有着平行的层级关系,职责分离。
图层能力

UIView的高级API可以实现一些简单的动画效果,但更多的细节和底层还需要通过CALayer直接处理CAAnimation来处理。

UIView能实现但CALayer不能实现
1.处理触摸事件
UIView没有暴露出来的CALayer的功能
1.阴影、圆角、带颜色的边框
2.3D变换
3.非矩形范围
4.透明覆盖
5.多级非线性动画


解释:为什么动画结束后返回原状态?
首先我们需要搞明白一点的是,layer动画运行的过程是怎样的?其实在我们给一个视图添加layer动画时,真正移动并不是我们的视图本身,而是 presentation layer 的一个缓存。动画开始时 presentation layer开始移动,原始layer隐藏,动画结束时,presentation layer从屏幕上移除,原始layer显示。这就解释了为什么我们的视图在动画结束后又回到了原来的状态,因为它根本就没动过。

这个同样也可以解释为什么在动画移动过程中,我们为何不能对其进行任何操作。

所以在我们完成layer动画之后,最好将我们的layer属性设置为我们最终状态的属性,然后将presentation layer 移除掉。


UIView中目前最常用的动画方法应该就是这个方法了
+(void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ nullable)(BOOL finished))completion ;

稍微复杂点的方法还是使用CALayer调用CAAnimation的API更为方便。


补充
- drawRect:
UIView的方法 没有默认实现

如果UIView检测到- drawRect:方法被调用,它会为视图分配一个寄宿图,这个寄宿图的尺寸等于视图大小乘以 contentsScale的值
- 会造成CPU资源和内存的浪费

- setNeedsDisplay
- drawRect:方法中利用CoreGraphics 去绘制一个寄宿图,然后被缓存起来直到它需要被更新,更新通常使用- setNeedsDisplay这个方法

CALayerDelegate
可以利用这个代理方法实现一些绘图,不必实现-displayLayer:-drawLayer:inContext:来绘制寄宿图。通常方法是实现 UIView的 - drawRect:方法,UIView就会帮你做完剩下的工作,包括在需要重绘的时候调用 - display方法


CALayer的子类
核心动画相关类的继承和简介

CAShapeLayer

CAShapeLayer是一个通过矢量图形而不是bitmap(位图)来挥之的图层子类。
你指定诸如颜色和线宽等属性,用CAPath来定义想绘制的图形,最后CAShapeLayer就自动渲染出来了。

优点
1.渲染快速。CAShapeLayer使用了硬件加速,绘制同一个图形会比用Core Graphics快很多。
2.高效使用内存。一个CAShapeLayer不需要像CALayer一样创建一个寄宿图,所以无论有多大,都不会占用大多的内存。
3.不会被图层边界裁剪掉。
4.不会出现像素化。当你给CAShapeLayer做3D变换时,它不像一个有寄宿图普通图层一样变得像素化。

创建一个CGPath
CAShapeLayer可以用来绘制所有能够通过CGPath来表示的形状。这个形状不一定要闭合,图层路径也不一定要不可破的,事实上你可以在一个图层上绘制好几个不同的形状。
你可以控制一些属性比如
lineWith(线宽,用点表示单位)、lineCap(线条结尾的样子)、和lineJoin(线条之间的结合点的样子)。

CAShapeLayer属性时CGPathRef类型,当时我们用 UIBezierPath 帮助类创建了图层路径,这样我们就不用考虑释放CGPath了。
🌰 一个火柴人

- (void)viewDidLoad {
    [super viewDidLoad];
    //create path
    UIBezierPath *path = [[UIBezierPath alloc] init];
    [path moveToPoint:CGPointMake(175, 100)];
    [path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:  2*M_PI clockwise:YES]; [path moveToPoint:CGPointMake(150, 125)];    [path addLineToPoint:CGPointMake(150, 175)]; 
    [path addLineToPoint:CGPointMake(125, 225)];
    [path moveToPoint:CGPointMake(150, 175)];
    [path addLineToPoint:CGPointMake(175, 225)];
    [path moveToPoint:CGPointMake(100, 150)];
    [path addLineToPoint:CGPointMake(200, 150)];
    //create shape layer
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.strokeColor = [UIColor redColor].CGColor;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.lineWidth = 5;
    shapeLayer.lineJoin = kCALineJoinRound;
    shapeLayer.lineCap = kCALineCapRound;
    shapeLayer.path = path.CGPath;
    //add it to our view
    [self.containerView.layer addSublayer:shapeLayer];
  } 

圆角
我们创建一个圆角矩形其实就是人工绘制单独的直线和弧度,但是事实上UIBezierPath有自动绘制圆角矩形的构造法。

🌰绘制一个有三个圆角一个直角的矩形

//define path parametersCGRect rect = CGRectMake(50, 50, 100, 100);CGSize radii = CGSizeMake(20, 20);UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft; //create pathUIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerR 

我们可以通过这个图层路径绘制一个既有直角又有圆角的视图。

CATiledLayer

载入大图片可能会相当地慢,那些对你看上去比较方便的做法(在主线程调用UIImage的-imageNamed: 方法或者-imageWithContentsOfFile:方法) 将会阻塞你的界面,至少会引起动画卡顿现象。

CATiledLayer为载入大图造成的性能问题提供了一个解决方案:将大图分解成小片然后将它们单独的载入。

🌰iOS上小图片拼成大图
略。。。在iOS上要先有n多的小图片,然后通过CATiledLayer把图片放对应位置,实现显示大图片。 比如地图的实现。
核心方法-drawLayer:inContext:

使用CALayer加载图片千万注意使用
tileLayer.contentsScale = [UIScreen mainScreen].scale;
保证图片清晰

CAMediaTiming -- 图层时间

CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性集合,CALayer和CA Animation都实现了这个协议,所以时间可以背任何基于一个图层或者一段动画的类控制。

—这个协议中有很多关于时间的属性:开始时间,速度,次数。。。

->持续和重复
duration:一个动画的迭代时间 CFTimeInterval 类型<类似双精度浮点型>
repeatCount:动画重复的迭代次数
⚠️这两个值默认值都是0,分别代表0.25s和1次

->相对时间
beginTIme:指定了动画开始之前的延迟。
speed:是一个时间的倍数,默认1。
timeoffset:对于一个持续1s的动画来说,设置timeoffset为0.5意味着动画将从一半的地方开始。
🌰 <1s动画 speed = 2,time offset = 0.5 -> 动画变成:时长0.5s,从0.5s开始 >

fillmode

removeOnCompletion被设置为NO的动画将在动画结束的时候仍然保持之前的状态。

动画开始之前和动画结束之后,被设置的动画属性将会是什么值呢:
保持动画开始之前的那一帧,或者动画结束后的那一帧。
这就是所谓的填充 因为动画开始和结束的值来填充开始之前和结束之后的时间。
fillMode 是一个NSString类型

kCAFillModeForwards
kCAFillModeBackwards
kCAFillModeBoth
kCAFillModeRemoved 默认

⚠️需要把removeOnCompletion设置为NO,另外需要给动画添加一个非空的键,于是可以在不需要动画的时候把它从图层上移除。

CAMediaTimeingFunction 缓冲

+ timingFunctionWithName:

kCAMediaTimingFunctionLinear 快速开始然后匀速。
kCAMediaTimingFunctionEaseIn 慢慢加速后突然停止。
kCAMediaTimingFunctionEaseOut 全速开始后慢慢停止。
kCAMediaTimingFunctionEaseInEaseOut 慢慢开始然后慢慢减速。
kCAMediaTimingFunctionDefault

UIView的动画方法,kCAMediaTimingFunctionEaseInEaseOut是默认的,但当创建CAAnimation的时候,就需要手动设置了。


贝赛尔曲线

一个三次贝赛尔曲线由4个点来定义:
第一个和最后一个:代表曲线的起点和终点。
剩下的两个点:叫做控制点。<贝赛尔曲线的控制点其实是位于曲线之外的点,也就是说曲线并不一定要穿过他们。你可以把它们想象成吸引经过它们曲线的磁铁>

实际上它是一个很奇怪的函数,先加速-减速-加速。

CAMediaTimeingFunction

getControlPointAtIndex:values:可以用来检索曲线的点
使用它我们可以找到标准的缓冲的点,然后用UIBezierPath和CAShapeLayer把它画出来。

曲线的起点和终点始终是{0,0}和{1,1},于是我们只需要检索曲线的第二个和第三个点。


定时器动画

NSTimer
工作原理:iOS上的每一个线程都管理了一个NSRunloop,字面上看就是通过一个循环来完成一些任务列表。

但对主线程,这些任务包涵如下几项:
-处理触摸事件
-发送和接收网络数据包
-执行使用gcd的代码
-处理计时器行为
-屏幕绘制

当你设置一个NSTimer,它会被插入到当前任务列表,然后知道指定时间过去之后才会被执行。但是何时启动定时器并没有一个时间上限,而且它只会在列表中上一个任务完成之后开始执行。
这通常会导致有几毫秒的延迟,但是如果上一个任务过了很久完成就导致延迟很长一段时间。

屏幕重绘的频率是一秒六十次,但适合定时器行为一样,如果列表中上一个执行很长时间,它也会延迟。

这个延迟是一个随机值,于是就不能保证定时器精准的一秒执行六十次。

我们可以通过一些途径来优化
-我们可以用CADisplayLink 让更新频率严格控制在每次屏幕刷新之后。
-基于真实帧的持续时间而不失假设的更新频率来做动画。
-调整动画计时器的runloop模式,这样就不会被别的事情干扰。


隐式动画

Core Animation基于一个假设,说屏幕上的任何东西都是可以(或可能) 做动画。
动画并不需要你在Core Animation中手动打开,相反需要明确的关闭,否者它一只存在。

当你改变CALayer的一个可以做动画的属性,它并不能在屏幕上体现出来。相反,它是从先前的值平滑过渡到新的值。这一切都是默认的行为,你不需要做额外的操作。

🌰 一个View上放一个CALayer,点击按钮改变layer的颜色<颜色是渐变过去的>

这就是隐式动画,之所以叫隐式动画是因为我们并没有指定任何的动画类型。我们仅仅改变了一个属性,然后Core Animation来决定如何并且何时去动画。

CATransaction 管理事务的类
+begin 入栈
+commit 出栈
+setAnimationDuration: 设置当前动画的时间
+AnimationDuration 获取动画时间

Core Animation在每个RUNLOOP周期中自动开始一次新的事物。
RUNROOP:iOS中负责收集用户输入,处理定时器或者网络事件并且重新绘制屏幕的东西。理解:涉及到任何主动或被动的操作改变都会唤醒run loop。
即使不显式的用[CATransaction begin] 开始一次事务,任何在一次run loop循环中属性的改变都会被集中起来,然后做一次0.25秒的动画。

UIView中的layer属性 并不存在 隐形动画

总结
1.UIView关联的图层禁用了隐形动画,对这种图层做动画的唯一方法就是使用IUView的动画函数(而不是依赖CATransaction),或者继承UIView,并覆盖 -actionForLayer:forKey: 方法,或者直接创建一个显性动画。

2.对于独立的图层,我们可以通过实现图层的 -actionForLayer:forKey:委托方法,或者提供一个actions字典来控制隐形动画

显式动画

animationDidStop: finished:
代码demo
属性动画
基础动画

CABasicAnimation *animation = [CABasicAnimation animation]; animation.keyPath = @"backgroundColor";animation.toValue = (__bridge id)color.CGColor;animation.delegate = self;//apply animation to layer[self.colorLayer addAnimation:animation forKey:nil]; 

关键帧路径动画

CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
animation.duration = 4.0;
animation.path = bezierPath.CGPath;
animation.rotationMode = kCAAnimationRotateAuto;
[shipLayer addAnimation:animation forKey:nil];

动画组
CABasicAnimation和CAKeyframeAnimation仅仅是作用于单独的属性。
CAAnimationGroup可以把这些动画组合在一起 <CAAnimation 子类>

过渡
属性动画只对图层的可动画属性属性起作用

所以如果要改变一个不能动画的属性(比如图片),或者从层级关系中添加或者移除图层,属性动画将不起作用。

过渡:过渡并不像属性动画那样平滑的在两个值之间做动画,而是影响到整个图层的变化
。过渡动画首先展示之前的图层外观,然后通过一个交换过渡到新的外观。

CATransition:CAAnimation子类
CATransition有个type和subtype来识别变换效果,类型 NSString

type
kCATransitionFade  //默认  淡入淡出效果
kCATransitionMoveIn  //新图片滑动进入,直接覆盖旧的图片
kCATransitionPush //边缘的一侧进来,把旧的图层从另一侧推出去
kCATransitionReveal //旧图片滑出去,来显示新图片
subtype
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom

在想:控制器之间的过场动画是不是能用这个实现。


仿射变换

UIView可以通过transform属性做变换,transform是一个CGAffineTransform类型

CGAffineTransform 是用矩阵相乘的方法实现仿射变换
CGAffineTransformMakeRotation(CGFloat angle) 旋转
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy) 缩放
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty) 位移

CALayer 同样又个transform属性,transform的属性是CATransform3D
CALayer对应UIView的transform属性叫做affineTransform

混合变换

当操纵一个变换的时候,初始生成一个什么都不做的变换很重要-也就是创建一个CGAffineTransform类型的空值。<CGAffineTransformIdentity>

CGAffineTransformRotate(CGAffineTransform t, CGFloat angle) CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy) CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)

最后,如果需要混合两个已经存在的矩阵,就可以使用下面方法,在两个基础上新建一个变换
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);

🌰❌
先缩小50%-再旋转30度-最后向右移动200像素

-  (void)viewDidLoad
{
     [super viewDidLoad]; //create a new transform      CGAffineTransform transform = CGAffineTransformIdentity; //scale by 50%transform = CGAffineTransformScale(transform, 0.5, 0.5); //rotate by 30 degreestransform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0); //translate by 200 points transform = CGAffineTransformTranslate(transform, 200, 0);//apply transform to layer
    self.layerView.layer.affineTransform = transform; 
 } 

结果并不是预期的结果:这意味着变换的顺序会影响最终的结果,也就是说旋转之后的平移和平移之后的的旋转结果可能不同

CG - Core Graphics 框架 :严格意义上说是2D绘图API
CGAffineTransform 仅仅对2D变换有效

3D变换

CALayer的transform属性是CATransform3D类型,就能实现3D空间内移动或者旋转
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz) CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)

想象x、y、z向旋转,缩放、位移的效果
🌰
绕Y轴做45度角的旋转

- (void)viewDidLoad
{
    [super viewDidLoad];
    //rotate the layer 45 degrees along the Y axis
    CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
    self.layerView.layer.transform = transform;
} 

sublayerTransform :CALayer的属性,可以对所有子视图操作


比较早整理的现在看来排版有点乱,见谅。
谢谢阅读。

相关文章

网友评论

    本文标题:iOS动画合集

    本文链接:https://www.haomeiwen.com/subject/idpyoxtx.html