前言
每个view都是由一个底层的layer来驱动的,也可以理解成view是layer的delegate(委托),当单独的layer属性发生变化时,都会触发一个从旧值过渡到新值的一个简单动画;如果是view中的layer,只是从这一帧直接编导下一帧,默认的隐式动画的layer行为就不起作用了。但是在通常情况下,view的layer某个属性在block外面被修改并不会触发动画,在block animation中却会触发动画,这是为什么?我在网络上查找资料看到的解释是,UIView默认情况下禁止了layer动画,而在block中又启用了它们,下面就开始简单介绍一些UIView动画的触发的过程,也权当我在学习过程中做一下学习笔记。
(一)layer通过向它的delegate发送actionForLayer:forKey来询问对应属性的action,如果是返回一个对象就执行这个对象的操作;返回nil或者是null都不会执行;这个就可以解释了上面描述的为什么view的layer某个属性在block外面被修改并不会触发动画,在block animation中却会触发动画;下面看一个段代码:
NSLog(@"outside animation block: %@",
[testView actionForLayer:testView.layer forKey:@"position"]);
[UIView animateWithDuration:0.3 animations:^{
NSLog(@"inside animation block: %@",
[testView actionForLayer:testView.layer forKey:@"position"]);
}];
输出的结果:
2016-06-03 13:54:06.425 H5Test[1700:147380] outside animation block: <null>
2016-06-03 13:54:06.426 H5Test[1700:147380] inside animation block: <_UIViewAdditiveAnimationAction: 0x7fff4bc22dd0>
很明显,在block外面询问对应属性的action返回的是<null>,而在block内的话返回的是一个_UIViewAdditiveAnimationAction对象,这就解释了上面的理论。
(二)下面就是说一下UIView的block动画是一个怎样的执行流程,然后我写了一个很简单的block动画,然后通过这个动画,我记录了一下分析得出来的内容;(注,如果设置的属性的值前后没有改变,是不会触发动画的,这估计是苹果在内部做了逻辑判断;例如
testView.layer.opacity = 1;
[UIView animateWithDuration:5 animations:^{
testView.layer.opacity = 1;
} completion:^(BOOL finished) {
if (finished) {
NSLog(@"completion");
}
}];是不会触发动画的)。
#import "DRInspectionView.h"
#import "DRInspectionLayer.h"
@implementation DRInspectionView
+ (Class)layerClass
{
return [DRInspectionLayer class];
}
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
NSLog(@"event -- %@",event);
return [super actionForLayer:layer forKey:event];
}
@end
DRInspectionView *testView = [[DRInspectionView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
testView.backgroundColor = [UIColor orangeColor];
// CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
// fadeIn.duration = 1;
// fadeIn.fromValue = @0;
// fadeIn.fillMode = kCAFillModeBoth;
// fadeIn.toValue = @0;
// fadeIn.delegate = [DRAnimationBlockDelegate animationDelegateWithBeginning:^{
//
// NSLog(@"animation start");
//
// } completion:^(BOOL finished) {
// if (finished) {
//
// NSLog(@"animation completion");
// }
// }];
//testView.layer.opacity = 1.0; //更改 model 的值 ...
// ...然后添加动画对象
//[testView.layer addAnimation:fadeIn forKey:@"fade in slowly"];
testView.layer.opacity = 0;
[UIView animateWithDuration:5 animations:^{
testView.layer.opacity = 1;
} completion:^(BOOL finished) {
if (finished) {
NSLog(@"completion");
}
}];
[self.view addSubview:testView];
(1)执行animations,检测改变了layer的opacity属性,然后执行- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
NSLog(@"event -- %@",event);
return [super actionForLayer:layer forKey:event];
},返回执行动画的对象;
(2)然后为layer添加动画,执行- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key
{
NSLog(@"adding animation: %@", [anim debugDescription]);
[super addAnimation:anim forKey:key];
},
(3)最后会执行第(2)步添加的动画,看看输出的内容信息:
2016-06-03 14:07:14.092 H5Test[1748:154159] event -- bounds
2016-06-03 14:07:14.092 H5Test[1748:154159] event -- opaque
2016-06-03 14:07:14.092 H5Test[1748:154159] event -- position
2016-06-03 14:07:14.092 H5Test[1748:154159] event -- backgroundColor
2016-06-03 14:07:14.092 H5Test[1748:154159] event -- opaque
2016-06-03 14:07:14.092 H5Test[1748:154159] event -- opacity
2016-06-03 14:07:14.092 H5Test[1748:154159] event -- opacity
2016-06-03 14:10:30.829 H5Test[1748:154159] adding animation: <CABasicAnimation:0x7ff599443d10; delegate = <UIViewAnimationState: 0x7ff5994396f0>; fillMode = both; timingFunction = easeInEaseOut; duration = 5; fromValue = 0.0; keyPath = opacity>
2016-06-03 14:10:30.830 H5Test[1748:154159] event -- onOrderIn
2016-06-03 14:10:35.842 H5Test[1748:154159] completion
先是通过- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event一直检测view的layer的所有属性,当检测到opacity属性发生变化时返回了一个动画对象,并执行了- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key添加了动画事件,最后执行。
(三)研究block动画执行
(1)block动画的执行过程:先执行了animations block中的内容,发现opacity属性值被修改。然后触发view中的actionForLayer:forKey方法并返回动画对象,最后执行view中layer的隐式动画。通过了解了动画执行的部分内部执行的原理,能更好的去使用block动画,也会发现UIView的封装动画不会那么神秘了。当然,研究得深入的话,我们还可以实现自己的一套基于block的动画APIs,说到这里,其实我们使用动画的时候只能delegate委托回调,用惯了block的人还是习惯于block回调,用起来比较方便。
(2)动画的delegate的UIViewAnimationState内部私有类实现了- (void)animationDidStart:(CAAnimation *)anim以及- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag方法,这个内部的私用类的也有一个delegate也是一个私有类UIViewAnimationBlockDelegate负责执行delegate回调,因此我们可以修改一下,改成block回调;
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface ZJAnimationBlockDelegate : NSObject
@property (copy) void(^start)(void);
@property (copy) void(^stop)(BOOL);
+ (instancetype)animationDelegateWithBeginning:(void(^)(void))beginning
completion:(void(^)(BOOL finished))completion;
@end
#import "ZJAnimationBlockDelegate.h"
@implementation ZJAnimationBlockDelegate
+ (instancetype)animationDelegateWithBeginning:(void (^)(void))beginning
completion:(void (^)(BOOL))completion
{
ZJAnimationBlockDelegate *result = [ZJAnimationBlockDelegate new];
result.start = beginning;
result.stop = completion;
return result;
}
- (void)animationDidStart:(CAAnimation *)anim
{
if (self.start) {
self.start();
}
self.start = nil;
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (self.stop) {
self.stop(flag);
}
self.stop = nil;
}
@end
//使用block回调;
CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeIn.duration = 1;
fadeIn.fromValue = @0;
fadeIn.fillMode = kCAFillModeBoth;
fadeIn.toValue = @0;
fadeIn.delegate = [ZJAnimationBlockDelegate animationDelegateWithBeginning:^{
NSLog(@"animation start");
} completion:^(BOOL finished) {
if (finished) {
NSLog(@"animation completion");
}
}];
这里自定义了一个委托类,委托类定义并实现了start,stop两个回调block,里面实现了- (void)animationDidStart:(CAAnimation *)anim以及- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag两个方法,在方法中分别执行block回调,这一步就相当于替代了UIViewAnimationBlockDelegate的回调操作,实现动画block回调。