记 iOS 绘图、动画相关的几件事

这是一篇记载几个关键点以备查询的文章

项目3.0版本告一段落,在这个版本的设计与制作质量相较2.0版本有了很大提高。为此 UI 的同事设计了许多动效。平时虽然经常看这方面的文章,但是真正做的时候还是踩了许多坑,这也印证了“纸上得来终觉浅,绝知此事要躬行”。在此,写下这篇文章记录一下以备查询。

CoreAnimation 绘图坐标系的问题

CoreAnimation 默认的坐标系原点是在屏幕左下方,x 和 y 轴的正方向分别指向屏幕上方和右方。
例如这段代码


- (CGMutablePathRef)createAnalyzePicPathWithPicRect:(CGRect)rect{
    CGFloat scaleRate = [UIScreen mainScreen].scale;
    CGAffineTransform transform = CGAffineTransformIdentity;
    transform = CGAffineTransformTranslate(transform, rect.origin.x + rect.size.width/2, rect.origin.y + rect.size.height/2);
    transform = CGAffineTransformScale(transform, 1/scaleRate, -1/scaleRate);
    CGMutablePathRef path = CGPathCreateMutable();
    //逆时针去点
    //point1
    CGFloat radius1 = (arc4random()%50+51)/100.0 * MIN(rect.size.width, rect.size.height);
    CGPathMoveToPoint(path, &transform, radius1, 0);
    //point2
    CGFloat radius2 = (arc4random()%50+51)/100.0 * MIN(rect.size.width, rect.size.height);
    CGPathAddLineToPoint(path, &transform,radius2 * cos(M_PI_4) ,radius2 * sin(M_PI_4) );
    //point3
    CGFloat radius3 = (arc4random()%50+51)/100.0 * MIN(rect.size.width, rect.size.height);
    CGPathAddLineToPoint(path, &transform, 0, radius3);
    //point4
    CGFloat radius4 = (arc4random()%50+51)/100.0 * MIN(rect.size.width, rect.size.height);
    CGPathAddLineToPoint(path, &transform,radius4 * cos(M_PI_4 * 3) ,radius4 * sin(M_PI_4 * 3) );
    //point5
    CGFloat radius5 = (arc4random()%50+51)/100.0 * MIN(rect.size.width, rect.size.height);
    CGPathAddLineToPoint(path, &transform, -radius5, 0);
    //point6
    CGFloat radius6 = (arc4random()%50+51)/100.0 * MIN(rect.size.width, rect.size.height);
    CGPathAddLineToPoint(path, &transform,radius6 * cos(M_PI_4 * 5) ,radius6 * sin(M_PI_4 * 5) );
    //point7
    CGFloat radius7 = (arc4random()%50+51)/100.0 * MIN(rect.size.width, rect.size.height);
    CGPathAddLineToPoint(path, &transform, 0, -radius7);
    //point8
    CGFloat radius8 = (arc4random()%50+51)/100.0 * MIN(rect.size.width, rect.size.height);
    CGPathAddLineToPoint(path, &transform,radius8 * cos(M_PI_4 * 7) ,radius8 * sin(M_PI_4 * 7) );
    //close
    CGPathCloseSubpath(path);
    return path;
}

注意这一句


    CGMutablePathRef path = CGPathCreateMutable();

这个 CGPth 是直接创建出来的,因此没有应用 transform,此时绘制六边形的时候,使用的坐标系就是 CoreAnimation 的坐标系。计算角度的时候注意就可以了。

但是,如果 CGContext 是用 UIKit 框架的方法获得的,例如:


    UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);

那么,这些方法会在 CGContext 上面应用一个 transform,让坐标系的原点变成屏幕左上角,x 轴与 y 轴的正方向分别指向屏幕下方和右方,绘图的时候相应的计算也变一下就可以了。

CAKeyframeAnimation 画动画时需要注意的几个参数

