iOS核心动画高级技巧--(八)显式动画

上一章介绍了隐式动画的概念。隐式动画是在iOS平台创建动态用户界面的一种直接方式,也是UIKit动画机制的基础,不过它并不能涵盖所有的动画类型。在这一章 中,我们将要研究一下显式动画,它能够对一些属性做指定的自定义动画,或者创建非线性动画,比如沿着任意一条曲线移动。

属性动画

CAAnimationDelegate在任何头文件中都找不到,但是可以在CAAnimation头文件或者苹果开发者文档中找到相关函数。在这个例子中,我们用- animationDidStop: finished: 方法在动画结束之后来更新图层backgroundColor的。

当更新属性的时候,我们需要设置一个新的事务,并且禁用图层行为。否则动画会发生两次,一个是因为显式的 CABasicAnimation ,另一次是因为隐式动画,具体实现代码如下。

- (void)viewDidLoad {
    [super viewDidLoad];
    CALayer *colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(0, 0, 100, 100);
    colorLayer.position = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 0.5);
    colorLayer.backgroundColor = [UIColor redColor].CGColor;
    self.colorLayer = colorLayer;
    [self.view.layer addSublayer:colorLayer];
}

- (IBAction)changeColor:(id)sender {
    
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
    //create a basic animation
    CABasicAnimation *baseAnimation = [CABasicAnimation animation];
    baseAnimation.keyPath = @"backgroundColor";
    baseAnimation.toValue = (__bridge id)color.CGColor;
    baseAnimation.delegate = self;
    [self.colorLayer addAnimation:baseAnimation forKey:nil];
    
}

- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{
    [CATransaction begin];
    [CATransaction setDisableActions:true];
    [CATransaction setDisableActions:0.25];
    self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
    [CATransaction commit];
}

CAAnimation而言,使用委托模式而不是一个完成块会带来一个问题,就是当 你有多个动画的时候,无法在在回调方法中区分。在一个视图控制器中创建动画的 时候,通常会用控制器本身作为一个委托(如上面所示),但是所有的动画都会调用同一个回调方法,所以你就需要判断到底是那个图层的调用。

动画本身会作为一个参数传入委托的方法,也许你会认为可以控制器中把动画存储
为一个属性,然后在回调用比较,但实际上并不起作用,因为委托传入的动画参数
是原始值的一个深拷贝,从而不是同一个值。

当使用-animation:forKey:把动画添加到图层, 这里有一个到目前为止我们都设置为nilkey参数。这里的键是-animationForKey: 方法找到对应动 画的唯一标识符,而当前动画的所有键都可以用animationKeys 获取。如果我们 对每个动画都关联一个唯一的键,就可以对每个图层循环所有键,然后调用 - animationForKey:来比对结果。尽管这不是一个优雅的实现。

幸运的是,还有一种更加简单的方法。像所有的 NSObject子类一 样,CAAnimation实现了KVC(键-值-编码)协议,于是你可以用 - setValue:forKey:- valueForKey:方法来存取属性。但是CAAnimation有 一个不同的性能:它更像一个NSDictionary,可以让你随意设置键值对,即使和你使用的动画类所声明的属性并不匹配。

这意味着你可以对动画用任意类型打标签。在这里,我们给UIView类型的指针添 加的动画,所以可以简单地判断动画到底属于哪个视图,然后在委托方法中用这个 信息正确地更新钟的指针。

在模拟器上运行的很好,但当真 正跑在iOS设备上时,我们发现在 -animationDidStop:finished: 委托方法调用 之前,指针会迅速返回到原始值。

问题在于回调方法在动画完成之前已经被调用了,但不能保证这发生在属性动画返
回初始状态之前。这同时也很好地说明了为什么要在真实的设备上测试动画代码,
而不仅仅是模拟器。

我们可以用一个 fillMode 属性来解决这个问题,下一章会详细说明,这里知道在 动画之前设置它比在动画结束之后更新属性更加方便。

关键帧动画

CABasicAnimation揭示了大多数隐式动画背后依赖的机制,这的确很有趣,但是显示地给图层添加CABasicAnimation相较于隐式动画而言,只能说费力不讨好。

CAKeyframeAnimation是另一种UIKit没有暴露出来但功能强大的类。和CABasicAnimation 类似, CAKeyframeAnimation 同样是CAPropertyAnimation的一个子类,它依然作用于单一的一个属性,但是和CABasicAnimation 不一样的是,它不限制于设置一个起始和结束的值,而是可以根据一连串随意的值来做动画。

