iOS核心动画笔记8-显式动画

显式动画

1. 属性动画

属性动画作用于图层的某一个单一的属性, 并指定它的目标值, 或者一连串要做动画的值. 属性动画分为基础动画关键帧动画.

1.1 基础动画

动画就是一段时间内发生的改变, 最简单的形式就是从一个值改变到另一个值, 这也是CABaseAnimation的最主要功能. CABaseAnimation是CAPropertyAnimation的一个子类, 而CAPropertyAnimation的父类是CAAnimation, CAAnimation同时也是CoreAnimation所有动画类型的抽象基类. 作为抽象类, 事实上CAAnimation并没有完成太多工作.

CAPropertyAnimation通过制定动画的keyPath作用于一个单一属性, CAAnimation通常作用于一个指定的CALayer, 于是这里的keyPath指的是一个图层的路径了. 实际上它是关键路径而不仅仅是一个属性的名称, 就是说, 动画不仅仅可以作用于图层本身的属性, 而且还包含了它的子成员的属性, 甚至包含虚拟属性.

CABaseAnimation继承自CAPropertyAnimation, 并添加了如下三个属性:

// 动画开始之前属性的值
@property(nullable, strong) id fromValue;
// 动画结束之后属性的值
@property(nullable, strong) id toValue;
// 动画执行过程中, 改变的值
@property(nullable, strong) id byValue;

fromValue, toValue, byValue可以用多种不同的方式进行组合, 但是为了防止冲突, 不能一次性指定三个值. 需要注意的是, 如果是图片必须转换成CGImageRef类型桥接成id类型; 如果是颜色也必须转换成CGColorRef类型桥接成id类型 (__bridge id) .

一个简单的改变背景颜色的基础动画(实际上如果是单独的layer, 用隐式动画比较好):

// 基础动画, 改变颜色
- (void)changeColor {
    
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 70, 80, 80)];
    [self.view addSubview:view];
    view.backgroundColor = [UIColor redColor];

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
    animation.toValue = (__bridge id)[UIColor purpleColor].CGColor;
    animation.duration = 10;
    [view.layer addAnimation:animation forKey:nil];
    
}

如上代码, 存在问题是, 当动画结束之后图层颜色马上变回原来的颜色了, 原因是做动画时候的图层是呈现层, 动画结束之后呈现层被移除, 所以颜色又变回原来的颜色了. 一种解决方案如下:

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 70, 80, 80)];
    [self.view addSubview:view];
    view.backgroundColor = [UIColor redColor];

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
    animation.fromValue = (__bridge id) view.layer.backgroundColor;
    animation.toValue = (__bridge id)[UIColor purpleColor].CGColor;
    animation.duration = 3;
    [view.layer addAnimation:animation forKey:nil];
    
    view.backgroundColor = [UIColor purpleColor];

1.2 CAAnimationDelegate

第七章的隐式动画中, 可以用CATransaction完成块中检测动画的完成, 但是在显式动画中显然是不可能的, 因为这里的动画和事务并没有鸟关联.

在CAAnimationDelegate的代理方法中, 我们可以获取动画的开始和结束.

如下:

/* Called when the animation begins its active duration. */

- (void)animationDidStart:(CAAnimation *)anim {
    
    NSLog(@"animation start");
}

/* Called when the animation either completes its active duration or
 * is removed from the object it is attached to (i.e. the layer). 'flag'
 * is true if the animation reached the end of its active duration
 * without being removed. */

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    
    NSLog(@"animation finish");
}

我们不能通过隐式动画来实现UIView的实例, 因为所有图层的隐式动画都被禁用了. 我们可以通过简单的UIView动画实现简单的动画效果, 但是如果想要更好的控制动画时间, 使用显式动画比较好.

动画的代理方法中, 动画本身会作为一个参数传入委托方法中, 但是需要注意: 委托传入的动画参数是原始值的一个深拷贝, 而不是同一个值.