CAKeyframeAnimation 是非常强大的。他主要有这样几个参数:

  1. values

@property(nullable, copy) NSArray *values;

这里可以传的东西与 CABasicAnimation 中能生成隐式动画的东西是一致的。例如:position transform opacity 这些keypath 要求的 NSNumber。
也可以传 CGImage 和 CGPath 等。
下面分别是传入 CGImage 和 CGPath 的例子:

111g-2017618

这里可以看到,values 设置为不同的 CGImage 后,效果非常漂亮。

222g-2017618

这是一个传入 CGPath 的例子,可以看到,中间红色和蓝色的分析图的动画,在关键帧之间自动插入了补间动画。关键帧的 CGPath 就是用上面画六边形的代码生成的。

  1. keytimes

@property(nullable, copy) NSArray<NSNumber *> *keyTimes;

这个参数没有什么好介绍的,就是文档中所讲的,keytimes 数组中元素的个数与 values 必须相同。同时,第一个值必须是0,最后一个必须是1,中间的值需要介于0~1之间。

phoneContainerAnimation.values = @[(__bridge id)[UIImage imageNamed:@"guide_空-手机内容"].CGImage,(__bridge id)[UIImage imageNamed:@"guide_费率1折-手机内容"].CGImage,(__bridge id)[UIImage imageNamed:@"guide_空-手机内容"].CGImage,(__bridge id)[UIImage imageNamed:@"guide_热门专题-手机界面"].CGImage,(__bridge id)[UIImage imageNamed:@"guide_空-手机内容"].CGImage,(__bridge id)[UIImage imageNamed:@"guide_基金超市-手机内容"].CGImage,(__bridge id)[UIImage imageNamed:@"guide_空-手机内容"].CGImage,(__bridge id)[UIImage imageNamed:@"guide_定投-手机界面"].CGImage,(__bridge id)[UIImage imageNamed:@"guide_空-手机内容"].CGImage,(__bridge id)[UIImage imageNamed:@"guide_T+0+手机界面"].CGImage,(__bridge id)[UIImage imageNamed:@"guide_空-手机内容"].CGImage,(__bridge id)[UIImage imageNamed:@"guide_智能-手机界面"].CGImage,(__bridge id)[UIImage imageNamed:@"guide_空-手机内容"].CGImage];

phoneContainerAnimation.keyTimes = @[[NSNumber numberWithFloat:0],[NSNumber numberWithFloat:(1.0 * timeSlice/totalDuration)],[NSNumber numberWithFloat:(2.0 * timeSlice/totalDuration)],[NSNumber numberWithFloat:(3.0 * timeSlice/totalDuration)],[NSNumber numberWithFloat:(4.0 * timeSlice/totalDuration)],[NSNumber numberWithFloat:(5.0 * timeSlice/totalDuration)],[NSNumber numberWithFloat:(6.0 * timeSlice/totalDuration)],[NSNumber numberWithFloat:(7.0 * timeSlice/totalDuration)],[NSNumber numberWithFloat:(8.0 * timeSlice/totalDuration)],[NSNumber numberWithFloat:(9.0 * timeSlice/totalDuration)],[NSNumber numberWithFloat:(10.0 * timeSlice/totalDuration)],[NSNumber numberWithFloat:(11.0 * timeSlice/totalDuration)],[NSNumber numberWithFloat:1]];

  1. timingFunctions 和 calculationMode

如果 CAKeyframeAnmation 的 values 中有 n 项,那么 timingFunctions 必须有n-1个CAMediaTimingFunction对。
它所描述的是关键帧到帧的变化速率。

kCAMediaTimingFunctionLinear


6fed5fbe-db73-4d5c-96ef-4fb62db20660-2017619

kCAMediaTimingFunctionDefault 这个曲线怎么形容呢?反正挺好看也挺好用的不是吗


2dec0ceb-b0ea-43a8-8878-4831837df670-2017619

kCAMediaTimingFunctionEaseIn 慢-快