关键帧起源于传动动画,意思是指主导的动画在显著改变发生时重绘当前帧(也就 是关键帧),每帧之间剩下的绘制(可以通过关键帧推算出)将由熟练的艺术家来 完成。 CAKeyframeAnimation 也是同样的道理:你提供了显著的帧,然后Core Animation在每帧之间进行插入。

我们可以用之前使用颜色图层的例子来演示,设置一个颜色的数组,然后通过关键 帧动画播放出来

- (void)viewDidLoad {
    [super viewDidLoad];
    CALayer *colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(0, 0, 100, 100);
    colorLayer.backgroundColor = [UIColor redColor].CGColor;
    colorLayer.position = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 0.5);
    self.colorLayer = colorLayer;
    [self.view.layer addSublayer:colorLayer];
}
- (IBAction)changeColor:(id)sender {
    CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];
    keyAnimation.keyPath = @"backgroundColor";
    keyAnimation.duration = 2.0;
    keyAnimation.values = @[(__bridge id)[UIColor blueColor].CGColor,
                            (__bridge id)[UIColor redColor].CGColor,
                            (__bridge id)[UIColor greenColor].CGColor,
                            (__bridge id)[UIColor blueColor].CGColor
                            ];
    [self.colorLayer addAnimation:keyAnimation forKey:nil];
}

注意到序列中开始和结束的颜色都是蓝色,这是因为CAKeyframeAnimation并不 能自动把当前值作为第一帧(就像CABasicAnimation 那样把fromValue设为 nil )。动画会在开始的时候突然跳转到第一帧的值,然后在动画结束的时候 突然恢复到原始的值。所以为了动画的平滑特性,我们需要开始和结束的关键帧来 匹配当前属性的值。

当然可以创建一个结束和开始值不同的动画,那样的话就需要在动画启动之前手动更新属性和最后一帧的值保持一致,就和之前讨论的一样。

我们用duration属性把动画时间从默认的0.25秒增加到2秒,以便于动画做的不 那么快。运行它,你会发现动画通过颜色不断循环,但效果看起来有些奇怪。原因 在于动画以一个恒定的步调在运行。当在每个动画之间过渡的时候并没有减速,这 就产生了一个略微奇怪的效果,为了让动画看起来更自然,我们需要调整一下缓 冲,第十章将会详细说明。

提供一个数组的值就可以按照颜色变化做动画,但一般来说用数组来描述动画运动并不直观。
CAKeyframeAnimation有另一种方式去指定动画,就是使用CGPathpath属性可以用一种直观的方式,使用Core Graphics函数定义运动序列来绘制动画。

我们来用一个宇宙飞船沿着一个简单曲线的实例演示一下。为了创建路径,我们需要使用一个三次贝塞尔曲线,它是一种使用开始点,结束点和另外两个控制点来定义形状的曲线,可以通过使用一个基于CCore Graphics绘图指令来创建,不过用UIKit提供的 UIBezierPath类会更简单。
我们这次用CAShapeLayer来在屏幕上绘制曲线,尽管对动画来说并不是必须 的,但这会让我们的动画更加形象。绘制完 CGPath之后,我们用它来创建一 个CAKeyframeAnimation,然后用它来应用到我们的宇宙飞船。

运行示例,你会发现飞船的动画有些不太真实,这是因为当它运动的时候永远指向 右边,而不是指向曲线切线的方向。你可以调整它的 affineTransform 来对运动 方向做动画,但很可能和其它的动画冲突。

苹果预见到了这点,并且给CAKeyFrameAnimation添加了一个rotationMode 的属性。设置它为常量KCAAnimationRotateAuto,图层将会根据曲线的切线自动旋转。

- (void)viewDidLoad {
    [super viewDidLoad];
    UIBezierPath *bezierPath = [[UIBezierPath alloc]init];
    [bezierPath moveToPoint:CGPointMake(0, 150)];
    [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(200, 200) controlPoint2:CGPointMake(150, 50)];
    CAShapeLayer *pathLayer = [CAShapeLayer layer];
    pathLayer.path = bezierPath.CGPath;
    pathLayer.fillColor = [UIColor clearColor].CGColor;
    pathLayer.strokeColor = [UIColor redColor].CGColor;
    pathLayer.lineWidth = 3.0f;
    [self.containerView.layer addSublayer:pathLayer];
    
    CALayer *shipLayer = [CALayer layer];
    shipLayer.frame = CGRectMake(0, 0, 64, 64);
    shipLayer.position = CGPointMake(0, 150);
    shipLayer.contents = (__bridge id)[UIImage imageNamed:@"ship.png"].CGImage;
    [self.containerView.layer addSublayer:shipLayer];

    
    CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];
    keyAnimation.keyPath = @"position";
    keyAnimation.duration = 4.0;
    keyAnimation.path = bezierPath.CGPath;
    keyAnimation.rotationMode = kCAAnimationRotateAuto;
    [shipLayer addAnimation:keyAnimation forKey:nil];
}
虚拟属性