事实上, 在项目中我们用来唯一标识一个动画的方法是使用key值, 一般我们通过 -setValue:forKey:-valueForKey: 这两个方法来存取. 但是CAAnimation有一个不提欧宁的性能, 它更像一个NSDictionary, 计时你使用动画类型所声明的属性并不匹配, 你也可以随意的使用键值对.

1.2 关键帧动画

CAKeyframeAnimation是另一种UIKit没有暴露出来但功能强大的类. 它也是CAPropertyAnimation的一个子类, 也作用于单一的一个属性, 但是它可以根据已连串随意的值来做动画.

关键帧起源于传动动画, 意思是指主导动画在显著改变发生时重绘当前帧(也就是关键帧), 没帧之间剩下的绘制通过关键帧推算出来. CAKeyFrameAnimation也是同样的道理, 你提供了显著帧之后, CoreAnimation在每帧之间进行插入.

关键帧动画, 可以提供一个数组, 数组中包含一组相关属性的值, 然后CoreAnimation会将这些值以动画的形式展示出来, 两个值之间的值CoreAnimation会计算出来, 但是, 关键帧动画开始执行时候会直接跳转到第一帧的值, 而并不会将当前的值平滑过渡到第一帧, 这时候, 我们一般需要将第一帧设置为当前颜色值相同的值. 同样的最后一帧也要和最终的颜色想等, 不然会有跳跃.

// 2. 关键帧动画, 使用颜色数组改变颜色
- (void)keyFrameAnimationChangeColor {
    
    CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"backgroundColor"];
    keyAnimation.duration = 7;
    
    keyAnimation.values = @[(__bridge id)self.colorView.backgroundColor.CGColor,
                            (__bridge id)[UIColor blueColor].CGColor,
                            (__bridge id)[UIColor redColor].CGColor,
                            (__bridge id)[UIColor blackColor].CGColor,
                            (__bridge id)[UIColor purpleColor].CGColor,
                            (__bridge id)[UIColor cyanColor].CGColor,
                            (__bridge id)[UIColor yellowColor].CGColor];
    [self.colorView.layer addAnimation:keyAnimation forKey:nil];
    // 配置完动画后, 更改view颜色, 避免动画结束之后颜色突然跳跃式变化回原来颜色.
    self.colorView.layer.backgroundColor = [UIColor yellowColor].CGColor;
}

一般来说, 通过数组描述动画的运动并不直观. CAKeyFrameAnimation可以用另一种方式指定动画 CGPath . path属性可以用一种直观的方式, 使用CoreGraphics函数定义运动序列来绘制动画路径.

// 3. 关键帧动画,
- (void)pathAnimation {
    
    // 使用贝塞尔画曲线
    UIBezierPath *bPath = [UIBezierPath bezierPath];
    [bPath moveToPoint:CGPointMake(30, 250)];
    [bPath addCurveToPoint:CGPointMake(350, 250) controlPoint1:CGPointMake(100, 150) controlPoint2:CGPointMake(250, 400)];
    
    
    // 将曲线添加到shapeLayer上面
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = bPath.CGPath;
    shapeLayer.lineWidth = 0.5;
    shapeLayer.strokeColor = [UIColor redColor].CGColor;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    [self.view.layer addSublayer:shapeLayer];
    
    
    
    //创建飞机控件
    UIImageView *plane = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
    plane.center = CGPointMake(30, 150);
    plane.backgroundColor = [UIColor blueColor];
    [self.view addSubview:plane];
    
    
    // 创建path动画
    CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    keyAnimation.path = bPath.CGPath;
    keyAnimation.duration = 3.f;
// 设置图形方向跟随曲线切面方向自动做旋转动作.
    keyAnimation.rotationMode = kCAAnimationRotateAuto;
    [plane.layer addAnimation:keyAnimation forKey:nil];
}

**效果: **

1.3 虚拟属性

属性动画针对的是一个键, 也就意味着可以对子属性甚至虚拟属性做动画. 例如, 如果需要用到旋转则需要使用 transform 属性, 但是使用transform直接旋转2*M_PI, 实际上不会做任何变化, 使用起来还是非常不方便的, 这时候可以使用transform的虚拟属性. transform.rotation, 创建CABaseAnimation动画, 设置keypath是 trabsfirm.rotation, 设置byValue, 就可以直接对图层进行旋转操作, 比使用 CATransform3D方便的多.

