iOS核心动画高级技巧 – 4

作者 : 开心源码 本文共15323个字,预计阅读时间需要39分钟 发布时间: 2022-05-13 共322人阅读

8. 显式动画

显式动画

假如想让事情变得顺利,只有靠自己 — 夏尔·纪尧姆

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

8.1 属性动画

属性动画

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

当升级属性的时候,我们需要设置一个新的事务,并且禁用图层行为。否则动画会发生两次,一个是由于显式的CABasicAnimation,另一次是由于隐式动画,具体实现见订单8.3。

一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:1012951431, 分享BAT,阿里面试题、面试经验,探讨技术, 大家一起交流学习成长!希望帮助开发者少走弯路。

清单8.3 动画完成之后修改图层的背景色

@implementation ViewController- (void)viewDidLoad{    [super viewDidLoad];    //create sublayer    self.colorLayer = [CALayer layer];    self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);    self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;    //add it to our view    [self.layerView.layer addSublayer:self.colorLayer];}- (IBAction)changeColor{    //create a new random color    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 *animation = [CABasicAnimation animation];    animation.keyPath = @"backgroundColor";    animation.toValue = (__bridge id)color.CGColor;    animation.delegate = self;    //apply animation to layer    [self.colorLayer addAnimation:animation forKey:nil];}- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{    //set the backgroundColor property to match animation toValue    [CATransaction begin];    [CATransaction setDisableActions:YES];    self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;    [CATransaction commit];}@end

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

考虑一下第三章的闹钟,“图层几何学”,我们通过简单地每秒升级指针的角度来实现一个钟,但假如指针动态地转向新的位置会更加真实。

我们不能通过隐式动画来实现由于这些指针都是UIView的实例,所以图层的隐式动画都被禁用了。我们可以简单地通过UIView的动画方法来实现。但假如想更好地控制动画时间,使用显式动画会更好(更多内容见第十章)。使用CABasicAnimation来做动画可能会更加复杂,由于我们需要在-animationDidStop:finished:中检测指针状态(用于设置结束的位置)。

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

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

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

这意味着你可以对动画用任意类型打标签。在这里,我们给UIView类型的指针增加的动画,所以可以简单地判断动画究竟属于哪个视图,而后在委托方法中用这个信息正确地升级钟的指针(清单8.4)。

清单8.4 使用KVC对动画打标签

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIImageView *hourHand;@property (nonatomic, weak) IBOutlet UIImageView *minuteHand;@property (nonatomic, weak) IBOutlet UIImageView *secondHand;@property (nonatomic, weak) NSTimer *timer;@end@implementation ViewController- (void)viewDidLoad{    [super viewDidLoad];    //adjust anchor points    self.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);    self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);    self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);    //start timer    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];    //set initial hand positions    [self updateHandsAnimated:NO];}- (void)tick{    [self updateHandsAnimated:YES];}- (void)updateHandsAnimated:(BOOL)animated{    //convert time to hours, minutes and seconds    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];    NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;    NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];    CGFloat hourAngle = (components.hour / 12.0) * M_PI * 2.0;    //calculate hour hand angle //calculate minute hand angle    CGFloat minuteAngle = (components.minute / 60.0) * M_PI * 2.0;    //calculate second hand angle    CGFloat secondAngle = (components.second / 60.0) * M_PI * 2.0;    //rotate hands    [self setAngle:hourAngle forHand:self.hourHand animated:animated];    [self setAngle:minuteAngle forHand:self.minuteHand animated:animated];    [self setAngle:secondAngle forHand:self.secondHand animated:animated];}- (void)setAngle:(CGFloat)angle forHand:(UIView *)handView animated:(BOOL)animated{    //generate transform    CATransform3D transform = CATransform3DMakeRotation(angle, 0, 0, 1);    if (animated) {        //create transform animation        CABasicAnimation *animation = [CABasicAnimation animation];        [self updateHandsAnimated:NO];        animation.keyPath = @"transform";        animation.toValue = [NSValue valueWithCATransform3D:transform];        animation.duration = 0.5;        animation.delegate = self;        [animation setValue:handView forKey:@"handView"];        [handView.layer addAnimation:animation forKey:nil];    } else {        //set transform directly        handView.layer.transform = transform;    }}- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{    //set final position for hand view    UIView *handView = [anim valueForKey:@"handView"];    handView.layer.transform = [anim.toValue CATransform3DValue];}

我们成功的识别出每个图层中止动画的时间,而后升级它的变换到一个新值,很好。

不幸的是,即便做了这些,还是有个问题,清单8.4在模拟器上运行的很好,但当真正跑在iOS设施上时,我们发现在-animationDidStop:finished:委托方法调用之前,指针会迅速返回到原始值,这个清单8.3图层颜色发生的情况一样。

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

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

关键帧动画

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

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

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

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

清单8.5 使用CAKeyframeAnimation应用一系列颜色的变化

- (IBAction)changeColor{    //create a keyframe animation    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];    animation.keyPath = @"backgroundColor";    animation.duration = 2.0;    animation.values = @[                         (__bridge id)[UIColor blueColor].CGColor,                         (__bridge id)[UIColor redColor].CGColor,                         (__bridge id)[UIColor greenColor].CGColor,                         (__bridge id)[UIColor blueColor].CGColor ];    //apply animation to layer    [self.colorLayer addAnimation:animation forKey:nil];}

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

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

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

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

我们来用一个宇宙飞船沿着一个简单曲线的实例演示一下。为了创立路径,我们需要使用一个三次贝塞尔曲线,它是一种使用开始点,结束点和另外两个控制点来定义形状的曲线,可以通过使用一个基于C的Core Graphics绘图指令来创立,不过用UIKit提供的UIBezierPath类会更简单。

我们这次用CAShapeLayer来在屏幕上绘制曲线,虽然对动画来说并不是必需的,但这会让我们的动画更加形象。绘制完CGPath之后,我们用它来创立一个CAKeyframeAnimation,而后用它来应用到我们的宇宙飞船。代码见清单8.6,结果见图8.1。

清单8.6 沿着一个贝塞尔曲线对图层做动画

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView;@end@implementation ViewController- (void)viewDidLoad{    [super viewDidLoad];    //create a path    UIBezierPath *bezierPath = [[UIBezierPath alloc] init];    [bezierPath moveToPoint:CGPointMake(0, 150)];    [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];    //draw the path using a CAShapeLayer    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];    //add the ship    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];    //create the keyframe animation    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];    animation.keyPath = @"position";    animation.duration = 4.0;    animation.path = bezierPath.CGPath;    [shipLayer addAnimation:animation forKey:nil];}@end

这么做是可行的,但看起来更由于是运气而不是设计的起因,假如我们把旋转的值从M_PI(180度)调整到2 * M_PI(360度),而后运行程序,会发现这时候飞船完全不动了。这是由于这里的矩阵做了一次360度的旋转,和做了0度是一样的,所以最后的值根本没变。

现在继续使用M_PI,但这次用byValue而不是toValue。也许你会认为这和设置toValue结果一样,由于0 + 90度 == 90度,但实际上飞船的图片变大了,并没有做任何旋转,这是由于变换矩阵不能像角度值那样叠加。

那么假如需要独立于角度之外单独对平移或者者缩放做动画呢?因为都需要我们来修改transform属性,实时地重新计算每个时间点的每个变换效果,而后根据这些创立一个复杂的关键帧动画,这一切都是为了对图层的一个独立做一个简单的动画。

幸运的是,有一个更好的处理方案:为了旋转图层,我们可以对transform.rotation关键路径应用动画,而不是transform本身(清单8.9)。

清单8.9 对虚拟的transform.rotation属性做动画

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView;@end@implementation ViewController- (void)viewDidLoad{    [super viewDidLoad];    //add the ship    CALayer *shipLayer = [CALayer layer];    shipLayer.frame = CGRectMake(0, 0, 128, 128);    shipLayer.position = CGPointMake(150, 150);    shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;    [self.containerView.layer addSublayer:shipLayer];    //animate the ship rotation    CABasicAnimation *animation = [CABasicAnimation animation];    animation.keyPath = @"transform.rotation";    animation.duration = 2.0;    animation.byValue = @(M_PI * 2);    [shipLayer addAnimation:animation forKey:nil];}@end

结果运行的特别好,用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用于把我们赋给虚拟的transform.rotation简单浮点值转换成真正的用于摆放图层的CATransform3D矩阵值。你可以通过设置CAPropertyAnimationvalueFunction属性来改变,于是你设置的函数将会覆盖默认的函数。

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

8.2 动画组

动画组

CABasicAnimationCAKeyframeAnimation仅仅作用于单独的属性,而CAAnimationGroup可以把这些动画组合在一起。CAAnimationGroup是另一个继承于CAAnimation的子类,它增加了一个animations数组的属性,用来组合别的动画。我们把清单8.6那种关键帧动画和调整图层背景色的基础动画组合起来(清单8.10),结果如图8.3所示。

清单8.10 组合关键帧动画和基础动画

- (void)viewDidLoad{    [super viewDidLoad];    //create a path    UIBezierPath *bezierPath = [[UIBezierPath alloc] init];    [bezierPath moveToPoint:CGPointMake(0, 150)];    [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];    //draw the path using a CAShapeLayer    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];    //add a colored layer    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];    //create the position animation    CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animation];    animation1.keyPath = @"position";    animation1.path = bezierPath.CGPath;    animation1.rotationMode = kCAAnimationRotateAuto;    //create the color animation    CABasicAnimation *animation2 = [CABasicAnimation animation];    animation2.keyPath = @"backgroundColor";    animation2.toValue = (__bridge id)[UIColor redColor].CGColor;    //create group animation    CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];    groupAnimation.animations = @[animation1, animation2];     groupAnimation.duration = 4.0;    //add the animation to the color layer    [colorLayer addAnimation:groupAnimation forKey:nil];}

8.3 过渡

过渡

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

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

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

kCATransitionFade kCATransitionMoveIn kCATransitionPush kCATransitionReveal

到目前为止你只能使用上述四种类型,但你可以通过少量别的方法来自己设置过渡效果,后续会详细详情。

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

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

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

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

kCATransitionFromRight kCATransitionFromLeft kCATransitionFromTop kCATransitionFromBottom

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

清单8.11 使用CATransition来对UIImageView做动画

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIImageView *imageView;@property (nonatomic, copy) NSArray *images;@end@implementation ViewController- (void)viewDidLoad{    [super viewDidLoad];    //set up images    self.images = @[[UIImage imageNamed:@"Anchor.png"],                    [UIImage imageNamed:@"Cone.png"],                    [UIImage imageNamed:@"Igloo.png"],                    [UIImage imageNamed:@"Spaceship.png"]];}- (IBAction)switchImage{    //set up crossfade transition    CATransition *transition = [CATransition animation];    transition.type = kCATransitionFade;    //apply transition to imageview backing layer    [self.imageView.layer addAnimation:transition forKey:nil];    //cycle to next image    UIImage *currentImage = self.imageView.image;    NSUInteger index = [self.images indexOfObject:currentImage];    index = (index + 1) % [self.images count];    self.imageView.image = self.images[index];}@end

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

8.4 在动画过程中取消动画

在动画过程中取消动画

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

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

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

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

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

或者者移除所有动画:

- (void)removeAllAnimations;

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

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

清单8.15是升级后的示例代码,图8.6显示了结果。

清单8.15 开始和中止一个动画

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView;@property (nonatomic, strong) CALayer *shipLayer;@end@implementation ViewController- (void)viewDidLoad{    [super viewDidLoad];    //add the ship    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: @"Ship.png"].CGImage;    [self.containerView.layer addSublayer:self.shipLayer];}- (IBAction)start{    //animate the ship rotation    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)stop{    [self.shipLayer removeAnimationForKey:@"rotateAnimation"];}- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{    //log that the animation stopped    NSLog(@"The animation stopped (finished: %@)", flag? @"YES": @"NO");}@end

8.5 总结

总结

另外,假如你想一起进阶,不妨增加一下交流群1012951431,选择加入一起交流,一起学习。期待你的加入!

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

在第九章中,我们继续学习CAMediaTiming协议,来看一看Core Animation是怎么解决逝去的时间。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » iOS核心动画高级技巧 – 4

发表回复