8cb968a2-677e-47a5-b31b-b4afa275946e-2017619

kCAMediaTimingFunctionEaseOut 快-慢


53e7f51d-2556-4c02-b18e-60f34b86853d-2017619

kCAMediaTimingFunctionEaseInEaseOut 慢-快-慢


52f9f372-58f2-42ab-adc9-fb889980e265-2017619

银行卡跳动的动画使用了 timingFunctions 调节关键帧之间的变换速率,有点弹跳的感觉。


444g-2017619

calculationMode 其主要针对的是每一帧的内容为一个座标点的情况,也就是对anchorPoint 和 position 进行的动画.当在平面座标系中有多个离散的点的时候,可以是离散的,也可以直线相连后进行插值计算,也可以使用圆滑的曲线将他们相连后进行插值计算.calculationMode目前提供如下几种模式:
kCAAnimationLinear calculationMode的默认值,表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算; kCAAnimationDiscrete 离散的,就是不进行插值计算,所有关键帧直接逐个进行显示; kCAAnimationPaced 使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions无效; kCAAnimationCubic 对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,这里的主要目的是使得运行的轨迹变得圆滑;
kCAAnimationCubicPaced 看这个名字就知道和kCAAnimationCubic有一定联系,其实就是在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的.

http://blog.csdn.net/u011700462/article/details/37540709

  1. removedOnCompletion 和 fillMode
    众所周知,CAPropertyAnimation 在添加到 CALayer 上的时候,会产生一份 copy,并且,取 Animation 的时候也必须用 animation 的 key 来取。
    在动画完成后,如果removedOnCompletion=YES,则会自动的从 CALayer 上面移除动画。
    我们也知道,CALayer 添加 CAPropertyAnimation 后,会将原 CALayer 隐藏,同时产生一个新的 CALayer 专门用来做动画,等动画完成后,默认情况下原 CALayer 会再次出现。例如,对 position 做动画,控件移动到最终位置后会突然回到初始位置。
    如果想让控件始终保持在最终位置,可以设置 fillMode。

kCAFillModeForwards //当动画结束后,layer会一直保持着动画最后的状态.
kCAFillModeBackwards //这个和kCAFillModeForwards是相对的,就是在动画开始前,你只要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始.你可以这样设定测试代码,将一个动画加入一个layer的时候延迟5秒执行.然后就会发现在动画没有开始的时候,只要动画被加入了layer,layer便处于动画初始状态.
kCAFillModeBoth
kCAFillModeRemoved

设置 fillMode 为 kCAFillModeForwards、kCAFillModeBoth,必须让 removedOnCompletion 为 NO 才行。

http://www.cnblogs.com/xs514521/archive/2016/02/16/5192378.html

  1. autoreverses
    这个属性可以让动画正向完成后再做反向动画。如果这个位 YES 那么 repeatCount 的 1 是指正向+反向。
    repeatCount 可以与 fillMode 结合。例如 repeatCount=1.5 fillMode = kCAFillModeForwards,那么动画是 fromValue-toValue-fromValue-toValue。
    这个过程与下面谈的时间控制息息相关。

CALayer 和 CoreAnimation 中动画运行时间的控制

CAAnimation 和 CALayer 类都实现了一个协议——CAMediaTiming。这个协议中的方法可以帮我们控制动画的进度。
每一个 CALayer 都有自己的控制动画进度的时间线,父 layer 的时间线会影响到子 layer 的时间线。
这也就是我们平时设置 CALayer 的 beginTime 时,必须 CACurrentMediaTime() + 0.5 才行。
CALayer 的时间线可以使用 convertTime:fromLayer: convertTime:toLayer: 转换,就像在不同的 layer 中转换坐标一样。
设置 CALayer 的 speed 属性,可以让动画播放的速度成倍的改变,设置成 0 可以让动画暂停,设置小于1可以让动画后退播放。

