核心动画

⬅️ Back

当修改一个 CALayer 的属性时,它会通过-actionForKey:来查询属性对应的 action,而 key 就是对应的属性名称。

CAAnimation 遵守 CAAction 协议,返回的 action 其实就是一个 CAAnimation 动画对象。
一般默认返回的是 CABasicAnimation,默认动画时间0.25秒,动画速率函数为渐入渐出效果(kCAMediaTimingFunctionEaseInEaseOut)。

View 和 Layer 的关系:

所有的 View 都是由一个底层的 Layer 来驱动的,View 只是一个简单的控制器,View 的绝大多数属性都是从 Layer 对象中获取的数据。

AVCaptureVideoPreviewLayerCAShapeLayer是不需要附加到 View 上就可以在屏幕上显示内容。

参考:CALayer与UIView之间的关系

Layer 的自定义动画

可以通过监听自定义 Layer 的自定义属性值的变动,实现自定义动画。

  1. 监听自定义属性的 setter 方法,实现对应动画。

  2. 利用自定义属性的dynamic特性自行实现属性的 ivar 和 getter 方法,进行监听值的变化。

    通过重写+needsDisplayForKey: 方法可以监听属性值的变化。

    自定义属性必须是可进行插值(interpolate)的,否则无法获取属性的增量值。

    + (BOOL)needsDisplayForKey:(NSString *)key
    {
        // time 属性修改
        if ([@"time" isEqualToString:key])
        {
            // layer 的 display 函数会调用
            return YES;
        }
        return [super needsDisplayForKey:key];
    }
       
    - (void)display
    {
        // 使用 presentation Layer 来获取当前属性值
        // 而 model Layer 表示的是实际设置的属性最终值
        NSLog(@"time: %f", [[self presentationLayer] time]);
        // 使用 Core Graphics 函数绘制动画
    }
       
       
    // 当属性 time 变化时,添加新的动画
    - (id<CAAction>)actionForKey:(NSString *)key
    {
        if ([key isEqualToString:@"time"])
        {
            CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
            animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
            animation.fromValue = @([[self presentationLayer] time]);
            return animation;
        }
        return [super actionForKey:key];
    }
    

    model Layer(模型层)和 presentation Layer(表示层)的区别:

    1. 前者负责数据的存储和获取。表示正在进行的动画结束时 layer 所达到的最终状态。
    2. 后者负责效果的展示,是前者的一个拷贝,表示的是当前的中间动画状态。

    属性赋值时修改的是 model Layer 中的值,下次屏幕刷新时 presentation Layer 的状态会回到 model Layer 的状态。

ObjC 中国 - Layer 中自定义属性的动画

Layer 自带的仿射动画

CGAffineTransform 形变就是把二维形变使用一个三维矩阵来表示,其中第三列总是(0,0,1),形变通过矩阵前两列来控制。

系统提供了 CGAffineTransformMake 结构体来控制形变。

动画函数 意义
CGAffineTransformIdentity layer 形变动画的还原(单位矩阵)
CGAffineTransformMakeTranslation 平移动画(tx,ty)
CGAffineTransformMakeScale 缩放动画(sx,sy)
CGAffineTransformMakeRotation 旋转动画(angle)
CGAffineTransformTranslate 实现在已存在的形变动画上的平移动画(t,tx,ty)
CGAffineTransformScale 实现在已存在的形变动画上的缩放动画(t,sx,sy)
CGAffineTransformRotate 实现在已存在的形变动画上的旋转动画(t,angle)
CGAffineTransformInvert 实现形变动画的翻转效果(t)

CGAffineTransform _ Apple Developer Documentation
iOS CoreAnimation专题——实战篇(四)基于拖动手势的视图3D旋转效果 - CSDN博客

iOS 动画分为显式动画隐式动画

UIView 隐式动画

当我们修改界面的 CALayer 的可动画属性时,就会触发系统的隐式动画。
但是,系统中默认 UIView 对应的 CALayer 关闭了隐式动画。所以直接做出修改时无法产生效果的。

UIView 有一系列的 animatedWithDuration动画,可以使用 animation block 重新开启默认关闭的 Layer 动画。

