专栏名称: Adrenine
iOS开发
目录
相关文章推荐
51好读  ›  专栏  ›  Adrenine

iOS动画

Adrenine  · 掘金  ·  · 2017-12-13 08:46

正文

iOS动画-从不会到熟练应用#前言上次总结了多线程的用法,这次再复习下iOS动画的东西.这次依然先是以api为主,因为好多人还是api好多的东西还不会用.然后中间穿插些例子,例子和代码文章中都会有.因为篇幅比较长,先列一下大纲.动画的继承结构

CAAnimation{ CAPropertyAnimation{ CABasicAnimation{ CASpringAnimation } CAKeyframeAnimation } CATransition  CAAnimationGroup}

###CAAnimation(动画根类,不可以直接使用)CAAnimation-属性(复杂点的属性,下面会有详细解释)

//动画的代理回调,下面会有@property(nullable, strong) id delegate;//动画执行完以后是否移除动画,默认YES@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;//动画的动作规则,包含以下值//kCAMediaTimingFunctionLinear 匀速//kCAMediaTimingFunctionEaseIn 慢进快出//kCAMediaTimingFunctionEaseOut 快进慢出//kCAMediaTimingFunctionEaseInEaseOut 慢进慢出 中间加速//kCAMediaTimingFunctionDefault 默认@property(nullable, strong) CAMediaTimingFunction *timingFunction;

以上属性的详解:delegate:动画执行的代理,在动画开始前设定,不用显式的写在代码里,它包含两个方法:动画开始回调 - (void)animationDidStart:(CAAnimation *)anim; 动画结束回调 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag; removedOnCompletion:动画完成后是否移除动画.默认为YES.此属性为YES时, fillMode不可用,具体为什么不可用,可以自己结合两个属性分析一下,这里不再赘述.timingFunction 设置动画速度曲线,默认值上面已经给出.下面说它的几个方法:这两个方法是一样的.如果我们对系统自带的速度函数不满意,可以通过这两个函数创建一个自己喜欢的速度曲线函数,具体用法可以参考这篇文章CAMediaTimingFunction的使用

+ (instancetype)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;- (instancetype)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;

获取曲线函数的缓冲点,具体用法可以参考这篇文章:iOS-核心动画高级编程/10-缓冲 - (void)getControlPointAtIndex:(size_t)idx values:(float[2])ptr; CAAnimation 协议的属性//开始时间.这个属性比较复杂,傻瓜用法为:CACurrentMediaTime() + x,//其中x为延迟时间.如果设置 beginTime = CACurrentMediaTime() + 1.0,产生的效果为延迟一秒执行动画,下面详解原理

@property CFTimeInterval beginTime;//动画执行时间,此属性和speed有关系speed默认为1.0,如果speed设置为2.0,那么动画执行时间则为duration*(1.0/2.0).@property CFTimeInterval duration;//动画执行速度,它duration的关系参考上面解释@property float speed;//动画的时间延迟,这个属性比较复杂,下面详解@property CFTimeInterval timeOffset;//重复执行次数@property float repeatCount;//重复执行时间,此属性优先级大于repeatCount.也就是说如果repeatDuration设置为1秒重复10次,那么它会在1秒内执行完动画.@property CFTimeInterval repeatDuration;//是否自动翻转动画,默认NO.如果设置YES,那么整个动画的执行效果为A->B->A.@property BOOL autoreverses;//动画的填充方式,默认为: kCAFillModeRemoved,包含以下值//kCAFillModeForwards//动画结束后回到准备状态//kCAFillModeBackwards//动画结束后保持最后状态//kCAFillModeBoth//动画结束后回到准备状态,并保持最后状态//kCAFillModeRemoved//执行完成移除动画@property(copy) NSString *fillMode;

