在iOS各类动画效果中,习惯分为两类:隐式动画和显式动画。
隐式动画
简单的讲,由系统进行相关动画配置,执行动画效果。
例如:
//UIView 调用相关api进行动画
[UIView animateWithDuration:3 animations:^{
self.view.center = CGPointMake(10, 10);
}];
//CALayer 修改相应属性 执行的动画效果
self.layer.position = CGPointMake(10, 10);
//等...
->问题:为什么layer只需要给position赋值就会有动画效果,而view需要在animate...的block中才会有动画效果??
->问题的答案,一起看看下面的探索,应该可以得出。
ps: 先看看下面显式动画CABasicAnimation的使用,再回过头看探索,在某些问题上会比较清晰
为了更好的探索,定义了三个类:LLayer: CALayer LView: UIView LAnimation: NSObject
ps: LAnimation 导入
### LLayer.m
@implementation LLayer
- (id)actionForKey:(NSString *)event {
//这里有一段注释,最后告知会返回一个遵守协议的对象,
//下面只要有一步找到了,便不会往下走了
/*
*主要是下面这几部
* 1. if defined, call the delegate method -actionForLayer:forKey:
* 先找layer的delegate方法,打印得知改变layer属性时delegate为nil,
所以猜想在改变layer属性时,应该是直接走第二步去了
* 2. look in the layer's `actions' dictionary
* 上面没有找到,找actions集合
* 3. look in any `actions' dictionaries in the `style' hierarchy
* 上面还没有找到,在style中查找
* 4. call +defaultActionForKey: on the layer's class
* 最后么,通过default尝试返回id
* 5. 如果都没有找到,返回NSNull,
在官方api中有个NSNull遵守了协议,但是应该没有在协议方法中做相应操作
*/
//这里返回了自定义的一个LAnimation: NSObject
//ps:CAAnimation都是遵守的
return [LAnimation new];
}
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key {
[super addAnimation:anim forKey:key];
//通过这个将CAAnimation 添加到layer
}
@end
### LView.m
@implementation LView
- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
//这里也是尝试返回一个 id
//但是当View的属性不是在animation...block中时,打印得到的NSNull对象
//当在block中时,打印得到<_UIViewAdditiveAnimationAction: 0x60000294c940>
//这个UIViewAdditiveAnimationAction是一个私有的类,查看不到,猜想里面进行了相关动画的添加
/*
* id objc = [super actionForLayer:layer forKey:event];
* NSLog(@"%@",objc);
*/
//这里返回了自定义的一个LAnimation: NSObject
return [LAnimation new];
}
@end
### LAnimation
@implementation LAnimation
- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict {
//先明白下参数
//event 属性的key 类似于 position
//anObject 是layer
//dict 动画的一些参数
//猜想:在这个协议方法中, 假设是按照下面这种方式进行添加animation的
//1. 按照系统的调用,执行这个方法的应该是CAAnimation
//2. 这里应该就是 [anObject addAnimation:self forKey:event];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:event];
animation.toValue = @(CGPointMake(300, 300));
animation.duration = 10;
[anObject addAnimation:animation forKey:event];
}
@end
探索1:给layer.position赋值,会走那些方法?
/*
*1. 修改layer相关属性,调用layer的方法 - (id)actionForKey:(NSString *)event
*2. - (id)actionForKey:(NSString *)event会返回一个遵守的对象
*3. 遵守的对象会调用 CAAction协议方法- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict
*4. 在协议方法中 会为layer添加一个CAAnimation对象 进行相关动画配置
*5. runloop会开启动画事务,执行一个0.25s的动画
*/
关于第4点,是猜想。在LAnimation中实现的操作,只是向layer添加了一个CAAnimation。
但我不能100%确定官方是否是这样做的,
即使我们打印出来,在actionForKey 方法中父类返回的是一个CAAnimation
探索2:给view.center赋值,会走那些方法?
ps. 通过两种方式赋值,
view.center = ..
[UIView animateWithDuration:0.25 animations:^{
view.center = ..;
}];
/*
* 1. 修改view的属性,会去改变view.layer 对应属性
* 2. 调用layer方法 - (id)actionForKey:(NSString *)event
* 3. 调用layer.delegete 方法- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event
* 4. 接下来的几步,猜想和layer接下来的调用一样
*/
关于第2点调用delegate的方法,打印得到layer.delegate指向的是当前view
而上面打印得到layer.delegate为nil
结论猜想:
1. 每个隐式动画,都是在layer上面添加了一个CAAnimation
2. UIView 切断了layer层CAAnimation的添加,达到无动画效果
实操:
//关闭隐式动画,利用动画事务CATransaction
[CATransaction begin];
[CATransaction setDisableActions:YES];//主要这里配置 禁用动画
//这里是可能引起隐式动画的相关属性修改
layer.position = point;
[CATransaction commit];
显式动画
这里主要介绍的是CAAnimation,将CAAnimation主动添加到layer上。
CAAnimation是一个抽象类,不会直接使用,一般都是使用子类。
一般也不会使用CAPropertyAnimation
这里针对坐标大小进行的相关动画操作,改变的都不是layer本身,动画结束后,会发现视图回到初始位置,这里会有代理监听动画结束开始,可以在代理里面进行原始参数的修改。
另:在动画里面,有两个参数
[basicAnimation setRemovedOnCompletion:NO];
basicAnimation.fillMode = kCAFillModeBoth;
设置之后,动画结束后视图不会回到初始位置,这只是表象,看到的是执行动画的那个临时图层,并不是原始的那个图层。这一点,当动画结束后,看图层会很清晰
CASpringAnimation
//这个动画效果,类似于拉开弹簧松手
CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:@"position"];
springAnimation.mass = 5; //坠物
springAnimation.stiffness = 10000; //刚度
springAnimation.damping = 300; //阻尼
springAnimation.initialVelocity = 100;//初速度
springAnimation.toValue = @(point);
//动画需要的时长 这里参数要么不配置 要么这样配置成预估的时间
springAnimation.duration = springAnimation.settlingDuration;
[self.animationView.layer addAnimation:springAnimation forKey:nil];
CABasicAnimation
//这个就一个平滑的动画过程
CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
basicAnimation.toValue = @(point);
[basicAnimation setRemovedOnCompletion:NO];
basicAnimation.fillMode = kCAFillModeBoth;
[self.animationView.layer addAnimation:basicAnimation forKey:nil];
CAKeyframeAnimation
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:200 startAngle:0 endAngle:2 * M_PI clockwise:YES];
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyframeAnimation.path = path.CGPath;
keyframeAnimation.repeatCount = NSNotFound; //执行次数无数次
[self.animationView.layer addAnimation:keyframeAnimation forKey:nil];
//也可以通过values 指定几个值进行相关配置 根据keyPath具体调试
CAAnimationGroup
CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation1.toValue = @(M_PI);
animation1.duration = 1;
animation1.repeatCount = NSNotFound;
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
animation2.toValue = @0.2;
animation2.duration = 1;
animation2.repeatCount = NSNotFound;
animation2.autoreverses = YES;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:200 startAngle:0 endAngle:2 * M_PI clockwise:YES];
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyframeAnimation.path = path.CGPath;
keyframeAnimation.repeatCount = NSNotFound; //执行次数无数次
keyframeAnimation.duration = 2;
keyframeAnimation.beginTime = 3;//进行组动画的时候 这个参数还是很好用的 当动画执行3秒后 才执行关键帧动画 关键帧动画2秒执行一次
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 10;
group.animations = @[animation1,animation2,keyframeAnimation];
group.repeatCount = NSNotFound;
[self.animationView.layer addAnimation:group forKey:nil];
//这里改变单个动画不同参数 改变组动画不同参数 看看变化 多实践吧
CATransition
CATransition *trans = [CATransition animation];
// 效果
/*
kCATransitionFade 交叉淡化过渡
kCATransitionMoveIn 新视图移到旧视图上面
kCATransitionPush 新视图把旧视图推出去
kCATransitionReveal 将旧视图移开,显示下面的新视图
2.用字符串表示
pageCurl 向上翻一页
pageUnCurl 向下翻一页
rippleEffect 滴水效果
suckEffect 收缩效果,如一块布被抽走
cube 立方体效果
oglFlip 上下翻转效果
*/
trans.type = @"pageUnCurl";
// 开始位置 (0-1)
trans.startProgress = 0;
// 结束位置 (0-1)
trans.endProgress = 1;
// 效果方向
trans.subtype = kCATransitionFromTop;
// 重复次数
trans.repeatCount = 10;
// 持续时间
trans.duration = 3;
[self.animationView.layer addAnimation:trans forKey:nil];
这里还有一个比较重要的协议CAMediaTiming,里面包含了动画的执行时长、执行次数、是否需要原路返回等参数
//动画多久开始执行 在组动画总效果非常明显
@property CFTimeInterval beginTime;
//单次动画执行时长
@property CFTimeInterval duration;
//理解成一个时间单位
@property float speed;
//额外偏移量 这个参数影响这beginTime
//假设beginTime=3 timeOffset=2 实际效果是1秒后动画便会执行
//默认0
@property CFTimeInterval timeOffset;
//执行次数 NSNotFound 无数次
@property float repeatCount;
//动画执行总时长 上面有一个speed
//repeatDuration / speed 可以得到动画执行次数
@property CFTimeInterval repeatDuration;
//是否原路返回 默认NO
@property BOOL autoreverses;
/*
关于fillMode,有一段很清晰的探索,结合组动画配置beginTime等
kCAFillModeRemoved也是fillMode的默认属性,
动画的效果:
开始,colorLayer的size为(20,20),当到2s的时候,
2s时,colorLayer的size突然就变为(100,100),
然后开始动画,当到7s的时候colorLayer的duration已经完成,
此时colorLayer的size会突然 变为(20,20),然后在持续3s,当总时间到10s时结束。
当fillMode的属性设置为kCAFillModeForwards的时候,
动画效果为:
开始时,colorLayer的size为(20,20),
当到2s的时候,colorLayer的size突然就变为(100,100),
然后开始做动画,之前都和kCAFillModeRemoved都一样,
不一样的时当到7s的时候colorLayer的duration已经完成,
此时colorLayer的size还会保持在(400,400),
然后在持续3s,当总时间到10s时结束,此时size才变为(20,20)
当fillMode的属性设置为kCAFillModeBackwards的时候,
动画效果为:
开始时,colorLayer的size就为(100,100),
当到2s的时候,colorLayer开始做动画,
当到7s的时候colorLayer的duration已经完成,
此时colorLayer的size会突然 变为(20,20),
然后在持续3s,当总时间到10s时结束。
kCAFillModeBoth是kCAFillModeForwards和kCAFillModeBackwards的结合。
配合removedOnCompletion一起使用,kCAFillModeForwards kCAFillModeBoth这两个是保存最后状态
*/
@property(copy) CAMediaTimingFillMode fillMode;
额外资料
查看layer中那些属性可以有动画,注释最后带Animatable都是可以进行动画的
/* CATransform3D Key Paths */
/* 旋转x,y,z分别是绕x,y,z轴旋转 */
static NSString *kCARotation = @"transform.rotation";
static NSString *kCARotationX = @"transform.rotation.x";
static NSString *kCARotationY = @"transform.rotation.y";
static NSString *kCARotationZ = @"transform.rotation.z";
/* 缩放x,y,z分别是对x,y,z方向进行缩放 */
static NSString *kCAScale = @"transform.scale";
static NSString *kCAScaleX = @"transform.scale.x";
static NSString *kCAScaleY = @"transform.scale.y";
static NSString *kCAScaleZ = @"transform.scale.z";
/* 平移x,y,z同上 */
static NSString *kCATranslation = @"transform.translation";
static NSString *kCATranslationX = @"transform.translation.x";
static NSString *kCATranslationY = @"transform.translation.y";
static NSString *kCATranslationZ = @"transform.translation.z";
/* 平面 */
/* CGPoint中心点改变位置,针对平面 */
static NSString *kCAPosition = @"position";
static NSString *kCAPositionX = @"position.x";
static NSString *kCAPositionY = @"position.y";
/* CGRect */
static NSString *kCABoundsSize = @"bounds.size";
static NSString *kCABoundsSizeW = @"bounds.size.width";
static NSString *kCABoundsSizeH = @"bounds.size.height";
static NSString *kCABoundsOriginX = @"bounds.origin.x";
static NSString *kCABoundsOriginY = @"bounds.origin.y";
/* 透明度 */
static NSString *kCAOpacity = @"opacity";
/* 背景色 */
static NSString *kCABackgroundColor = @"backgroundColor";
/* 圆角 */
static NSString *kCACornerRadius = @"cornerRadius";
/* 边框 */
static NSString *kCABorderWidth = @"borderWidth";
/* 阴影颜色 */
static NSString *kCAShadowColor = @"shadowColor";
/* 偏移量CGSize */
static NSString *kCAShadowOffset = @"shadowOffset";
/* 阴影透明度 */
static NSString *kCAShadowOpacity = @"shadowOpacity";
/* 阴影圆角 */
static NSString *kCAShadowRadius = @"shadowRadius";