当属性在动画 block 中被改变时,就附加了动画效果。

属性改变时 Layer 会向 View 请求一个动作,而一般情况下 View 将返回一个 NSNull。
只有当属性改变发生在动画 block 中时,系统会通过addAnimation:forKey:方法将动画添加到 Layer中, View 才会返回实际的动作。

显式动画

关键帧动画:最少含有起始帧、结束帧,中间的过渡效果由系统自动生成。

逐帧动画:周期性的调用绘制方法,绘制每帧的动画对象。比如,重写 UIView 的 - drawRect:方法来修改视图属性绘制视图。

iOS 的动画都是基于 CALayer 的,使用 CADisplayLink(基于屏幕刷新的,每次屏幕刷新都会触发调用)来周期性调用指定方法。

调用addAnimation:forKey:方法时,要注意设置 key 值。

设置 key 值后,系统再次执行该动画时会先检查该动画是否被创建。查找成功后会开头执行,失败后创建动画。

CAPropertyAnimation

当对属性赋值时,layer 会让它的 delegate 调用actionForLayer:forKey:方法获取一个返回值:

  1. 返回值可能为 nil,则 layer 会走自己的隐式动画。
  2. 返回值可能是 NSNull,则 layer 不会做任何动画。
  3. 返回值是一个实现了 CAAction 协议的对象,则 layer 会用该对象生成一个 CABaseAnimation 加到自身并执行动画。

CABasicAnimation

基本动画通过设置起始帧、结束帧的信息来实现动画效果。

属性 解释
duration 动画的持续时间
repeatCount 动画持续次数(可以设置小数值,默认值为 0)
repeatDuration 在设置的时间内,动画一直执行不计次数
beginTime 指定动画开始的时间
timingFunction 设置动画的速度变化
fillMode 动画在开始和结束时的动作,默认值是 kCAFillModeRemoved
autoreverses 动画结束时是否执行逆动画
fromValue 所改变属性的起始值
toValue 所改变属性的结束值
byValue 所改变属性相同起始值的改变量
additive 使 Core Animation 在更新 presentation layer 之前将动画的值添加到 model layer 中去。
对于所有形式的需要更新的元素可以重用相同的动画,且无需提前知道它们的位置。
默认值为 NO。
removedOnCompletion 动画完成后其是否从渲染树中移除。默认值为 YES。

repeatCount

想要设置一直不断重复时,在 Swift 中使用Float.infinity,OC 中使用HUGE_VALF

timingFunction

用来设置时间段内动画的速度变化。可以使用 CAMediaTimingFunction+functionWithControlPoints::::函数自定义速率函数。

使用 CAKeyFrameAnimation 时,可以使用 timingFunctions 来指定阶段性的速度变化函数。

fillMode

delegate

常见的 keyPath

CALayer 的 Animatable 属性直接赋值可以产生隐式动画,可以在 CAAnimation 的 keyPath 中设置。

keyPath值 说明
transform.scale
transform.rotation
transform.translation
比例缩放
旋转状态
平移状态(具体方法同下)
transform.scale.x
transform.scale.y
缩放宽/高的比例
transform.rotation.x
transform.rotation.y
transform.rotation.z
围绕x/y/z轴旋转
backgroundColor 背景颜色的变化
bounds 大小缩放,中心不变
position 位置(中心点改变)
contents 内容变化(比如 UIImageView 的变化)
opacity 透明度
contentsRect.size.width
contentsRect.size.height
横向/纵向拉伸缩放
shadowColor 阴影的颜色
shadowOffset 阴影的偏移
shadowOpacity 阴影的不透明度
shadowRadius 阴影渲染的模糊半径
cornerRadius 绘制圆角时使用的半径
borderWidth 边框的宽度
hidden 隐藏过渡动画

CATransform3D Key Paths

CASpringAnimation

它继承于 CABaseAnimation,用于制作弹簧动画。

该动画只在 iOS 9及其以后可用。

CAKeyframeAnimation

关键帧动画,关键帧动画就是在动画控制过程中开发者指定主要的动画状态,至于各种状态间动画如何进行则由系统自动运算补充(每个两个关键帧之间系统形成的动画成为补间动画)。