好处如下:

  • 可以不通过关键帧动画旋转大于180°.
  • 可以用相对值进行旋转而不是绝对值.(设置byValue而不是toValue).
  • 可以不创建CATransform3D, 而使用一个简单的数值来指定角度.
  • 不会和transform.position或者transform.scale冲突(同样是使用关键路径来做独立的动画属性).

transform.rotation 属性有个奇特的问题是, 它并不存在. 因为CATransform3D并不是一个对象, 它实际上是一个结构体, 也没有符合KVC相关属性, transform.rotation实际上是一个CALayer用于处理动画变换的虚拟属性.

你不可以直接设置transform.rotation或者transform.scale, 他们不能被直接使用. 当你对他们做动画时候, CoreAnimation自动的根据通过CAValueFunction来计算的值来更新transform属性.

CAValueFunction用于我们赋值给虚拟的transform.rotation简单浮点值转换成真正的用于摆放图层的CATransfor3D矩阵值. 你可以通过CAPropertyAnimation的valueFunction属性来改变, 于是你设置的函数将会覆盖默认的函数.

CAValueFunction看起来似乎是对那些不能简单相加的属性(例如变换矩阵)做动画的非常有用的机制,但由于CAValueFunction的实现细节是私有的,所以目前不能通过继承它来自定义。你可以通过使用苹果目前已近提供的常量(目前都是和变换矩阵的虚拟属性相关,所以没太多使用场景了,因为这些属性都有了默认的实现方式)。

通过虚拟属性这一小节, 应该弄明白, CAPropertyAnimation的valueFunction的作用是什么.

1.4 动画组

CABaseAnimation的CAKeyframeAnimation仅仅作用于单独的属性, 而CAAnimationGroup可以把这些动画组合在一起. CAAnimationGroup是另一个继承自CAAnimation的子类, 它添加了一个animations数组的属性, 用来组合别的动画.

动画组用起来也是巨简单. 创建对象, 然后将其它动画放到animations数组中即可.

// 创建path动画
    CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    keyAnimation.path = bPath.CGPath;
    keyAnimation.duration = 3.f;
    keyAnimation.rotationMode = kCAAnimationRotateAuto;
 
    // 改变颜色的动画
    CAKeyframeAnimation *colorAnimation = [CAKeyframeAnimation animationWithKeyPath:@"backgroundColor"];
    colorAnimation.duration = 7;
    colorAnimation.values = @[(__bridge id)self.colorView.backgroundColor.CGColor,
                            (__bridge id)[UIColor blueColor].CGColor,
                            (__bridge id)[UIColor redColor].CGColor,
                            (__bridge id)[UIColor blackColor].CGColor,
                            (__bridge id)[UIColor purpleColor].CGColor,
                            (__bridge id)[UIColor cyanColor].CGColor,
                            (__bridge id)[UIColor yellowColor].CGColor];
    
    // animationGroup 代码.
    CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
    animationGroup.duration = 7;
    animationGroup.animations = @[keyAnimation, colorAnimation];
    
    [plane.layer addAnimation:animationGroup forKey:nil];

2. 过渡动画

对iOS应用程序来说, 属性动画只对图层的可动动画属性起作用, 但是要改版夜歌不能动画属性(比如图片), 或者从层级关系中添加或者移除图层, 属性动画将不起作用.

于是就有了过度动画的概念. 过度动画并不像属性动画那亚航平滑的在两个值之间做做动画, 而是影响到整个图层的变化. 过度动画首先需要展示之前的图层外观, 然后通过一个交换过度到新的外观. 创建过度动画我们使用的是 CATransition , 同样是另一个CAAnimation子类, 和别的子类不同, CATransform有一个type和subtype来标识变换效果. type是NSString类型值:

kCATransitionFade   //淡入淡出
kCATransitionMoveIn  //见名知意...
kCATransitionPush 
kCATransitionReveal

subtype用来控制动画移动的方向:

kCATransitionFade 
kCATransitionMoveIn 
kCATransitionPush 
kCATransitionReveal

2.1 隐式过度

当设置CALayer的contents属性的时候, CATransition的确是默认的行为. 但是对于与视图关联的图层, 或者是其他隐式动画的欣慰, 这个特性依然是被禁用的, 但是对于自己创建的图层, 这意味着对图层contents图片做的改动都会自动附上淡入淡出的动画.

2.2 对图层树的动画

CATransition并不作用于指定的图层属性, 就是说, 可以在不能准确得知改变什么的情况下对图层做动画, 六在不知道UITableView哪一行被添加或者移除的情况下就可以平滑的刷新它, 或者在不知道UIViewCVontroller内部视图层级的情况下对两个不同的实例做过度动画. 这些例子的变换不涉及到图层的属性, 而是整个图层树的改变, 我们在这种动画的过程中手动的在层级关系中添加或者移除图层.

注意: 要确保CATransition添加到的图层在过度动画发生时候不会再树状结构中被移除, 否则CATransition会和图层一起被移除, 一般来说, 只需要将动画添加到被影响图层的superlayer.

例如, 对UITabBarController的视图图层做, 切换标签时候淡入淡出效果.

// 首先设置代理, 然后实现代理方法, 在代理中添加过度动画.
// 自定义切换tabbar时候的过度动画
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
    
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionFade;
    transition.duration = 0.2f;
//    transition.subtype = kCATransitionFromLeft;
    [self.view.layer addAnimation:transition forKey:nil];
}

2.3 自定义动画

过渡动画是一种对那些不太好做平滑动画的属性的强大工具, 但是提供的动画类型太少了.
另外苹果通过UIView +transitionFromView:toView:duration:options:completion:和+transitionWithView:duration:options:animations:方法提供了Core Animation的过渡特性。但是这里的可用的过渡选项和CATransition的type属性提供的常量完全不同。UIView过渡方法中options参数可以由如下常量指定:

UIViewAnimationOptionTransitionFlipFromLeft 
UIViewAnimationOptionTransitionFlipFromRight
UIViewAnimationOptionTransitionCurlUp 
UIViewAnimationOptionTransitionCurlDown
UIViewAnimationOptionTransitionCrossDissolve 
UIViewAnimationOptionTransitionFlipFromTop 
UIViewAnimationOptionTransitionFlipFromBottom

但是在iOS平台上还是可以做自定义的过渡动画的. 原理是: 先对目标view进行截图, 将截图放在原来view的上面, 然后对截图进行一些动画操作, 实现自定义过渡动画.

截图: CALayer中有个-renderInContext:方法,可以通过把它绘制到Core Graphics的上下文中捕获当前内容的图片,然后在另外的视图中显示出来。如果我们把这个截屏视图置于原始视图之上,就可以遮住真实视图的所有变化,于是重新创建了一个简单的过渡效果。

这里有个警告:-renderInContext:捕获了图层的图片和子图层,但是不能对子图层正确地处理变换效果,而且对视频和OpenGL内容也不起作用。但是用CATransition,或者用私有的截屏方式就没有这个限制了。

3. 在动画过程中取消动画

之前提到过,你可以用-addAnimation:forKey:方法中的key参数来在添加动画之后检索一个动画,使用如下方法:

- (CAAnimation *)animationForKey:(NSString *)key;

但并不支持在动画运行过程中修改动画,所以这个方法主要用来检测动画的属性,或者判断它是否被添加到当前图层中。

为了终止一个指定的动画,你可以用如下方法把它从图层移除掉:

- (void)removeAnimationForKey:(NSString *)key;

或者移除所有动画:

- (void)removeAllAnimations;

动画一旦被移除,图层的外观就立刻更新到当前的模型图层的值。一般说来,动画在结束之后被自动移除,除非设置removedOnCompletion为NO,如果你设置动画在结束之后不被自动移除,那么当它不需要的时候你要手动移除它;否则它会一直存在于内存中,直到图层被销毁。

转载于:https://my.oschina.net/whforever/blog/742699

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值