IOS基础:动画
原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 比较
- 一、UIView 动画
- 1、简介
- 2、演示
- 二、Core Animation
- 1、简介
- 2、CABasicAnimation
- 3、CAKeyframeAnimation
- 4、CATransition
- 5、CASpringAnimation
- 6、CAAnimationGroup
- 7、CADisplayLink
- Demo
- 参考文献
比较
iOS系统本身提供了两套绘图的框架,即UIBezierPath
和 Core Graphics
。前者所属UIKit
,其实是对Core Graphics
框架关于path
的进一步封装,所以使用起来比较简单。但是毕竟Core Graphics
更接近底层,所以它更加强大。我们使用UIView的 -(void)drawRect:
方法绘图就是使用的CoreGraphics
框架。
CoreGraphics(核心图形)
它是iOS的核心图形库,包含Quartz2D
绘图API
接口,常用的CGFloat
、CGSize
、CGPoint
等这些图形,都定义在这个框架中,类名以CG
开头的都属于CoreGraphics
框架,它提供的都是C
语言函数接口,是可以在iOS
和mac OS
通用的。
CoreAnimation(核心动画)
用来做动画的,非常的简单,但是效果非常绚丽。
-
CoreAnimation
是跨平台的,既可以支持IOS
,也支持MAC OS
。 -
CoreAnimation
执行动画是在后台,不会阻塞主线程。 -
CoreAnimation
作用在CALayer
,不是UIView
。
CoreGraphics和CoreAnimation的关系
它们都是跨iOS
和Mac OS
使用的,这点区别于UIKit
,并且- CoreAnimation
中大量使用到CoreGraphics
中的类,因为实现动画要用到图形库中的东西。
一、UIView 动画
1、简介
比较
- 简单的动画不会直接使用
Core Animation
框架,而是使用UIView
动画,本质上也是Core Animation
框架实现的,只是进行了封装和优化 - 每个视图都关联到一个图层
(CALayer)
对象,视图主要用来处理事件,图层用来处理动画 - 视图有一系列支持动画的属性,如
frame
、bounds
、center
、alpha
、transform
等,其他如动画延迟事件、动画曲线(-in
缓入out
缓出linear
线性匀速)、动画过渡、重复次数、自动反转(FlipFromRight
从右往左翻转FlipFromBottom
从下往上翻转CurlUp
向上翻页CurlDown
向下翻页) -
options
选项类型中成员值是位掩码,位或运算结果时两种效果叠加,如options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationTransitionFlipFromRight
即过渡动画以缓出速度从左往右翻转
常用枚举类型
视图动画曲线 UIViewAnimationCurve
视图内容填充模式 UIViewContentMode
视图动画过渡效果 UIViewAnimationTransition
视图自动调整大小方式 UIViewAutoresizing
视图的动画选项 UIViewAnimationOptions
视图关键帧动画选项 UIViewKeyframeAnimationOptions
视图的系统动画 UISystemAnimation
布局约束的轴 UILayoutConstraintAxis(水平还是竖直)
常见视图动画扩展
UIViewGeometry:视图几何相关的扩展
UIViewHierarchy:视图层次结构相关的扩展
UIViewRendering:视图外观渲染相关的扩展,例如是否隐藏、透明度、背景颜色等
UIViewAnimation:视图动画相关的扩展
UIViewAnimationWithBlocks:视图用block快速定义动画的扩展
UIViewKeyframeAnimations:视图关键帧动画相关的扩展
UIViewGestureRecognizers:视图上手势相关的扩展
UIViewMotionEffects:视图上运动效果相关的扩展
UIConstraintBasedLayoutCoreMethods:视图上约束相关的扩展
UISnapshotting:视图快照相关的扩展
2、动画演示
动画演示 动画演示 动画演示 动画演示- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// 红方块起始frame
if (CGRectEqualToRect(self.redOriginFrame, CGRectZero))
{
self.redOriginFrame = self.redView.frame;
}
// 蓝方块起始frame
if (CGRectEqualToRect(self.blueOriginFrame, CGRectZero))
{
self.blueOriginFrame = self.blueView.frame;
}
switch (indexPath.item)
{
case 0:// 重置
{
self.redView.frame = self.redOriginFrame;// 红方块起始frame
self.redView.alpha = 1.0;// 透明
self.redView.transform = CGAffineTransformIdentity;// 移动
self.redView.backgroundColor = [UIColor redColor];
self.blueView.frame = self.blueOriginFrame;// 蓝方块起始frame
break;
}
case 1:// 移动红色方块
{
CGRect redFrame = self.redView.frame;
// 下移100点
redFrame.origin.y += 100;
// 产生动画效果
[UIView animateWithDuration:UIAnimationDuration animations:^{
// 红方块新的frame
self.redView.frame = redFrame;
}];
break;
}
case 2:// 缩小红色方块,蛮奇怪的,它放大以后再缩小,我明明是减小宽高
{
CGRect redFrame = self.redView.frame;
// 缩小宽高
redFrame.size.width -= 50;
redFrame.size.height -= 50;
// 产生动画效果
[UIView animateWithDuration:UIAnimationDuration animations:^{
// 红方块新的frame
self.redView.frame = redFrame;
}];
break;
}
case 3:// 旋转红色方块
{
// 获取初始transform属性
CGAffineTransform transform = self.redView.transform;
// 旋转90度
transform = CGAffineTransformRotate(transform, M_PI/4);
// 产生动画效果
[UIView animateWithDuration:UIAnimationDuration animations:^{
// 红方块新的transform属性
self.redView.transform = transform;
}];
break;
}
case 4:// 改变红色为紫色
{
[UIView animateWithDuration:UIAnimationDuration animations:^{
self.redView.backgroundColor = [UIColor purpleColor];
}];
break;
}
case 5:// 改变透明度为半透明
{
[UIView animateWithDuration:UIAnimationDuration animations:^{
self.redView.alpha = 0.5;
}];
break;
}
case 6:// 移动红色方块并同时旋转90度
{
// 下移
CGRect redFrame = self.redView.frame;
redFrame.origin.y += 100;
// 旋转
CGAffineTransform transform = self.redView.transform;
transform = CGAffineTransformRotate(transform, M_PI/2);
// 产生动画效果
[UIView animateWithDuration:UIAnimationDuration animations:^{
// 新的transform和frame属性
self.redView.frame = redFrame;
self.redView.transform = transform;
}];
break;
}
case 7:// 先移动后旋转
{
// 下移
CGRect redFrame = self.redView.frame;
redFrame.origin.y += 100;
// 旋转
CGAffineTransform transform = self.redView.transform;
transform = CGAffineTransformRotate(transform, M_PI/2);
[UIView animateWithDuration:UIAnimationDuration animations:^{// 下移动画
self.redView.frame = redFrame;
} completion:^(BOOL finished) {// 完成后进入旋转动画
[UIView animateWithDuration:UIAnimationDuration animations:^{
self.redView.transform = transform;
} completion:^(BOOL finished) {// 完成后输出旋转完成
NSLog(@"旋转完成");
}];
}];
break;
}
case 8:// 通过中心下移
{
CGPoint center = self.redView.center;
// 下移100点
center.y += 100;
[UIView animateWithDuration:UIAnimationDuration animations:^{
// 新的中心
self.redView.center = center;
}];
break;
}
case 9:// 渐变方式进行缩放
{
// 缩放一半
CGAffineTransform transform = self.redView.transform;
transform = CGAffineTransformScale(transform, 0.5, 0.5);
// 产生动画效果
[UIView animateWithDuration:UIAnimationDuration delay:1.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
// 新的transform属性
self.redView.transform = transform;
} completion:^(BOOL finished) {
NSLog(@"缩放完成");
}];
break;
}
case 10:// 通过中心让蓝色和红色方块同时右移
{
CGPoint redCenter = self.redView.center;
CGPoint blueCenter = self.blueView.center;
// 下移100点
redCenter.x += 100;
blueCenter.x += 100;
// 产生动画效果
[UIView animateWithDuration:UIAnimationDuration animations:^{
self.redView.center = redCenter;
self.blueView.center = blueCenter;
}];
break;
}
case 11:// 重复来回移动
{
CGPoint center = self.redView.center;
// 下移100点
center.y += 100;
// 重复/自动逆向
[UIView animateWithDuration:UIAnimationDuration delay:0 options:UIViewAnimationOptionRepeat|UIViewAnimationOptionAutoreverse animations:^{
self.redView.center = center;
} completion:nil];
break;
}
default:
break;
}
}
二、Core Animation
1、简介
a、Core Animation
区别
UIView
动画可以看成是对Core Animation
的封装,和UIView
动画不同的是,通过Core Animation
改变layer
的状态(比如position
),动画执行完毕后实际上是没有改变的(表面上看起来已改变)。
优点
1)性能强大,使用硬件加速,可以同时向多个图层添加不同的动画效果
2)接口易用,只需要少量的代码就可以实现复杂的动画效果
3)运行在后台线程中,在动画过程中可以响应交互事件(UIView
动画默认动画过程中不响应交互事件)
层次结构
-
CAAnimation:所有动画对象的父类,实现
CAMediaTiming
协议,负责控制动画的时间、速度和时间曲线等等,是一个抽象类,不能直接使用 -
CAPropertyAnimation:支持动画地显示图层的
keyPath
,一般不直接使用 - CASpringAnimation:实现弹簧效果的动画
-
CAAnimationGroup:
Group
中所有动画并发执行,可以方便地实现需要多种类型动画的场景 -
CATransition:转场动画从一个场景以动画的形式过渡到另一个场景,如
Push
b、CABasicAnimation 和 CAKeyframeAnimation
a、简介
都是CAPropertyAnimation
的子类,通过描绘路径来形成动画。CABasicAnimation
通过设定起始点,终点,时间,可以看成是只有两个点的特殊的CAKeyFrameAnimation
动画。CAKeyFrameAnimation
则可以设置路径为更多的点构成的路径,动画会沿着我们设置的多个点进行移动
常用属性
-
duration
:动画的持续时间 -
repeatCount
:动画持续次数,最大次数用MAXFLOAT
-
repeatDuration
:设置动画的时间,在该时间内动画一直执行,不计次数 -
beginTime
:指定动画开始的时间。从开始延迟几秒的话,设置为CACurrentMediaTime() +
秒数的方式 -
timingFunction
:设置动画的速度变化,timingFunctions
同keyTimes
的含义,设置每一小段路径上的动画的变化速率(CAKeyframeAnimation
独有)
kCAMediaTimingFunctionLinear:匀速运动,默认函数,立即加速并且保持匀速到达终点的场景会有意义(例如射出枪膛的子弹)
kCAMediaTimingFunctionEaseIn:一个慢慢加速然后突然停止的方法。对于自由落体的例子来说很适合,或者对准一个目标的导弹的发射。
kCAMediaTimingFunctionEaseOut:以一个全速开始,然后慢慢减速停止 有一个削弱的效果,应用的场景比如一扇门慢慢地关上,而不是砰地一声
kCAMediaTimingFunctionEaseInEaseOut:慢慢加速然后再慢慢减速的过程
kCAMediaTimingFunctionDefault
-
fillMode
:动画在开始和结束时的动作,默认值是kCAFillModeRemoved
动画结束时是否执行逆动画
kCAFillModeForwards :为了使动画结束之后layer保持结束状态,应将removedOnCompletion设置为NO
kCAFillModeRemoved:动画将在设置的 beginTime 开始执行(如没有设置beginTime属性,则动画立即执行),动画执行完成后将会layer的改变恢复原状。
-
fromValu toValue byValue
:所改变属性的起始值、结束值、改变量(CABasicAnimation
独有) -
values
:关键帧数组对象,里面每一个元素即为一 个关键帧,动画会在对应的时间段内,依次执行数组中每一个关键帧的动画(CAKeyframeAnimation
独有) -
keyTimes
:上面values
设定了路径上的关键点,本参数则设定关键点之间的路径段上所需的时间,所以-keyTimes
的个数应该比values
的个数小1 (CAKeyframeAnimation
独有 ) -
path
:可以直接设置动画路径(CAKeyframeAnimation
独有)
c、CATransition
-
type
:过渡动画的类型 -
subtype
:设置转场方向 -
startProgress
:开始进度,默认0.0.如果设置0.3,那么动画将从动画的0.3的部分开始 -
endProgress
:结束进度,默认1.0.如果设置0.6,那么动画将从动画的0.6部分以后就会结束
d、CASpringAnimation
-
mass
:质量(影响弹簧的惯性,质量越大,弹簧惯性越大,运动的幅度越大) -
stiffness
:弹性系数(弹性系数越大,弹簧的运动越快) -
damping
:阻尼系数(阻尼系数越大,弹簧的停止越快) -
initialVelocity
:初始速率(弹簧动画的初始速度大小,弹簧运动的初始方向与初始速率的正负一致,若初始速率为0,表示忽略该属性) -
settlingDuration
:结算时间(根据动画参数估算弹簧开始运动到停止的时间,动画设置的时间最好根据此时间来设置)
2、CABasicAnimation
a、CABasicAnimation的渐变加载Demo
加载LoadingView.h
// view宽度必须使用此值
FOUNDATION_EXPORT const CGFloat LoadingViewSize;
// 全局loading
@interface LoadingView : UIView
/** 开始动画 */
- (void)startAnimating;
/** 结束动画 */
- (BOOL)isAnimating;
@end
LoadingView.m
const CGFloat LoadingViewSize = 200.0;// 加载视图的大小
const CGFloat LoadingLayerWidth = 80.0;// 加载图层的宽度
@interface LoadingView ()<CAAnimationDelegate>
// 是否正在动画
@property (nonatomic, assign, getter=isAnimating) BOOL animating;
// 用于显示加载效果的渐变图层
@property (nonatomic, strong) CAGradientLayer *loadingLayer;
@end
@implementation LoadingView
@end
绘图
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.5];
self.layer.cornerRadius = 3.0;
_animating = NO;// 未启动动画
// 绘图
[self drawLoadingLayer];
}
return self;
}
- (void)drawLoadingLayer
{
CGSize size = self.bounds.size;
CGPoint center = CGPointMake(size.width/2, size.height/2);// 圆心
CGFloat radius = LoadingLayerWidth/2;// 半径
CGFloat strokeWidth = 2.0;// 描边宽度
CGFloat angleDelta = M_PI / 2;// 角度变化量
// 1.创建渐变图层
// 初始化渐变图层
_loadingLayer = [CAGradientLayer layer];
_loadingLayer.frame = CGRectMake(center.x - LoadingLayerWidth/2, center.y - LoadingLayerWidth/2, LoadingLayerWidth, LoadingLayerWidth);
[self.layer addSublayer:_loadingLayer];
// 填充色不可用带透明度的颜色(会导致颜色叠加),要使用实色
_loadingLayer.colors = @[(id)[UIColor whiteColor].CGColor, (id)[UIColor whiteColor].CGColor, (id)[UIColor redColor].CGColor];
_loadingLayer.locations = @[@0.0, @0.5, @1.0];// 关键位置
_loadingLayer.startPoint = CGPointMake(0.5, 0.0);// 开始点
_loadingLayer.endPoint = CGPointMake(0.5, 1.0);// 结束点
_loadingLayer.type = kCAGradientLayerAxial;// 线性渐变
// 2.创建圆圈形状
// 创建外部弧
center = CGPointMake(radius, radius);
UIBezierPath *maskPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:angleDelta endAngle:angleDelta*4 clockwise:YES];
// 添加右边弧
CGFloat strokeRaduis = strokeWidth / 2;
[maskPath addArcWithCenter:CGPointMake(center.x+radius-strokeRaduis, center.y) radius:strokeRaduis startAngle:0 endAngle:angleDelta*2 clockwise:YES];
// 添加内部弧
[maskPath addArcWithCenter:center radius:(radius-strokeWidth) startAngle:angleDelta*4 endAngle:angleDelta clockwise:NO];
// 添加底边弧
[maskPath addArcWithCenter:CGPointMake(center.x, center.y+radius-strokeRaduis) radius:strokeRaduis startAngle:-angleDelta endAngle:angleDelta clockwise:YES];
// 完成路径绘制,闭合路径
[maskPath closePath];// 注释掉好像也没影响
// 添加路径到形状图层上,_loadingLayer没有path属性,只有mask属性
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = _loadingLayer.bounds;// 二者重合
maskLayer.path = maskPath.CGPath;
// 3.将形状图层作为渐变图层的遮罩,不加这句得到的只是个旋转的矩形
_loadingLayer.mask = maskLayer;
}
配置动画
// 展示视图动画
- (void)showAnimation
{
// 透明度动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = @(0.0);// 初始值
animation.toValue = @(1.0);// 结束值
animation.removedOnCompletion = NO;// 完成后不移除
animation.duration = 0.3;// 持续0.3秒
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];// 渐渐退出
// 添加到视图图层中
[self.layer addAnimation:animation forKey:@"showAnimation"];
}
// 隐藏视图动画
- (void)hideAnimation
{
// 透明度动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = @(1.0);// 初始值
animation.toValue = @(0.0);// 结束值
animation.removedOnCompletion = NO;// 完成后不移除
animation.duration = 0.3;// 持续0.3秒
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];// 渐渐退出
// 添加到视图图层中
[self.layer addAnimation:animation forKey:@"hideAnimation"];
}
// 加载视图动画
- (void)loadingAnimating
{
// 绕Z轴旋转动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation.fromValue = @(0);// 初始值
animation.toValue = @(M_PI*2);// 结束值
animation.removedOnCompletion = NO;// 完成后不移除
animation.repeatCount = HUGE_VALF;// 重复无限次
animation.duration = 1.0;// 持续1秒
// 添加到加载图层中
[_loadingLayer addAnimation:animation forKey:@"loadingAnimating"];
}
操作
// 开始动画
- (void)startAnimating
{
// 已经在动画状态中则直接返回
if (_animating)
{
return;
}
// 清除之前的旧动画
[self cleanAnimations];
// 设置为动画状态
_animating = YES;
// 不再隐藏视图
self.hidden = NO;
// 展示视图动画
[self showAnimation];
// 加载视图动画
[self loadingAnimating];
}
// 停止动画
- (void)stopAnimating
{
// 清除动画
[self cleanAnimations];
// 隐藏视图
self.hidden = YES;
// 设置为非动画状态
_animating = NO;
}
// 清除动画
- (void)cleanAnimations
{
[self.layer removeAnimationForKey:@"showAnimating"];// 移除显示动画
[self.layer removeAnimationForKey:@"hideAnimating"];// 移除隐藏动画
[_loadingLayer removeAnimationForKey:@"loadingAnimating"];// 移除加载动画
}
使用
// 渐变
- (void)createLoadingView
{
// 创建视图
self.loadingView = [[LoadingView alloc] initWithFrame:CGRectMake(100, 300, LoadingViewSize, LoadingViewSize)];
[self.view addSubview:self.loadingView];
// 开始动画
[self.loadingView startAnimating];
}
3、CAKeyframeAnimation
a、8个球加载Demo
8个球BallsLoadingView.h文件
FOUNDATION_EXPORT const CGFloat BallsLoadingLayerSize;
@interface BallsLoadingView : UIView
/** 开始动画 */
- (void)startAnimation;
/** 结束动画 */
- (void)stopAnimation;
@end
BallsLoadingView.m文件
const CGFloat BallsLoadingLayerSize = 100.0;// 加载图层的大小
@interface BallsLoadingView ()
@property (nonatomic, strong) CALayer *loadingLayer;// 加载图层
@end
@implementation BallsLoadingView
@end
绘图
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self drawInnerLoadingLayer];
}
return self;
}
// 加载视图为8个球
- (void)drawInnerLoadingLayer
{
CGSize size = self.bounds.size;
CGFloat radius = size.width/2;// 半径
CGFloat dotRadius = 7.5 * (size.width/BallsLoadingLayerSize);// 球的半径
CGFloat postionRadius = radius - dotRadius;// 距离
CGFloat angleDelta = M_PI / 4;// 角度变化量
// 1.创建加载图层
_loadingLayer = [CALayer layer];
_loadingLayer.frame = self.bounds;
[self.layer addSublayer:_loadingLayer];
// 2.绘制8个球
UIColor *ballColor = [UIColor blueColor];// 球的颜色
for (int i=7; i>=0; I--)
{
// 计算每个球的圆心
CGFloat postionX = radius + postionRadius * cos(angleDelta*i);
CGFloat postionY = radius + postionRadius * sin(angleDelta*i);
// 绘制每个球的路径
UIBezierPath *dotPath = [UIBezierPath bezierPath];
[dotPath addArcWithCenter:CGPointZero radius:dotRadius startAngle:0 endAngle:M_PI*2 clockwise:YES];
[dotPath closePath];
// 创建形状图层
CAShapeLayer *dotLayer = [CAShapeLayer layer];
dotLayer.position = CGPointMake(postionX, postionY);// 球的放置位置
dotLayer.anchorPoint = CGPointZero;// 球的锚点
dotLayer.path = dotPath.CGPath;// 绘制路径
dotLayer.fillColor = ballColor.CGColor;// 填充颜色
// 将形状图层添加到加载图层上
[_loadingLayer addSublayer:dotLayer];
}
}
配置动画
// 小球依次缩放和透明
- (void)configureAnimation
{
// 获取加载图层中的每个球图层
NSArray<CALayer *> *sublayers = _loadingLayer.sublayers;
// 球的数量
NSUInteger count = sublayers.count;
// 依次延迟0.12秒展示
CGFloat delayDelta = 0.12;
// 依次为每个球添加动画
for (int i=0; i<count; I++)
{
// 获取每个球的图层
CALayer *subLayer = sublayers[I];
NSArray *scales = @[@1.0, @0.4, @1.0];// 缩放 1->0.4->1
NSArray *opacities = @[@1.0, @0.3, @1.0];// 透明度 1->0.3->1
NSArray *keyTimes = @[@0, @0.5, @1.0];// 关键时刻 0->0.5->1
CGFloat duration = 2.0;// 时长
// xScale: X轴上的缩放动画
CAKeyframeAnimation *xScaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.x"];
xScaleAnimation.values = scales;
xScaleAnimation.keyTimes = keyTimes;
xScaleAnimation.duration = duration;
// yScale: Y轴上的缩放动画
CAKeyframeAnimation *yScaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.y"];
yScaleAnimation.values = scales;
yScaleAnimation.keyTimes = keyTimes;
yScaleAnimation.duration = duration;
// opacity: 透明度动画
CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.values = opacities;
opacityAnimation.keyTimes = keyTimes;
opacityAnimation.duration = duration;
// 组合动画
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[xScaleAnimation, yScaleAnimation, opacityAnimation];
groupAnimation.duration = duration;
groupAnimation.removedOnCompletion = NO;// 完成后不移除动画
groupAnimation.repeatCount = HUGE_VALF;// 无限次重复
groupAnimation.beginTime = delayDelta * i;// 依次延迟0.12秒展示
// 将组合动画添加到球图层上
[subLayer addAnimation:groupAnimation forKey:@"groupAnimation"];
}
}
操作
// 开始动画
- (void)startAnimation
{
[self configureAnimation];
}
// 结束动画
- (void)stopAnimation
{
// 获取加载图层中的每个球图层
NSArray<CALayer *> *sublayers = _loadingLayer.sublayers;
NSUInteger count = sublayers.count;
for (int i=0; i<count; I++)
{
// 获取每个球的图层
CALayer *subLayer = sublayers[I];
// 移除球图层上的所有动画
[subLayer removeAllAnimations];
}
}
使用
// 8个球
- (void)createBallsLoadingView
{
// 创建视图
self.ballsLoadingView = [[BallsLoadingView alloc] initWithFrame:CGRectMake(150, 300, BallsLoadingLayerSize, BallsLoadingLayerSize)];
[self.view addSubview:self.ballsLoadingView];
// 开始动画
[self.ballsLoadingView startAnimation];
}
4、CATransition
a、过渡到蓝色视图
过渡到蓝色视图TransitionViewController
// 过渡动画视图
@interface TransitionViewController : UIViewController
@property (nonatomic, strong) UIView *blueView;
@end
@implementation TransitionViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.blueView = [[UIView alloc] initWithFrame:CGRectZero];
self.blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:self.blueView];
[self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
make.left.equalTo(self.view.mas_left);
make.right.equalTo(self.view.mas_right);
}];
}
@end
CoreAnimationViewController
// 过渡动画
- (void)transitionAnimation
{
TransitionViewController *transitonVC = [[TransitionViewController alloc] init];
[self.navigationController pushViewController:transitonVC animated:NO];
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;// Push方式
transition.subtype = kCATransitionFromLeft;// 从左部滑出
// 在图层上添加动画
[transitonVC.view.layer addAnimation:transition forKey:@"transition"];
}
5、CASpringAnimation
6、CAAnimationGroup
a、波纹动画
波纹动画RippleLayer.h文件
#import <QuartzCore/QuartzCore.h>
@interface RippleLayer : CALayer
/** 开始动画 */
- (void)startAnimation;
/** 结束动画 */
- (void)stopAnimation;
@end
RippleLayer.m文件
#import <FLAnimatedImage/FLAnimatedImage.h>
@interface RippleLayer ()<CAAnimationDelegate>// 动画委托
// 计时器
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation RippleLayer
@end
创建波纹图
- (CAShapeLayer *)rippleLayer
{
// 路径
CGSize size = self.bounds.size;
CGFloat raduis = size.width/2;// 半径
CGPoint center = CGPointMake(size.width/2, size.height/2);// 圆心
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:raduis startAngle:0 endAngle:M_PI*2 clockwise:YES];// 圆
[path closePath];// 闭合
UIColor *strokeColor = [UIColor colorWithRed:189.0/255 green:141.0/255 blue:4.0/255 alpha:0.45];// 描边颜色
UIColor *fillColor = [UIColor colorWithRed:189.0/255 green:141.0/255 blue:4.0/255 alpha:0.08];// 填充颜色
// 图层
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = path.CGPath;
layer.lineWidth = 0.5;
layer.strokeColor = strokeColor.CGColor;
layer.fillColor = fillColor.CGColor;
// 勿改变设置顺序
layer.anchorPoint = CGPointMake(0.5, 0.5);// 铆点 0~1 这里指视图中心
layer.frame = self.bounds;
layer.transform = CATransform3DMakeScale(1.5, 1.5, 1.0);// 缩放
layer.opacity = 0.0;// 透明
return layer;
}
配置波纹动画:透明度 + 缩放
- (void)fireRippleAnimation
{
// 添加轮胎图层
CAShapeLayer *layer = [self rippleLayer];
[self addSublayer:layer];
// scalePath
UIBezierPath *scalePath = [UIBezierPath bezierPath];
[scalePath moveToPoint:CGPointMake(1.5, 0)];
[scalePath addQuadCurveToPoint:CGPointMake(1.8, 1.0) controlPoint:CGPointMake(1.2, 0.2)];
[scalePath addQuadCurveToPoint:CGPointMake(3.0, 3.0) controlPoint:CGPointMake(3.0, 2.5)];
// opacityPath
UIBezierPath *opacityPath = [UIBezierPath bezierPath];
[opacityPath moveToPoint:CGPointZero];
[opacityPath addLineToPoint:CGPointMake(0.8, 0.2)];
[opacityPath addLineToPoint:CGPointMake(0.0, 2.5)];
[opacityPath addLineToPoint:CGPointMake(0.0, 3.0)];
CFTimeInterval duration = 3.0;
// ScaleAnimation
CAKeyframeAnimation *xScaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.x"];
xScaleAnimation.calculationMode = kCAAnimationPaced;
xScaleAnimation.path = scalePath.CGPath;
xScaleAnimation.duration = duration;
CAKeyframeAnimation *yScaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.y"];
yScaleAnimation.calculationMode = kCAAnimationPaced;
yScaleAnimation.path = scalePath.CGPath;
yScaleAnimation.duration = duration;
// OpacityAnimation
CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.calculationMode = kCAAnimationPaced;
opacityAnimation.path = opacityPath.CGPath;
opacityAnimation.duration = duration;
// GroupAnimation
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[xScaleAnimation,yScaleAnimation,opacityAnimation];
groupAnimation.duration = duration;
groupAnimation.delegate = self;
groupAnimation.removedOnCompletion = NO;
// 给图层添加动画
[layer addAnimation:groupAnimation forKey:@"groupAnimation"];
}
操作
// 开始动画
- (void)startAnimation
{
// 已经在计时则直接返回
if (_timer)
{
return;
}
// 代理机制防止计时器的循环引用
FLWeakProxy *target = [FLWeakProxy weakProxyForObject:self];
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:target selector:@selector(fireRippleAnimation) userInfo:nil repeats:YES];
}
// 结束动画
- (void)stopAnimation
{
[_timer invalidate];
_timer = nil;
}
// 动画停止时调用
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
{
// 删除最低部的layer
CALayer *layer = [self.sublayers firstObject];
[layer removeFromSuperlayer];
}
使用
- (void)createRippleLayerView
{
// 创建View
CGSize viewSize = self.view.bounds.size;
UIView *rippleView = [[UIView alloc] initWithFrame:CGRectMake(viewSize.width/2, 300, 100, 100)];
// 创建Layer
self.rippleLayer = [RippleLayer layer];
self.rippleLayer.frame = rippleView.bounds;
// 将Layer添加到View
[rippleView.layer addSublayer:self.rippleLayer];
// 添加View
[self.view addSubview:rippleView];
// 启动动画
[self.rippleLayer startAnimation];
}
7、CADisplayLink
a、下拉加载动画
下拉加载动画RefreshView.h
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <CoreGraphics/CoreGraphics.h>
FOUNDATION_EXPORT const CGFloat PullLoadingViewSize;
@interface RefreshView : UIView
/** 关联滑动视图 */
@property (weak, nonatomic) UIScrollView *scrollView;
/** 拉多少距离转一周 */
@property (nonatomic, assign) CGFloat distanceForTurnOneCycle;
/** 下拉加载 */
- (void)loading;
/** 当取消或者加载完成的时机会调用这个方法 */
- (void)loadingFinished;
@end
RefreshView.m
const CGFloat PullLoadingViewSize = 30.0;
@interface RefreshView ()
@property (nonatomic, strong) CALayer *wheelLayer;
@property (nonatomic, strong) NSArray *windLayers;
@property (nonatomic, strong) NSArray *windOffsets;
@property (nonatomic, strong) CADisplayLink *displayLink;
@end
@implementation RefreshView
@end
绘制视图
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self drawRefreshView];
}
return self;
}
// 绘制加载风车视图
- (void)drawRefreshView
{
// 下拉80点转动一周
_distanceForTurnOneCycle = 80;
// 类似计时器,但每一帧都会调用
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayDidRefresh)];
// 放到NSRunLoopCommonModes,防止下拉切换mode导致计时器失效
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
CGFloat halfWidth = self.bounds.size.width / 2;// 视图一半
CGPoint center = CGPointMake(halfWidth, halfWidth);// 轮胎中心点
// 创建轮胎视图
_wheelLayer = [CALayer layer];
_wheelLayer.frame = self.bounds;
[self.layer addSublayer:_wheelLayer];
//拆分为两部分,轮胎使用描边,扇叶使用填充
//轮胎描边
UIBezierPath *wheelMaskPath = [UIBezierPath bezierPathWithArcCenter:center radius:halfWidth startAngle:0 endAngle:M_PI*2 clockwise:YES];// 路径为外圆
CAShapeLayer *wheelMask = [CAShapeLayer layer];// 外层轮胎图层
wheelMask.frame = self.bounds;
wheelMask.path = wheelMaskPath.CGPath;// 外层轮胎图层上的路径
CGFloat tyreRadius = 23/2 - 1.5;// 内层轮胎半径
UIColor *tyreColor = [UIColor redColor];// 内层轮胎颜色为红色
CAShapeLayer *tyreLayer = [CAShapeLayer layer];// 内层轮胎图层
UIBezierPath *tyrePath =[UIBezierPath bezierPathWithArcCenter:center radius:tyreRadius startAngle:0 endAngle:M_PI * 2 clockwise:YES];// 路径为内圆
[tyrePath closePath];// 关闭路径
tyreLayer.path = tyrePath.CGPath;// 内层轮胎图层上的路径
tyreLayer.strokeColor = tyreColor.CGColor;// 描边颜色
tyreLayer.lineWidth = 3;// 描边宽度
tyreLayer.fillColor = [UIColor clearColor].CGColor;// 填充颜色
tyreLayer.frame = self.bounds;
tyreLayer.mask = wheelMask;// 遮罩
[_wheelLayer addSublayer:tyreLayer];// 将形状图层添加到轮胎图层上
// 扇叶
CGFloat innerRadius = 7/2;
CGFloat outerRadius = 15/2;
CGFloat angleDelta = M_PI * 2 / 5;// 转动角度
CGFloat arcAngle = angleDelta * 2 / 3;// 圆弧角度
UIBezierPath *maskPath = [UIBezierPath bezierPath];// 遮罩路径
for (int i=0; i<5; I++)
{
CGFloat startAngle = angleDelta * i;// 开始角度
CGFloat endAngle = startAngle + arcAngle;// 开始角度 + 弧度 = 结束角度
CGPoint innerArcStart = [self calcPointWithAngle:endAngle radius:innerRadius center:center];// 计算开始点
CGPoint outerArcStart = [self calcPointWithAngle:startAngle radius:outerRadius center:center];// 计算结束点
// 绘制路径
UIBezierPath *path = [UIBezierPath bezierPath];
// 外部弧
[path addArcWithCenter:center radius:outerRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
// 连线
[path addLineToPoint:innerArcStart];
// 内部弧
[path addArcWithCenter:center radius:innerRadius startAngle:endAngle endAngle:startAngle clockwise:NO];
// 连线
[path addLineToPoint:outerArcStart];
// 闭合
[path closePath];
[maskPath appendPath:path];
}
// 内部环
CGFloat dotRadius = 3.0 / 2;// 点半径
UIBezierPath *dotPath = [UIBezierPath bezierPathWithArcCenter:center radius:dotRadius startAngle:0 endAngle:M_PI*2 clockwise:YES];// 画圆
[dotPath closePath];// 闭合
[maskPath appendPath:dotPath];// 添加到遮罩路径
// 遮罩图层
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
UIColor *fanBladeColor = [UIColor redColor];// 风扇颜色
CALayer *fanBladeLayer = [CALayer layer];// 风扇图层
fanBladeLayer.backgroundColor = fanBladeColor.CGColor;// 背景颜色为红色
fanBladeLayer.frame = self.bounds;
fanBladeLayer.mask = maskLayer;// 遮罩图层
// 也可将fanBladeLayer添加到tyreLayer中
[tyreLayer addSublayer:fanBladeLayer];
// 风
UIColor *windColor = [UIColor redColor];// 风的颜色为红色
NSMutableArray *windLayers = [NSMutableArray array];
for (int i=0; i<3; I++)
{
// 创建风图层
CAShapeLayer *layer = [CAShapeLayer layer];// 图层
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(6, 0)];// 绘制线条
layer.path = path.CGPath;// 图层的线条
layer.strokeColor = windColor.CGColor;// 描边颜色为风的颜色
layer.lineWidth = 1.0;// 描边宽度
layer.position = [self resetWindLayerPositionWithIndex:i];// 根据index计算风视图的开始位置
layer.opacity = 0.0;// 刚开始透明
[self.layer addSublayer:layer];// 添加风图层
[windLayers addObject:layer];// 放到风图层的集合中
}
_windLayers = windLayers;
_windOffsets = @[@18, @23, @16];// 根据index计算风视图的X轴上的坐标偏移量
}
让视图动起来
// 轮胎动画
- (void)wheelAnimation
{
// 设置Z轴上的旋转动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation.fromValue = @(M_PI*2);
animation.toValue = @(0);// 转动一圈
animation.removedOnCompletion = NO;// 完成后不移除
animation.repeatCount = HUGE_VALF;// 重复
animation.duration = 1.0;// 1秒
[_wheelLayer addAnimation:animation forKey:@"loading"];// 作为加载动画
}
// 吹风动画
- (void)windAnimation
{
// 为3个风图层分别添加动画
for (int i=0; i<3; I++)
{
[self addAnimationToWindLayerForIndex:i];
}
}
// 在风视图上添加动画
- (void)addAnimationToWindLayerForIndex:(NSInteger)index
{
CGFloat duration = 1.5;// 1.5秒
// 移动动画
// 根据index计算风视图的开始位置
CGPoint startPosition = [self resetWindLayerPositionWithIndex:index];
// 根据index计算风视图的X轴上的坐标偏移量
CGFloat offsetX = [_windOffsets[index] floatValue];
// 风视图的结束位置
CGPoint endPosition = CGPointMake(startPosition.x+offsetX, startPosition.y);
// 设置关键位置:开始->结束->开始
NSArray *positions = @[[NSValue valueWithCGPoint:startPosition],
[NSValue valueWithCGPoint:endPosition],
[NSValue valueWithCGPoint:endPosition],
[NSValue valueWithCGPoint:startPosition]];
// 设置时间点
NSArray *positionKeyTimes = @[@0, @(1.0/duration), @(1.3/duration), @1.0];
// 设置动画效果
NSArray *positionFunctions = @[[CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionEaseOut],
[CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionLinear],
[CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionLinear]];
// 透明度动画
// 设置关键点的透明度
NSArray *opacities = @[@0.0, @1.0, @1.0, @0.0, @0.0];
// 设置时间点
NSArray *opacityKeyTimes = @[@0, @(0.3/duration), @(1.0/duration), @(1.3/duration), @1.0];
// 设置动画效果
NSArray *opacityFunctions = @[[CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionEaseOut],
[CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionLinear],
[CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionEaseOut],
[CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionLinear]];
// 根据index获取创建好的风图层,并将其本地化
CALayer *windLayer = _windLayers[index];
// 创建透明度动画
CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.values = opacities;
opacityAnimation.keyTimes = opacityKeyTimes;
opacityAnimation.timingFunctions = opacityFunctions;
// 创建位置动画
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
positionAnimation.values = positions;
positionAnimation.keyTimes = positionKeyTimes;
positionAnimation.timingFunctions = positionFunctions;
// 创建组合动画
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[opacityAnimation, positionAnimation];
groupAnimation.duration = duration;
groupAnimation.repeatCount = HUGE_VALF;// 重复无限次
// 将组合动画作为风动画添加到风图层上
[windLayer addAnimation:groupAnimation forKey:@"wind"];
}
// 根据index计算风视图的开始位置
- (CGPoint)resetWindLayerPositionWithIndex:(NSInteger)index
{
CGFloat positionX = self.bounds.size.width/2 + 23/2 - 3;
CGFloat positionYDelta = 5;
CGFloat positionYStart = self.bounds.size.width/2 - positionYDelta;
return CGPointMake(positionX, positionYStart + positionYDelta*index);
}
- (CGPoint)calcPointWithAngle:(CGFloat)angle radius:(CGFloat)radius center:(CGPoint)center
{
// 正弦和余弦函数计算做坐标点
CGFloat postionX = center.x + radius * cos(angle);
CGFloat postionY = center.y + radius * sin(angle);
return CGPointMake(postionX, postionY);
}
和scrollView联动
// 滚动视图存在则实现下拉加载联动效果
- (void)displayDidRefresh
{
if (_scrollView)
{
// 总下拉距离
CGFloat distance = _scrollView.contentOffset.y;
// 2PI *(总下拉距离 / 80)= 转动几圈
CGFloat angle = - (M_PI * 2 * distance / _distanceForTurnOneCycle);
// 仿射变换实现旋转效果
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
// 在轮胎图层上添加旋转动画
[_wheelLayer setAffineTransform:transform];
}
}
操作方法
// 下拉加载,开始风和轮胎动画
- (void)loading
{
// 停止关联
_displayLink.paused = YES;
[self wheelAnimation];
[self windAnimation];
}
// 当取消或者加载完成的时机会调用这个方法
- (void)loadingFinished
{
// 启动关联
_displayLink.paused = NO;
// 移除加载动画
[_wheelLayer removeAnimationForKey:@"loading"];
for (CALayer *layer in _windLayers)
{
[layer removeAllAnimations];
}
}
- (void)dealloc
{
[_displayLink invalidate];
}
使用
@interface RefreshViewController() <UIScrollViewDelegate>
@property(nonatomic, strong) RefreshView *refreshView;
@end
@implementation RefreshViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(10, 500, 200, 300)];
scrollView.contentSize = CGSizeMake(200, 600);
scrollView.backgroundColor = [UIColor yellowColor];
scrollView.delegate = self;
[self.view addSubview:scrollView];
RefreshView *refreshView = [[RefreshView alloc] initWithFrame:CGRectMake(100, 100, PullLoadingViewSize, PullLoadingViewSize)];
refreshView.scrollView = scrollView;
[self.view addSubview:refreshView];
self.refreshView = refreshView;
}
// 下拉开始的时候加载
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self.refreshView loading];
}
// 下拉减速的时候结束加载
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
[self.refreshView loadingFinished];
}
@end
Demo
Demo在我的Github上,欢迎下载。
AnimationDemo