常见的设置模式:

CABasicAnimation 只能从一个数值(fromValue)变到另一个数值(toValue),
而 CAKeyframeAnimation 可以使用数组保存一组数值。
CABasicAnimation 可以看做是最多只有2个关键帧的 CAKeyframeAnimation。

属性 解释
values 由许多关键帧值组成的数组用来进行动画。
(只有在 path 属性为 nil 时才有效)
path 可以指定一个路径(CGPathRef/CGMutablePathRef),让动画沿着这个路径执行。
cacluationMode 主要是指对平面中多个离散点的计算模式。
keyTimes 一个包含若干 NSNumber 对象值的数组,用来指定对应的关键帧对应的时间点。
(NSNumber 的值在 0.0~1.0 之间,且后边的值要比前一个大或相等。
此值未设置时,各个关键帧的时间是平分的。)
timingFunctions 设置动画的速度变化数组,类似于 values。
rotationMode 设置旋转样式
duration 动画在指定时间内完成

cacluationMode

设置平面中多个点的散列方式。

rotationMode

沿路径的动画

通过设置 path 属性代替 values 属性,设置动画。

CGPathRef 和 CGMutablePathRef 都是 CGPath 结构体类型的指针,前者是 const 类型不可修改,后者是可修改的。

创建 CGMutablePathRef 对象时,记得调用 CGPathRelease 方法释放。

常见的创建 Path 的方法:

CATransition

主要用于转场动画,即从一个场景以动画的形式过渡到另一个场景。

UIView Animation

UIView(UIViewAnimation)

+ (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context;
+ (void)commitAnimations;
// 设置动画曲线
// UIViewAnimationCurveEaseInOut(默认值)
// UIViewAnimationCurveEaseIn
// UIViewAnimationCurveEaseOut
// UIViewAnimationCurveLinear
+ (void)setAnimationCurve:(UIViewAnimationCurve)curve;
// 设置过渡动画类型
// UIViewAnimationTransitionNone
// UIViewAnimationTransitionFlipFromLeft
// UIViewAnimationTransitionFlipFromRight
// UIViewAnimationTransitionCurlUp
// UIViewAnimationTransitionCurlDown
+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;

UIView(UIViewAnimationWithBlocks)

为什么 UIView 的属性动画要设置在 block 内?

CALayer 中存在一个持有者 UIView 的 delegate。

当给 UIView 属性赋值时,除了调用其持有的 CALayer 对应的属性的 setter 方法外,CALayer 还会让其 delegate(即 UIView)调用actionForLayer:forKey:方法。

当在 block 外 设置属性时,此方法会返回 NSNull。
当在 block 内 设置属性时,此方法会返回正确的 CAAction。

UIView block动画实现原理

+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;
// 默认delay为0,options为0
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;
// 默认delay为0,options为0,completion为NULL
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations;

// 阻尼动画
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;

/*
* 转场动画
* 用于实现不能使用属性动画实现的动画效果
* UIViewAnimationOptions:通过控制参数实现不同的动画效果
*/
// 单视图的转场动画需要在动画块中设置视图转场前的内容和视图转场后的内容
+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;
// 增加toView至fromView.SuperView,从父视图移除fromView
+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion;

+ (void)performSystemAnimation:(UISystemAnimation)animation onViews:(NSArray *)views options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))parallelAnimations completion:(void (^ __nullable)(BOOL finished))completion;

UIView (UIViewKeyframeAnimations)

+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;
+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations;

CAAnimationGroup

通过同时为多个属性添加动画来完成一些复杂的效果。

Core Animation 编程指南

Facebook_pop(An extensible iOS and OS X animation library, useful for physics-based interactions)

扩展阅读

CALayer

CALayer 所共有的特点:可动画属性、隐式动画、transform 变形等。

类型 含义
CAShapeLayer 根据路径绘制矢量图形
CATextLayer 绘制文字信息
CAGradientLayer 绘制线性渐变色
CAEmitterLayer 各种炫酷的粒子效果
CATransformLayer 使用单独的图层创建 3D 图形
CATiledLayer 将大图裁剪成多个小图以提高内存和性能
CAScrollLayer 没有交互效果的滚动图层,没有滚动边界,可以任意滚动上面的图层内容
CAReplicatorLayer 高效地创建多个相似的图层并施加相似的效果或动画
CAEAGLLayer 用来显示任意的 OpenGL 图形
AVPlayerLayer 用来播放视频