以上属性的详解:beginTime:刚才上面简单解释了下这个属性的用法:CACurrentMediaTime()+ x 会使动画延迟执行x秒.不知道到这里有没有人想过如果-x会出现怎么样效果?假设我们有执行一个3秒的动画,然后设置beginTime = CACurrentMediaTime()- 1.5那么执行动画你会发现动画只会执行后半段,也就是只执行后面的3-1.5s的动画.为什么会这样?其实动画都有一个timeline(时间线)的概念.动画开始执行都是基于这个时间线的绝对时间,这个时间和它的父类有关(系统的属性注释可以看到).默认的CALayer的beginTime为零,如果这个值为零的话,系统会把它设置为CACurrentMediaTime(),那么这个时间就是正常执行动画的时间:立即执行.所以如果你设置beginTime=CACurrentMediaTime()+x;它会把它的执行时间线推迟x秒,也就是晚执行x秒,如果你beginTime=CACurrentMediaTime()-x;那它开始的时候会从你动画对应的绝对时间开始执行.timeOffset:时间偏移量,默认为0;既然它是时间偏移量,那么它即和动画时间相关.这么解释:假设我们设置一个动画时间为5s,动画执行的过程为1->2->3->4->5,这时候如果你设置timeOffset = 2s那么它的执行过程就会变成3->4->5->1->2如果你设置timeOffset = 4s那么它的执行过程就会变成5->1->2->3->4,这么说应该很明白了吧?CAPropertyAnimation属性动画,抽象类,不能直接使用CAPropertyAnimation的属性

//需要动画的属性值@property(nullable, copy) NSString *keyPath;//属性动画是否以当前动画效果为基础,默认为NO@property(getter=isAdditive) BOOL additive;//指定动画是否为累加效果,默认为NO@property(getter=isCumulative) BOOL cumulative;//此属性相当于CALayer中的transform属性,下面会详解@property(nullable, strong) CAValueFunction *valueFunction;

以上属性的详解:CAPropertyAnimation是属性动画.顾名思义也就是针对属性才可以做的动画.那它可以对谁的属性可以做动画?是CALayer的属性,比如:bounds,position等.那么问题来了,我们改变CALayer的position可以直接设置[CAPropertyAnimation animationWithKeyPath:@"position"]如果我们设置它的transform(CATransform3D)呢?CATransform3D是一个矩阵,如果我们想为它做动画怎么办?下面这个属性就是用来解决这个问题的.valueFunction:我们来看它可以设置的值:

kCAValueFunctionRotateXkCAValueFunctionRotateYkCAValueFunctionRotateZkCAValueFunctionScalekCAValueFunctionScaleXkCAValueFunctionScaleYkCAValueFunctionScaleZkCAValueFunctionTranslatekCAValueFunctionTranslateXkCAValueFunctionTranslateYkCAValueFunctionTranslateZ

说到这里大家应该都知道该怎么用了吧~.CAPropertyAnimation的方法

//通过key创建一个CAPropertyAnimation对象+ (instancetype)animationWithKeyPath:(nullable NSString *)path;

下面我们来看一下可以设置属性动画的属性归总:

CATransform3D{ rotation旋转 transform.rotation.x transform.rotation.y transform.rotation.z scale缩放 transform.scale.x transform.scale.y transform.scale.z translation平移 transform.translation.x transform.translation.y transform.translation.z}CGPoint{ position position.x position.y}CGRect{ bounds bounds.size bounds.size.width bounds.size.height bounds.origin bounds.origin.x bounds.origin.y}property{ opacity backgroundColor cornerRadius borderWidth contents Shadow{ shadowColor shadowOffset shadowOpacity shadowRadius }}

总结: CAAnimation是基类, CAPropertyAnimation是抽象类,两者都不可以直接使用, 那我们只有使用它的子类了.CABasicAnimation基本动画CABasicAnimation的属性

//开始值@property(nullable, strong) id fromValue;//结束值@property(nullable, strong) id toValue;//结束值@property(nullable, strong) id byValue;

这三个属性之间的规则fromValue和toValue不为空,动画的效果会从fromValue的值变化到toValue.fromValue和byValue都不为空,动画的效果将会从fromValue变化到fromValue+byValuetoValue和byValue都不为空,动画的效果将会从toValue-byValue变化到toValue只有fromValue的值不为空,动画的效果将会从fromValue的值变化到当前的状态.只有toValue的值不为空,动画的效果将会从当前状态的值变化到toValue的值.只有byValue的值不为空,动画的效果将会从当前的值变化到(当前状态的值+byValue)的值.CABasicAnimation看起来不太复杂,但实际只用这个就足以可以做很多种动画了,下面简单用一下,先看效果:CABasicAnimation.gif然后再看下实现代码:

#import "ViewController.h"#import "TFEasyCoder.h"@interface ViewController ()@property (nonatomic,strong)UIView *demoView;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; kdeclare_weakself; NSArray *titles = @[@"淡入淡出",@"缩放",@"旋转",@"平移"]; for (unsigned int i = 0; i < titles.count; i++) { [UIButton easyCoder:^(UIButton *ins) { [weakSelf.view addSubview:ins]; ins.backgroundColor = [UIColor brownColor]; ins.tag = i; ins.frame = CGRectMake(10, 50 + 80 * i, 100, 60); [ins setTitle:titles[i] forState:UIControlStateNormal]; [ins addTarget:self action:@selector(animationBegin:) forControlEvents:UIControlEventTouchUpInside]; }]; } [UIView easyCoder:^(UIView *ins) { ins.frame = CGRectMake(0, 0, 100, 100); ins.backgroundColor = [UIColor redColor]; ins.center = self.view.center; weakSelf.demoView = ins; [weakSelf.view addSubview:weakSelf.demoView]; }];}-(void)animationBegin:(UIButton *)btn{ CABasicAnimation *animation = nil; switch (btn.tag) { case 0:{ //淡如淡出 animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; [animation setFromValue:@1.0]; [animation setToValue:@0.1]; }break; case 1:{ //缩放 animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; [animation setFromValue:@1.0];//设置起始值 [animation setToValue:@0.1];//设置目标值 }break; case 2:{ //旋转 animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; //setFromValue不设置,默认以当前状态为准 [animation setToValue:@(M_PI)]; }break; case 3:{ //平移 animation = [CABasicAnimation animationWithKeyPath:@"position"]; //setFromValue不设置,默认以当前状态为准 [animation setToValue:[NSValue valueWithCGPoint:CGPointMake(self.view.center.x, self.view.center.y + 200)]]; }break; default:break; } [animation setDelegate:self];//代理回调 [animation setDuration:0.25];//设置动画时间,单次动画时间 [animation setRemovedOnCompletion:NO];//默认为YES,设置为NO时setFillMode有效 /** *设置时间函数CAMediaTimingFunction *kCAMediaTimingFunctionLinear 匀速 *kCAMediaTimingFunctionEaseIn 开始速度慢,后来速度快 *kCAMediaTimingFunctionEaseOut 开始速度快 后来速度慢 *kCAMediaTimingFunctionEaseInEaseOut = kCAMediaTimingFunctionDefault 中间速度快,两头速度慢 */ [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; //设置自动翻转 //设置自动翻转以后单次动画时间不变,总动画时间增加一倍,它会让你前半部分的动画以相反的方式动画过来 //比如说你设置执行一次动画,从a到b时间为1秒,设置自动翻转以后动画的执行方式为,先从a到b执行一秒,然后从b到a再执行一下动画结束 [animation setAutoreverses:YES]; //kCAFillModeForwards//动画结束后回到准备状态 //kCAFillModeBackwards//动画结束后保持最后状态 //kCAFillModeBoth//动画结束后回到准备状态,并保持最后状态 //kCAFillModeRemoved//执行完成移除动画 [animation setFillMode:kCAFillModeBoth]; //将动画添加到layer,添加到图层开始执行动画, //注意:key值的设置与否会影响动画的效果 //如果不设置key值每次执行都会创建一个动画,然后创建的动画会叠加在图层上 //如果设置key值,系统执行这个动画时会先检查这个动画有没有被创建,如果没有的话就创建一个,如果有的话就重新从头开始执行这个动画 //你可以通过key值获取或者删除一个动画: //[self.demoView.layer animationForKey:@""]; //[self.demoView.layer removeAnimationForKey:@""] [self.demoView.layer addAnimation:animation forKey:@"baseanimation"];}/** * 动画开始和动画结束时 self.demoView.center 是一直不变的,说明动画并没有改变视图本身的位置 */- (void)animationDidStart:(CAAnimation *)anim{ NSLog(@"动画开始------:%@", NSStringFromCGPoint(self.demoView.center));}- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{ NSLog(@"动画结束------:%@", NSStringFromCGPoint(self.demoView.center));}