之前提到过属性动画实际上是针对于关键路径而不是一个键,这就意味着可以对子属性甚至是虚拟属性做动画。但是虚拟属性到底是什么呢?
考虑一个旋转的动画:如果想要对一个物体做旋转的动画,那就需要作用
transform 属性,因为 CALayer没有显式提供角度或者方向之类的属性,代 码如下所示

transform.rotation 而不是 transform 做动画的好处 如下:

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

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

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

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

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

动画组

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

- (void)viewDidLoad {
    [super viewDidLoad];
    UIBezierPath *bezierPath = [[UIBezierPath alloc]init];
    [bezierPath moveToPoint:CGPointMake(0, 150)];
    [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(200, 200) controlPoint2:CGPointMake(150, 150)];
    
    CAShapeLayer *pathLayer = [CAShapeLayer layer];
    pathLayer.path = bezierPath.CGPath;
    pathLayer.fillColor = [UIColor clearColor].CGColor;
    pathLayer.strokeColor = [UIColor redColor].CGColor;
    pathLayer.lineWidth = 3.0f;
    [self.containerView.layer addSublayer:pathLayer];
    
    CALayer *colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(0, 0, 64, 64);
    colorLayer.position = CGPointMake(0, 150);
    colorLayer.backgroundColor = [UIColor greenColor].CGColor;
    [self.containerView.layer addSublayer:colorLayer];
    
    
    
    CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];
    keyAnimation.keyPath = @"position";
    keyAnimation.path = bezierPath.CGPath;
    keyAnimation.rotationMode = kCAAnimationRotateAuto;
    
    CABasicAnimation *baseAnimation = [CABasicAnimation animation];
    baseAnimation.keyPath = @"backgroundColor";
    baseAnimation.toValue = (__bridge id)[UIColor redColor].CGColor;
    
    CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
    groupAnimation.animations = @[keyAnimation, baseAnimation];
    groupAnimation.duration = 4.0;
    groupAnimation.autoreverses = true;
    [colorLayer addAnimation:groupAnimation forKey:nil];
}
过渡

有时候对于iOS应用程序来说,希望能通过属性动画来对比较难做动画的布局进行 一些改变。比如交换一段文本和图片,或者用一段网格视图来替换,等等。属性动画只对图层的可动画属性起作用,所以如果要改变一个不能动画的属性(比如图 片),或者从层级关系中添加或者移除图层,属性动画将不起作用。

于是就有了过渡的概念。过渡并不像属性动画那样平滑地在两个值之间做动画,而是影响到整个图层的变化。过渡动画首先展示之前的图层外观,然后通过一个交换过渡到新的外观。

为了创建一个过渡动画,我们将使用CATransition,同样是另一个CAAnimation的子类,和别的子类不同,CATransition 有一 个typesubtype来标识变换效果。type属性是一个NSString类型,可以被设置成如下类型:

kCATransitionFade
kCATransitionMoveIn
kCATransitionPush
kCATransitionReveal

到目前为止你只能使用上述四种类型,但你可以通过一些别的方法来自定义过渡效果,后续会详细介绍。

默认的过渡类型是 kCATransitionFade,当你在改变图层属性之后,就创建了一 个平滑的淡入淡出效果。

我们在第七章的例子中就已经用到过 kCATransitionPush,它创建了一个新的图 层,从边缘的一侧滑动进来,把旧图层从另一侧推出去的效果。

kCATransitionMoveInkCATransitionRevealkCATransitionPush类 似,都实现了一个定向滑动的动画,但是有一些细微的不同,kCATransitionMoveIn从顶部滑动进入,但不像推送动画那样把老土层推走,然而kCATransitionReveal把原始的图层滑动出去来显示新的外观,而不是把新的图层滑动进入。