anchorPoint 和 position 的关系

参考资料:

CAShapeLayer

CAShapeLayer 是一个通过矢量图形而不是 bitmap(位图)来绘制的 CALayer 子类。使用 CAShapeLayer 绘制路径有以下一些优点:

矢量图和位图:

CAShapeLayer 中几个常见的 Animatable 的属性:

Animating the Drawing of a CGPath With CAShapeLayer – Ole Begemann

CAShapeLayer 与 mask 的结合使用:

mask 蒙版的常见实现方式有:使用图片做蒙版、使用 CAShapeLayer 绘制图形做蒙版。

mask 蒙版上还可以添加自定义动画效果。

// 使用图片作为蒙版
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(self.imageView.centerX /8.0f, self.imageView.centerY /7.f, self.imageView.width /1.5f, self.imageView.height /1.5f);
layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"first"].CGImage);
self.imageView.layer.mask = layer;


// 使用 CAShapeLayer 绘制图形作为蒙版
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = path.CGPath;
maskLayer.lineWidth = 10.0f;
// 填充颜色为透明时,填充内容不再作为蒙版内容
maskLayer.fillColor = [UIColor clearColor].CGColor;
// 不设置为透明色时,描线颜色就会作为蒙版内容
maskLayer.strokeColor = [UIColor redColor].CGColor;
self.imageView.layer.mask = maskLayer;

// 为蒙版添加动画效果
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.duration = 3.0f;
animation.fromValue = @0;
[maskLayer addAnimation:animation forKey:nil];

iOS CoreAnimation专题——技巧篇(三)Layer Masking - 图层蒙版 - CSDN博客

CAGradientLayer

CAGradientLayer 可以实现线性渐变的效果,coreGraphics 可以绘制出更多渐变效果(实现复杂)。

将 CAGradientLayer 渐变效果与 mask 蒙版结合能够实现复杂的渐变效果,
将 Animation 动画加到 mask 蒙版上更能够实现复杂的渐变动画。

iOS CoreAnimation专题——实战篇(一)惊艳的进度条效果实现 - CSDN博客

CAMediaTiming 协议

动画的暂停和恢复:

// 暂停动画
- (void)pauseLayer:(CALayer *)layer {
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.speed = 0.0;
    layer.timeOffset = pausedTime;
}
// 恢复动画
- (void)resumeLayer:(CALayer *)layer {
    CFTimeInterval pausedTime = layer.timeOffset;
    layer.speed = 1.0;
    layer.timeOffset = 0.0;
    layer.beginTime = 0.0;
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    layer.beginTime = timeSincePause;
}

Controlling Animation Timing(控制动画时机)

FPS(frame per second):帧率,也就是屏幕每秒钟刷新次数。如果帧率为60,表示屏幕每秒刷新60次,但是并不是每次屏幕刷新的时间间隔都是平均的。

CADisplayLink:就是屏幕保持 >60 fps 的帧率进行刷新,每次刷新都会根据绘制信息重绘屏幕上的显示内容。

// 创建 CADisplayLink 对象
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
// 停止刷新
- (void)invalidate;

// 可以通过属性的设置来控制屏幕刷新的暂停与开始
@property(getter=isPaused, nonatomic) BOOL paused;

系统默认提供了两个常用的runloop mode:NSDefaultRunloopMode 和 NSRunLoopCommonModes。

一个 runloop只能在某个 mode 中运行,runloop 可以在多个不同 mode 间进行切换。

iOS CoreAnimation专题——实战篇(三)CADisplayLink高级应用:让视图的四条边振动起来 - CSDN博客

CAMediaTimingFunction

常见的 timingFunction

// 创建自定义的动画速率变化函数
+ (instancetype)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;
- (instancetype)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;

CAMediaTimingFunction 效果查看器


参考资料


⬅️ Back

⬆️ 回到顶部 ⬆️