这是一篇记载几个关键点以备查询的文章
项目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 是非常强大的。他主要有这样几个参数:
- values
@property(nullable, copy) NSArray *values;
这里可以传的东西与 CABasicAnimation 中能生成隐式动画的东西是一致的。例如:position transform opacity 这些keypath 要求的 NSNumber。
也可以传 CGImage 和 CGPath 等。
下面分别是传入 CGImage 和 CGPath 的例子:
这里可以看到,values 设置为不同的 CGImage 后,效果非常漂亮。
这是一个传入 CGPath 的例子,可以看到,中间红色和蓝色的分析图的动画,在关键帧之间自动插入了补间动画。关键帧的 CGPath 就是用上面画六边形的代码生成的。
- 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]];
- timingFunctions 和 calculationMode
如果 CAKeyframeAnmation 的 values 中有 n 项,那么 timingFunctions 必须有n-1个CAMediaTimingFunction对。
它所描述的是关键帧到帧的变化速率。
kCAMediaTimingFunctionLinear
kCAMediaTimingFunctionDefault 这个曲线怎么形容呢?反正挺好看也挺好用的不是吗
kCAMediaTimingFunctionEaseIn 慢-快
kCAMediaTimingFunctionEaseOut 快-慢
kCAMediaTimingFunctionEaseInEaseOut 慢-快-慢
银行卡跳动的动画使用了 timingFunctions 调节关键帧之间的变换速率,有点弹跳的感觉。
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
- 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
- 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 和矢量图描述动画。
** 以上记录了最近使用动画和绘图相关方法时发现的一些技巧和遇到的一些问题。这些点都可以搜索到更详细的解释说明,在此只是整理记录一下,以后遇到相关问题时,可以从这些点出发去寻找相应的知识解决问题。 **