iOS动画笔记

在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 导入 方便遵守CAAction

### 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.png

这里主要介绍的是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";

你可能感兴趣的:(iOS动画笔记)