设置 CALayer 的 beginTime 和 speed,会影响到 CALayer 以及其子 layer 的所有动画运行速度。

当然,也可以为 CoreAnimation 设置 beginTime 和 speed。如果 layer 上面有多个 animation,这样设置只会影响一个 animation 的动画速度。然而,如果 CoreAnimation 已经添加到 CALayer 上,就不能修改 beginTime 和 speed 了,必须从 CALayer 上面移除后再添加才可以,直接修改会报错。

对 beginTime 和 speed 的控制,在平时的开发中可能用到的不多,但是系统提供的自定义 ViewController 转场相关代码中,底层就是使用这个特性实现的。

CAAnimationGroup 的 duration autoreverses repeatCount 等会影响到添加到 group 中的 animation ,这些参数会覆盖 animation 的这些参数。

其它几个小技巧

1.CoreAnimation 是在子线程运行的
动画的各个属性都是在子线程计算的,如果需要在动画结束后精确的改变程序中的某些参数,请使用 CAAnimationDelegate。使用 GCD 的 dispatch_after 来实现动画完成后进行某些操作可能会出现操作发生的时间点与动画完成的时间点不完全一致的情况。

2.CGContextAddArc 的参数与坐标系

CGContextAddArc(Context, CGFloat x , CGFloat y, CGFloat radius, CGFloat startAngle , CGFloat endAngle, int clockwise);

xy 圆弧原点坐标 radius 半径 startAngle 和 endAngle 必须传入弧度 colckwise 0是逆时针,1是顺时针。圆弧会从 startAngle 与 radius 描述的坐标,按照 colckwise 规定的方向绘制到 endAngle 描述的坐标,即使在数学上 startAngle 比 endAngle 大也不例外。

在 CoreGraphics 绘图中,坐标系 x 轴正方向向右,y 轴正方向向上。弧度0位于 x 轴的正方向上 pi/2 位于 y 轴的正方向上,与数学上的定义是一致的。
如果 Context 是从 UIKit 的绘图方法中取得的,比如 UIGraphicsImageContext 那么会应用一个 transform,这时再计算 CGContextAddArc 的参数也要做出相应变化才行。

3.repeatCount = repeatDuration/duration
repeatCount 和 repeatDuration 不能同时使用。

4.CoreAnimation 有个属性 removedOnCompletion ,如果是 YES,那么动画过程中按 Home 键退到后台,再进入 app,动画就没了。

5.有个挺好用的库 lottie 这个有 lottie-ios 和 lottie-android,可以把 After Effect 制作的动画直接导入 app 中,但是在管理动画播放进度上面力不从心。如果遇到比较复杂的又与用户操作没有交互的动画(例如,不需要用手势控制动画的进度)可以考虑用这个库。

6.UIImageView 有个 animationimages 属性,可以直接播放序列图,能够起到与 lottie 相同的效果。但是 lottie 方式占用空间比较小,效果也比较好,因为 lottie 可以使用 json 和矢量图描述动画。

** 以上记录了最近使用动画和绘图相关方法时发现的一些技巧和遇到的一些问题。这些点都可以搜索到更详细的解释说明,在此只是整理记录一下,以后遇到相关问题时,可以从这些点出发去寻找相应的知识解决问题。 **

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

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,421评论 6 30
  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 3,056评论 1 23
  • 书写的很好,翻译的也棒!感谢译者,感谢感谢! iOS-Core-Animation-Advanced-Techni...
    钱嘘嘘阅读 2,274评论 0 6
  • 动画 - UIKit 动画原理 视觉残留效应 运动模糊 做动画的时候要达到 60FPS 时候,画面才能流畅,不然用...
    varlarzh阅读 789评论 2 10
  • 文/红茶Alina 我的大学导员圆圆是一个年轻的的女青年,和我们玩的很好,她和我们分享了她的爱情故事。 她的爱情故...
    赵大撞阅读 238评论 0 0