iOS 动画

CAAnimation - 1: CABasicAnimation

隐式动画

_myView = [[UIView alloc] initWithFrame:CGRectMake(50, 100, 50, 50)];
_myView.backgroundColor = [UIColor orangeColor];
[self.view addSubview:_myView];
    
_myLayer = [CALayer layer];
_myLayer.frame = CGRectMake(50, 200, 50, 50);
_myLayer.backgroundColor = [UIColor purpleColor].CGColor;
[self.view.layer addSublayer:_myLayer];

_myView.alpha = 0.2; // 不存在过渡动画
_myLayer.opacity = 0.2; // 存在过渡动画
  • UIView 的 alpha 和 CALayer 的 opacity 应该是一致的, opacity 会和 alpha 同步变化

Layer 树

  • Render Tree (Private): 渲染 // 不讨论
  • Model Layer Tree: [layer modelLayer]: Layer 属性的最终值
  • Presentation Layer Tree: [layer presentationLayer]: Layer 在某一个时间点的近似值
layer 运动过程, layer.position.x 初始为0
假若 layer 向右平移 100pt, layer.position.x, 所以变化如下
((CALayer *)[layer modelLayer]).position.x = 100 // 直接到达100
((CALayer *)[layer presentationLayer]).position.x = 0 ... 100 // 在运动过程中缓慢增加到100

CATransaction

  • 对 layer 属性的改变, 在渲染动画之前, 系统会产生一个 Transaction 事务, 打包所有动画操作, 然后提交给 RenderTree
  • CATransaction 各个属性
+ (void)begin;
// 所有操作应该放在这两个方法之中
+ (void)commit;

+ (void)setAnimationDuration:(CFTimeInterval)dur; // 设置默认的时间值

+ (void)disableActions; // 禁止隐式动画

+ (void)setCompletionBlock:(nullable void(^)(void))block; // 完成动画之后的回调
  • 代码示例
[CATransaction begin];
[CATransaction setCompletionBlock:^{
    NSLog(@"Set position animation completed.");
}];
[CATransaction setDisableActions:YES]; // 禁用隐式动画
[CATransaction setAnimationDuration:1]; // 动画时间
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
_myLayer.position = CGPointMake(_myLayer.position.x + 100, _myLayer.position.y);
[CATransaction commit];
  • 显式事务: 放在 begin 和 commit 中间的操作, 显式声明了事务的过程
  • 隐式事务: 不声明事务过程, 但系统在操作过程中会自动添加事务, 比如说默认的动画时长 0.25s

显式动画

CABasicAnimation 属性

哪个属性动
/* CABasicAnimation class */
// 指定哪个属性动, keyPath 是一个字符串
// "opacity" 透明度; "position" 位置; "position.x" 位置的 x 值; ......
// [CABasicAnimation animationWithKeyPath:@"position.x"]; 创建一个 BasicAnimation 对象

+ (instancetype)animationWithKeyPath:(nullable NSString *)path;
@property (nullable, copy) NSString *keyPath;
怎么动
// 怎么动
// 这三个值必须是一致的类型
// 三个属性可以为空, 但是不能设置超过两个属性值
@property (nullable, strong) id fromValue;
@property (nullable, strong) id toValue;
@property (nullable, strong) id byValue;
// 只设置以下值会发生的情况
fromValue && toValue    // fromValue ~ toValue
fromValue && byValue    // fromValue ~ (fromValue + byValue)
toValue && byValue      // (toValue - byValue) ~ toValue
fromValue                   // fromValue ~ PresentationLayer.value (fromValue ~ currentValue)
toValue                 // PresentationLayer.value ~ toValue (currentValue ~ toValue)
byValue                 // PresentationLayer.value ~ PresentationLayer.value + byValue (currentValue ~ currentValue + byValue)
谁动
// 给 layer 添加动画
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;
// 获取动画的名字
- (nullable NSArray<NSString *> *)animationKeys;
// 获取指定名字的动画对象
- (nullable CAAnimation *)animationForKey:(NSString *)key;

CABasicAnimation 动画demo

  • 动画过程中, 改变的是 PresentationLayer 的值, 但 modelLayer 的值不会被改变. 动画结束的时候, 动画被移除, 所以位置会恢复到 modelLayer 的值, 即还原到原来的位置
  • addAnimation:forKey:这个过程中, add 进去的 animation 会被拷贝的, 所以添加完动画之后, 对动画进行修改将不生效

CAMediaTiming

@protocol CAMediaTiming

@property CFTimeInterval duration;  // 虽然默认是0, 但是还是会保持动画隐式动画默认的0.25s
@property float repeatCount;        // 动画的重复次数, 设置 HUGE_VALF 可以设置成无限次
@property CFTimeInterval repeatDuration; // 每一次动画持续的时间, 和 repeatCount 不能同时设置, 系统通过计算总时间和每次动画时长计算重复的次数
@property BOOL autoreverses; // 回复