后面三种过渡类型都有一个默认的动画方向,它们都从左侧滑入,但是你可以通 过 subtype来控制它们的方向,提供了如下四种类型:

kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom

一个简单的用CATransition来对非动画属性做动画的例子,这里我们对UIImageimage属性做修改,但是隐式动画或者CAPropertyAnimation都不能对它做动画,因为Core Animation不知道如何在插图图片。通过对图层应用一个淡入淡出的过渡,我们可以忽略它的内容来做平滑动画,我们来尝试修改过渡的 type常量来观察其它效果。

使用 CATransition 来对UIImageView 做动画

- (void)viewDidLoad {
    [super viewDidLoad];
    self.images = @[
                    [UIImage imageNamed:@"Anchor.png"],
                    [UIImage imageNamed:@"Cone.png"],
                    [UIImage imageNamed:@"Igloo.png"],
                    [UIImage imageNamed:@"Spaceship.png"]
                    ];
}
- (IBAction)changeImage:(id)sender {
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionFade;
    [self.imageView.layer addAnimation:transition forKey:nil];
    
    UIImage *currentImage = self.imageView.image;
    NSUInteger index = [self.images indexOfObject:currentImage];
    index = (index + 1) % [self.images count];
    self.imageView.image = self.images[index];
}

效果如下:

效果.gif

你可以从代码中看出,过渡动画和之前的属性动画或者动画组添加到图层上的方式一致,都是通过- addAnimation: forKey:方法。但是和属性动画不同的是,对指定的图层一次只能使用一次 CATransition,因此,无论你对动画的键设置什么值,过渡动画都会对它的键设成“transition”,也就是常量 kCATransition 。

隐式过渡

CATransision 可以对图层任何变化平滑过渡的事实使得它成为那些不好做动画 的属性图层行为的理想候选。苹果当然意识到了这点,并且当设置了 CALayercontent属性的时候, CATransition 的确是默认的行为。但是对于视图关联的图层,或者是其他隐式动画的行为,这个特性依然是被禁用的,但 是对于你自己创建的图层,这意味着对图层contents图片做的改动都会自动附上 淡入淡出的动画。

我们在第七章使用CATransition作为一个图层行为来改变图层的背景色,当然backgroundColor属性可以通过正常的CAPropertyAnimation来实现,但这不是说不可以用CATransition来实行。

对图层树的动画

CATransition并不作用于指定的图层属性,这就是说你可以在即使不能准确得 知改变了什么的情况下对图层做动画,例如,在不知道 UITableView哪一行被添加或者删除的情况下,直接就可以平滑地刷新它,或者在不知道 UIViewController内部的视图层级的情况下对两个不同的实例做过渡动画。

这些例子和我们之前所讨论的情况完全不同,因为它们不仅涉及到图层的属性,而且是整个图层树的改变--我们在这种动画的过程中手动在层级关系中添加或者移除 图层。

这里用到了一个小诡计,要确保CATransition添加到的图层在过渡动画发生时不会在树状结构中被移除,否则CATransition将会和图层一起被移除. 一般来说,你只需要将动画添加到被影响图层的superlayer.

我们展示了如何在 UITabBarController切换标签的时候添加淡入淡出的动画。这里我们建立了默认的标签应用程序模板,然后用UITabBarControllerDelegate- tabBarController: deisSelectViewController:方法来应用过渡动画。我们把动画添加到UITabBarController的视图图层上,于是在标签被替换的时候动画不会被移除。

@interface AppDelegate ()
@property (nonatomic, strong)UITabBarController *tabBarController;
@end

@implementation AppDelegate 


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    FirstViewController *first = [[FirstViewController alloc]init];
    SecondViewController *second = [[SecondViewController alloc]init];
    self.tabBarController = [[UITabBarController alloc]init];
    self.tabBarController.viewControllers = @[first, second];
    self.tabBarController.delegate = self;
    self.window.rootViewController = self.tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
    
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionFade;
    [self.tabBarController.view.layer addAnimation:transition forKey:nil];
    
}
自定义动画

我们证实了过渡是一种对那些不太好做平滑动画属性的强大工具,但是CATransition的提供的动画类型太少了。

更奇怪的是苹果通过UIView +transitionFromView:toView:duration:options:completion:+ transitiononWithView:duration:options:animations:方法提供了Core Animation的过渡特性。但是这里的可用的过渡选项和CATransitiontype 属性提供的常量完全不同。UIView 过渡方法中options参数可以由如下常量指定:

 UIViewAnimationOptionTransitionFlipFromLeft