以上效果和代码还有注释都解释的很详细了,然后源码在这里:demo-基本动画CASpringAnimation弹性动画CASpringAnimation的属性(iOS9新加)

//理解下面的属性的时候可以结合现实物理现象,比如把它想象成一个弹簧上挂着一个金属小球//质量,振幅和质量成反比@property CGFloat mass;//刚度系数(劲度系数/弹性系数),刚度系数越大,形变产生的力就越大,运动越快@property CGFloat stiffness;//阻尼系数,阻止弹簧伸缩的系数,阻尼系数越大,停止越快,可以认为它是阻力系数@property CGFloat damping;//初始速率,动画视图的初始速度大小速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反.@property CGFloat initialVelocity;//结算时间,只读.返回弹簧动画到停止时的估算时间,根据当前的动画参数估算通常弹簧动画的时间使用结算时间比较准确@property(readonly) CFTimeInterval settlingDuration;

下面我们写一个demo看看效果:spring弹性动画.gif以上gif的代码为:

CASpringAnimation *spring = [CASpringAnimation animationWithKeyPath:@"position.y"];spring.damping = 5;spring.stiffness = 100;spring.mass = 1;spring.initialVelocity = 0;spring.duration = spring.settlingDuration;spring.fromValue = @(self.demoView1.center.y);spring.toValue = @(self.demoView1.center.y + (btn.selected?+200:-200));spring.fillMode = kCAFillModeForwards;[self.demoView1.layer addAnimation:spring forKey:nil];

CASpringAnimation效果不错,但是很不幸只有iOS9+系统才能使用,这就很操蛋了.以前项目有过这样的需求,然后就自己写了一个类似的动画,效果在下面:自定义弹性动画.gif然后上面自定义弹性动画的代码在这里:

#import "UIView+ShakeAnimation.h"#import <objc/runtime.h>typedef void (^RunAnimationBlock)();@interface UIView ()@property (nonatomic, copy)RunAnimationBlock block;@end@implementation UIView (ShakeAnimation)-(void)startAnimationFromFrame:(CGRect)framef toFrame:(CGRect)framet duration:(CGFloat)duration shakeTimes:(NSInteger)times stretchPercent:(CGFloat)stretchPercent completion:(void (^)(BOOL finished))completion{ self.layer.masksToBounds = YES; __block CGFloat perTime = duration / times; __block CGFloat perx = (framet.origin.x - framef.origin.x) * stretchPercent / times; __block CGFloat pery = (framet.origin.y - framef.origin.y) * stretchPercent / times; __block CGFloat perw = (framet.size.width - framef.size.width) * stretchPercent / times; __block CGFloat perh = (framet.size.height - framef.size.height) * stretchPercent / times; __block UIView * tmpView = self; __block NSInteger tmpTimes = (NSInteger)times; __block NSInteger tmpsymbol = -1; __weak typeof(self) weakSelf = self; self.block = ^{ [UIView animateWithDuration:perTime animations:^{ CGFloat x = framet.origin.x + perx * tmpTimes; CGFloat y = framet.origin.y + pery * tmpTimes; CGFloat w = framet.size.width + perw * tmpTimes; CGFloat h = framet.size.height + perh * tmpTimes; CGRect rect = CGRectMake(x, y, w, h); tmpView.frame = rect; }completion:^(BOOL finished) { tmpTimes = tmpTimes + tmpsymbol; tmpTimes = - tmpTimes; tmpsymbol = - tmpsymbol; if (tmpTimes != 0) { weakSelf.block(); }else{ [UIView animateWithDuration:perTime animations:^{ tmpView.frame = framet; }completion:^(BOOL finished) { completion(YES); }]; } }]; }; self.block();}static char RunAnimationBlockKey;-(RunAnimationBlock)block{ return objc_getAssociatedObject(self, &RunAnimationBlockKey);}-(void)setBlock:(RunAnimationBlock)block{ objc_setAssociatedObject(self, &RunAnimationBlockKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);}@end调用:[self.demoView2 startAnimationFromFrame:CGRectMake(10, 300, 100, 100) toFrame:CGRectMake(10, 300, 300, 100) duration:0.5 shakeTimes:5 stretchPercent:0.3 completion:^(BOOL finished) { NSLog(@"======over======:%@",self.demoView1); }];






请到「今天看啥」查看全文