@end

动画结束之后如何不返回原来的位置

  • 在动画结束的时候, 再进行一次位置的设置操作, 将 modelLayer 的值设置成位移之后应该变成的值
    • 在改变 layer 的位置的时候, 会产生隐式动画, 导致两个动画的产生
    • 保持两个动画的 key 值一致, 后面添加的动画会覆盖前面添加的动画
    • 所以, 可以通过设置一致的动画 key 值, 将显式动画覆盖隐式动画
// ------ animation ------
layer.position.x, byValue = 100;
// ------ animation stop ------
layer.position.x += 100;

CoreAnimation Timing

  • CAMediaTiming System
  • CACurrentMediaTime()获取创建的 CALayer 在系统时间轴上对应的时间是什么
- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(nullable CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(nullable CALayer *)l;
@protocol CAMediaTiming

@property CFTimeInterval beginTime; // 动画开始的时间, 
// animation.beginTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil] + 1; 表示 layer 动画添加之后的一秒执行

@property float speed; // 让整个动画加速,
// 设置动画时长 duration 为10, 默认 speed 为1; 当 speed 设置成2的时候, 动画将在5秒结束, 当 speed 设置成5的时候, 动画将在2秒结束, 以此类推

@property CFTimeInterval timeOffset; // 相当于把动画的运动作为一个循环, 设置 timeOffset 属性, 将会从 offset 的时间点进行动画, 结束之后循环到动画开始的时候进行运动
// 相对于整个动画的时长进行的 offset, 不会被 speed 影响

@end

暂停动画

  • 动画过程中, 调用暂停方法, 动画暂停在调用时的状态
  • timeOffset = 0 && speed = suitableValue;
  • 不能通过修改 animation.speed = 0 && animation.timeOffset = xxx; 进行设置
    • 因为 animation 添加到 Layer 上之后, 就被复制了, 之后再修改 animation 的属性不会对动画产生影响
    • 所以要使用 layer.speed = 0 && layer.timeOffset = xxx;
lt = (tp - begin) * speed + offset
// lt: layer local time
// tp: parent time
// begin: default 0
// speed: default 1
// offset: default 0

// 所以: lt = (tp - 0) * 1 + 0 => lt = tp
// lt = [layer convertTime:CACurrentMedaiTime() fromLayer:nil];
// tp = (lt - offset) / speed + begin
  • 动画过程
    1. time: tp0; speed=1, begin=0, lt=lt0, offset=0, => tp0 = lt0; // 动画开始
    2. time: tp1; speed=0, begin=0, lt=lt1, offset=0, => tp1 = lt1; // 动画暂停
      • 将 speed 置为0, => lt1' = offset = 0
      • 因为 lt1' 为0, 所以动画会恢复到原来的位置. 要保存动画位置状态, 需要让 lt1' = lt1, 即 offset = lt1
    3. time: tp2; speed=1, begin=0, lt=lt2, offset=0, => tp2 = lt2;

CAMediaTiming 动画结束后保持

  • fillMode属性
@property (copy) NSString *fillMode;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
animation.fromValue = (id)[UIColor yellowColor].CGColor; // 从黄色变化到橙色
animation.toValue = (id)[UIColor orangeColor].CGColor;
animation.duration = 2;
animation.fillMode = kCAFillModeForwards; // 动画结束之后保存动画状态, 需要将 layer 动画设置为不移除
animation.beginTime = [layer convertTime:CACurrentMediaTime() fromLayer:nir] + 1; // 延迟一秒开始动画
[layer addAnimation:animation forKey:@"backgroundColor"];

CAMediaTiming - timingFunction

  • 动画过程中的速率变化
+ (instancetype)functionWithName:(NSString *)name;
+ (instancetype)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;

CAAnimation - 2:

CASpringAnimation

@interface CASpringAnimation : CABasicAnimation
@property CGFloat mass;         // 质量, 弹簧物品的质量越大, 惯性越大
@property CGFloat stiffness;    // 刚度, 相当于拉弹簧的力
@property CGFloat damping;      // 阻尼; 取值范围 >= 0
@property CGFloat initialVelocity; // 初始速度

@property (readonly) CFTimeInterval settlingDuration; // 弹簧预估的时间

CAKeyFrameAnimation

@interface CAKeyFrameAnimation : CAPropertyAnimation
@property (nullable, copy) NSArray *values; // 进行动画的值 包含第一帧和最后一帧
@property (nullable, copy) NSArray<NSNumber *> *keyTimes; // 分别动画的时长 包含第一帧和最后一帧
@property (nullable, copy) NSArray<CAMediaTimingFunction *> *timingFunctions; // 动画速率
  • calculationMode 对动画运动路径产生变化
// 下面三个参数用于定制 calculateMode 路径
@property (nullable, copy) NSArray<NSNumber *> *tensionValues;
@property (nullable, copy) NSArray<NSNumber *> *continuityValues;
@property (nullable, copy) NSArray<NSNumber *> *biasValues;
  • additive 对动画参数进行叠加, 即在原来动画的基础上增量增加动画
  • path 可以覆盖 values 属性
    • path 在被赋值的时候是通过复制完成的
    • 需要将 calculateMode 设置为 kCAAnimationPaced
  • rotationMode 在进行动画的时候自动进行旋转

CATransition

  • type 描述动画的形式
    • cube
    • oglFlip
    • ... // CoreAnimation 2-3 06:00
  • subtype 动画的方向
  • startProgress 整个动画起始时间
  • endProgress
  • filter 定制动画的效果

CALayer 实现翻转的动画

CATransition *animation = [CATransition animation];
animation.duration = 1;
animation.type = @"oglFlip";
animation.subtype = kCATransitionFromLeft;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[parentView.layer addAnimation:animation forKey:@"myTrans"];
// 在动画中, 动画的 key 值都会被设置为 kCATransition
subview1.hidden = !subview1.hidden;
subview2.hidden = !subview2.hidden;

CAAnimationGroup

  • animations 动画组合
  • timingFunction 会叠加
  • 如果要设置 animation1 的 beginTime, 可以直接设置, 因为 animation1 属于 group 动画, group 是从0开始的
  • 动画的 duration 超出了 group 的 duration, 则会被截断
  • speed 和 timingFunction 都是会叠加的
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 2;
group.animations = @[animation1, animation2];
group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
group.fillMode = kCAFillModeForward;
group.removedOnCompletion = NO;
[layer addAnimation:group forKey:@"group"];

特殊的 Layers

CAShapeLayer

  • shapeLayer
  • fillColor
  • path 这个 path 不支持隐式动画
  • stroke; line
// path 动画 圆形和正方形进行转换
UIBezierPath *path1 = [UIBezierPath bezierPathWithOvalInRect:layer.bounds];
UIBezierPath *path2 = [UIBezierPath bezierPathWithRect:layer.bounds];
layer.path = path1.CGPath;

- (void)touchesEnded {
    CGPathRef fromValue = layer.path;
    CGPathRef toValue = CGPathEqualToPath(path1.CGPath, fromValue) ? path2.CGPath : path1.CGPath;
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"];
    animation.fromValue = (__bridge id _Nullable)(fromValue);
    animation.toValue = (__bridge id _Nullable)(toValue);
    animation.duration = 2;
    layer.path = toValue;
    [layer addAnimation:animation forKey:animation.keyPath];
}
  • fillColor 填充颜色
  • fillRule 填充规则
  • lineWidth 线的宽度
  • lineCap 线头形状
  • lineJoin 连接时
  • miterLimit
  • lineDashPhase 虚线从哪里开始画
  • lineDashPattern 虚线的样式 @[@10, @10]: 实线部分10, 虚线部分10

利用 CAShapeLayer 实线一个进度视图

CAReplicatorLayer

  • instanceCount
  • instanceDelay
  • instanceTransform
  • instanceColor
    • instanceRedOffset
    • instanceGreenOffset
    • instanceBlueOffset
    • instanceAlphaOffset
CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
replicatorLayer.frame = CGRectMake(0, 200, self.view.bounds.size.width, 50);
[self.view.layer addSublayer:replicatorLayer];

CALayer *layer = [CALayer layer];
layer.backgroundColor = [UIColor blueColor].CGColor;
layer.frame = CGRectMake(0, 0, 20, 50);
[replicatorLayer addSublayer:layer];

replicatorLayer.instanceCount = 10; // 复制10次
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(20, 0, 0);

CAEmitterLayer

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

推荐阅读更多精彩内容

  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 3,000评论 1 23
  • 显式动画 显式动画,它能够对一些属性做指定的自定义动画,或者创建非线性动画,比如沿着任意一条曲线移动。 属性动画 ...
    清风沐沐阅读 1,867评论 1 5
  • 一、iOS动画 iOS中实现一个动画十分简单,在view层面上通过调用 但是它不能控制动画的暂停和组合,所以就需要...
    nuclear阅读 7,823评论 6 38
  • 动画 - UIKit 动画原理 视觉残留效应 运动模糊 做动画的时候要达到 60FPS 时候,画面才能流畅,不然用...
    varlarzh阅读 778评论 2 10
  • 什么是动画 动画,顾名思义,就是能“动”的画。 人的眼睛对图像有短暂的记忆效应,所以当眼睛看到多张图片连续快速的切...
    CoderSC阅读 1,259评论 0 1