UIViewAnimationOptionTransitionFlipFromRight 
UIViewAnimationOptionTransitionCurlUp 
UIViewAnimationOptionTransitionCurlDown 
UIViewAnimationOptionTransitionCrossDissolve 
UIViewAnimationOptionTransitionFlipFromTop 
UIViewAnimationOptionTransitionFlipFromBottom

除了UIViewAnimationOptionTransitionCrossDissolve 之外,剩下的值和CATransition 类型完全没关系。

使用UIKit提供的方法来做过渡动画

- (void)viewDidLoad {
    [super viewDidLoad];
    self.images = @[
                    [UIImage imageNamed:@"Anchor.png"],
                    [UIImage imageNamed:@"Cone.png"],
                    [UIImage imageNamed:@"Igloo.png"],
                    [UIImage imageNamed:@"Spaceship.png"]
                    ];
}
- (IBAction)changeImage:(id)sender {
    [UIView transitionWithView:self.imageView duration:1.0 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{
        UIImage *currentImage = self.imageView.image;
        NSUInteger index = [self.images indexOfObject:currentImage];
        index = (index + 1) % [self.images count];
        self.imageView.image = self.images[index];
    } completion:^(BOOL finished) {
        
    }];
    }
使用UIKit提供的方法来做过渡动画.gif

文档暗示过在iOS5(带来了Core Image框架)之后,可以通过 CATransitionfilter 属性,用 CIFilter来创建其它的过渡效果。然是 直到iOS6都做不到这点。试图对CATransition 使用Core Image的滤镜完全没效果(但是在Mac OS中是可行的,也许文档是想表达这个意思)。

因此,根据要实现的效果,你只用关心是用CATransition 还是用UIView 的过渡方法就可以了。希望下个版本的iOS系统可以通过CATransition很好的支持Core Image的过渡滤镜效果(或许甚至会有新的方法)。

但这并不意味着在iOS上就不能实现自定义的过渡效果了。这只是意味着你需要做 一些额外的工作。就像之前提到的那样,过渡动画做基础的原则就是对原始的图层 外观截图,然后添加一段动画,平滑过渡到图层改变之后那个截图的效果。如果我 们知道如何对图层截图,我们就可以使用属性动画来代替 CATransition 或者是 UIKit的过渡方法来实现动画。

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

Demo 我们对当前视图状态截图,然后在我们改变原始 视图的背景色的时候对截图快速转动并且淡出,为了让事情更简单,我们用UIView - animateWithDuration: completion: 方法 来实现。虽然用 CABasicAnmation可以达到同样的效果,但是那样的话我们就 需要对图层的变换和不透明属性创建单独的动画,然后当动画结束的时候在 CAAnimationDelegate 中把coverView从屏幕中移除。

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

在动画过程中取消动画

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

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

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

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

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

或者移除所有动画:

- (void)removeAllAnimations;

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

我们来扩展之前旋转飞船的示例,这里添加一个按钮来停止或者启动动画。这一次 我们用一个非 nil的值作为动画的键,以便之后可以移除它。 - animationDidStop:finished:方法中的flag参数表明了动画是自然结束还是 被打断,我们可以在控制台打印出来。如果你用停止按钮来终止动画,它会打印NO ,如果允许它完成,它会打印 YES

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) CALayer *shipLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.shipLayer = [CALayer layer];
    self.shipLayer.frame = CGRectMake(0, 0, 128, 128);
    self.shipLayer.position = CGPointMake(150, 150);
    self.shipLayer.contents = (__bridge id)[UIImage imageNamed:@"Igloo.png"].CGImage;
    [self.containerView.layer addSublayer:self.shipLayer];
}


- (IBAction)startAnimation:(id)sender {
    
    
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation";
    animation.duration = 2.0;
    animation.byValue = @(M_PI * 2);
    animation.delegate = self;
    [self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];
}

- (IBAction)stopAnimation:(id)sender {
    [self.shipLayer removeAnimationForKey:@"rotateAnimation"];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    NSLog(@"The animation stopped(finished:%@)",flag ? @"YES" : @"NO");
}

总结

这一章中,我们涉及了属性动画(你可以对单独的图层属性动画有更加具体的控制),动画组(把多个属性动画组合成一个独立单元)以及过度(影响整个图层,可以用来对图层的任何内容做任何类型的动画,包括子图层的添加和移除)。

iOS核心动画高级技巧--目录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容