1 有关核心动画
1.1 概览
1.1.1 Core Animation 管理应用的内容
核心是 layer objects,
1.1.2 更改 layer 触发动画
Like views, layer objects have a bounds rectangle, a position onscreen, an opacity, a transform, and many other visually-oriented properties that can be modified. 更改这些属性的时候有隐式动画,可以明确指定动画行为。
1.1.3 layer可以组织成层级结构
1.1.4 action 让你更改图层的默认行为
隐式layer动画是使用 action objects。可以创建自己的 action objects ,实现自定义动画,或者也能实现其他类型的行为。
1.2 如果使用这个文档
1.3 预备工作
先熟悉 View Programming Guide for iOS.
核心动画基础
layer 提供绘图和动画的基础
Layer objects 对核心。layer只是管理内容和可视化属性,所以可以将layer想成 model 对象
基于 layer 的绘图模型
对于 view 中调用 drawRect:
方法,非常的损耗,因为这在线程上使用CPU。核心动画通过在硬件上缓存位图来达到同样或相似的效果。
基于 layer 的动画
layer 对象定义自己的几何
layer 也有像view 有的一些属性,frame bounds,并且也有类似 anchor poing(锚点)这种 view 没有的属性。
layer 使用两种类型的坐标系
point-based coordinate systems 和 unit coordinate systems
通常使用 point-based 坐标系指定 layer 的大小和位置,
unit coordinate system (单位坐标系)是用来表示当 layer 的大小变化的时候哪些属性可能改变。可以认为单位坐标系指定所有可能变化值的百分比。
锚点影响几何操作
layer 可以在三维空间中操作
Core Animation Functions Reference.
layer 树反映了动画状态的不同方面
有3个 layer 对象集合:
- model layer tree 中的对象是模型对象,存储动画的目标值
- presentation tree包含动画运行中的值。你应该从来都不要去修改这个 tree 中的对象。使用这些对象读取当前动画的值,或者使用这些值开始新的动画。
- render tree 中的对象执行实际的动画,这是核心动画私有的
layer 和 view 之间的关系
layer 不是 view 的替代品,不能单独靠layer 对象创建一个可视化的界面。layer 提供了 view 的基础。layer 使得view内容的绘制和动画更加有效,并且动画有较高的帧率,就是更流畅。layer不能处理事件,不能绘制内容,不能参与到响应链等等。所以应用必须有一个或多个 view 来处理这种交互
设置 layer 对象
激活核心动画支持
更改与视图相关的 layer 对象
改变 UIView 使用的 layer class
重写 layerClass 类方法 返回想要替换的 类对象。
指定 view 的 layer 类
+ (Class) layerClass {
return [CAMetalLayer class];
}
Changing the Layer Class Used By NSView
不同的 layer 类提供专属行为
CALayer子类及其用法
Class | Usage |
---|---|
给 layer 提供内容
3种方式给layer的位图提供数据:
- 直接给layer对象的 contents 属性赋值一个 image 对象。这是最好的方法
- 给layer赋值一个代理对象,并让代理绘制 layer 的内容。(适合内容周期性的变化,比如随着 view变化)
- 定义一个 layer 子类,并重写一个它的绘制方法
给 layer 内容设置图像
将image 赋值给 layer必须是 CGImageRef 类型。
使用代理给layer提供内容
代理实现 displayLayer:
方法
- (void)displayLayer:(CALayer *)theLayer {
// Check the value of some state property
if (self.displayYesImage) {
// Display the Yes image
theLayer.contents = [someHelperObject loadStateYesImage];
}
else {
// Display the No image
theLayer.contents = [someHelperObject loadStateNoImage];
}
}
代理实现 drawLayer:inContext:
方法
- (void)drawLayer:(CALayer *)theLayer inContext:(CGContextRef)theContext {
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
CGPathAddCurveToPoint(thePath,
NULL,
15.f,250.0f,
295.0f,250.0f,
295.0f,15.0f);
CGContextBeginPath(theContext);
CGContextAddPath(theContext, thePath);
CGContextSetLineWidth(theContext, 5);
CGContextStrokePath(theContext);
// Release the path
CFRelease(thePath);
}
通过子类给 layer 提供内容
- 重写 layer 的 display 方法,并使用它直接设置contents属性
- 重写 layer 的 drawInContext: 方法,并使用它来绘制图像内容
调整提供的内容
layer 的 contentsGravity 属性模式的设置是 kCAGravityResize 常量。
高分辨率图像
调整图层的视觉风格和外观
layer 有自己的背景和边界
layer 支持半径圆角
layer 支持内建阴影
OS X 的view 添加过滤效果
iOS 不能给 layer 对象添加过滤器
对于 OS X 性能影响的 layer 重回策略
给 layer 添加自定义属性
Animating Layer Content
修改 layer 属性,达到简单动画效果
精确的动画,需要创建一个 CABasicAnimation 对象,使用这个对象来配置参数。
CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 1.0;
[theLayer addAnimation:fadeAnim forKey:@"opacity"];
// Change the actual data value in the layer to the final value.
theLayer.opacity = 0.0;
使用关键帧动画来改变 layer 的属性
// create a CGPath that implements two arcs (a bounce)
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,74.0,74.0);
CGPathAddCurveToPoint(thePath,NULL,74.0,500.0,
320.0,500.0,
320.0,74.0);
CGPathAddCurveToPoint(thePath,NULL,320.0,500.0,
566.0,500.0,
566.0,74.0);
CAKeyframeAnimation * theAnimation;
// Create the animation object, specifying the position property as the key path.
theAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
theAnimation.path=thePath;
theAnimation.duration=5.0;
// Add the animation to the layer.
[theLayer addAnimation:theAnimation forKey:@"position"];
指定 keyframe 的值
在动画运行的时候,停止动画
removeAnimationForKey:
removeAllAnimations
只能移除显示动画,不能移除隐式动画。
多个动画一起变化
// Animation 1
CAKeyframeAnimation* widthAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderWidth"];
NSArray* widthValues = [NSArray arrayWithObjects:@1.0, @10.0, @5.0, @30.0, @0.5, @15.0, @2.0, @50.0, @0.0, nil];
widthAnim.values = widthValues;
widthAnim.calculationMode = kCAAnimationPaced;
// Animation 2
CAKeyframeAnimation* colorAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderColor"];
NSArray* colorValues = [NSArray arrayWithObjects:(id)[UIColor greenColor].CGColor,
(id)[UIColor redColor].CGColor, (id)[UIColor blueColor].CGColor, nil];
colorAnim.values = colorValues;
colorAnim.calculationMode = kCAAnimationPaced;
// Animation group
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:colorAnim, widthAnim, nil];
group.duration = 5.0;
[myLayer addAnimation:group forKey:@"BorderChanges"];
检测动画的结束
There are two different ways to be notified about the state of an animation:
- Add a completion block to the current transaction using the setCompletionBlock: method. When all of the animations in the transaction finish, the transaction executes your completion block.
- Assign a delegate to your CAAnimation object and implement the animationDidStart: and animationDidStop:finished: delegate methods.
基于 layer 的view 怎么做动画
iOS 中修改 layer 的规则
[UIView animateWithDuration:1.0 animations:^{
// Change the opacity implicitly.
myView.layer.opacity = 0.0;
// Change the position explicitly.
CABasicAnimation* theAnim = [CABasicAnimation animationWithKeyPath:@"position"];
theAnim.fromValue = [NSValue valueWithCGPoint:myView.layer.position];
theAnim.toValue = [NSValue valueWithCGPoint:myNewPosition];
theAnim.duration = 3.0;
[myView.layer addAnimation:theAnim forKey:@"AnimateFrame"];
}];
建立 layer 层级
将 layer 放入 layer层级中
添加、插入、移除 sublayer
sublayer 的位置和大小
myLayer.bounds = CGRectMake(0, 0, 100, 100);
myLayer.position = CGPointMake(200, 200);
重点:对于 layer 的宽高总是使用整数
layer 层级如何影响动画
speed 属性,动画速度的倍数。
调整layer层级的布局
Core Animation supports several options for adjusting the size and position of sublayers in response to changes to their superlayer. In iOS, the pervasive use of layer-backed views makes the creation of layer hierarchies less important; only manual layout updates are supported. For OS X, several other options are available that make it easier to manage your layer hierarchies
sublayer 和 cliping
masksToBounds 属性设置 YES,就可以启用自动裁剪
在layer 之间转换坐标值
还有 convertTime:fromLayer 和 convertTime:toLayer
高级动画技巧
过渡动画支持更改 layer 可见性
创建 CATransition 对象 ,并添加给 layer。
Listing 5-1 shows the code used to create an animated push transition between two views. In the example, both myView1 and myView2 are located at the same position in the same parent view but only myView1 is currently visible. The push transition causes myView1 to slide out to the left and fade until it is hidden while myView2 slides in from the right and becomes visible. Updating the hidden property of both views ensures that the visibility of both views is correct at the end of the animation.
CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
transition.duration = 1.0;
// Add the transition animation to both layers
[myView1.layer addAnimation:transition forKey:@"transition"];
[myView2.layer addAnimation:transition forKey:@"transition"];
// Finally, change the visibility of the layers.
myView1.hidden = YES;
myView2.hidden = NO;
自定义动画的时间
CAMediaTiming 协议。CAAnimation
和 CALayer
都遵守了这个协议。
#import <QuartzCore/CABase.h>
#import <objc/objc.h>
#import <Foundation/NSObject.h>
/* The CAMediaTiming protocol is implemented by layers and animations, it
* models a hierarchical timing system, with each object describing the
* mapping from time values in the object's parent to local time.
*
* Absolute time is defined as mach time converted to seconds. The
* CACurrentMediaTime function is provided as a convenience for querying the
* current absolute time.
*
* The conversion from parent time to local time has two stages:
*
* 1. conversion to "active local time". This includes the point at
* which the object appears in the parent's timeline, and how fast it
* plays relative to the parent.
*
* 2. conversion from active to "basic local time". The timing model
* allows for objects to repeat their basic duration multiple times,
* and optionally to play backwards before repeating. */
@class NSString;
NS_ASSUME_NONNULL_BEGIN
@protocol CAMediaTiming
/* The begin time of the object, in relation to its parent object, if
* applicable. Defaults to 0. */
@property CFTimeInterval beginTime;
/* The basic duration of the object. Defaults to 0. */
@property CFTimeInterval duration;
/* The rate of the layer. Used to scale parent time to local time, e.g.
* if rate is 2, local time progresses twice as fast as parent time.
* Defaults to 1. */
@property float speed;
/* Additional offset in active local time. i.e. to convert from parent
* time tp to active local time t: t = (tp - begin) * speed + offset.
* One use of this is to "pause" a layer by setting `speed' to zero and
* `offset' to a suitable value. Defaults to 0. */
@property CFTimeInterval timeOffset;
/* The repeat count of the object. May be fractional. Defaults to 0. */
@property float repeatCount;
/* The repeat duration of the object. Defaults to 0. */
@property CFTimeInterval repeatDuration;
/* When true, the object plays backwards after playing forwards. Defaults
* to NO. */
@property BOOL autoreverses;
/* Defines how the timed object behaves outside its active duration.
* Local time may be clamped to either end of the active duration, or
* the element may be removed from the presentation. The legal values
* are `backwards', `forwards', `both' and `removed'. Defaults to
* `removed'. */
@property(copy) NSString *fillMode;
@end
/* `fillMode' options. */
CA_EXTERN NSString * const kCAFillModeForwards
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCAFillModeBackwards
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCAFillModeBoth
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCAFillModeRemoved
__OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
NS_ASSUME_NONNULL_END
得到 layer 的当前 local time
CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];
暂停和重新开始动画
-(void)pauseLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
-(void)resumeLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}
显式 Transactions 可以更改动画参数
创建显式的 transaction
[CATransaction begin];
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];
修改动画的默认持续时间
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
forKey:kCATransactionAnimationDuration];
// Perform the animations
[CATransaction commit];
嵌套显式 transaction
[CATransaction begin]; // Outer transaction
// Change the animation duration to two seconds
[CATransaction setValue:[NSNumber numberWithFloat:2.0f]
forKey:kCATransactionAnimationDuration];
// Move the layer to a new position
theLayer.position = CGPointMake(0.0,0.0);
[CATransaction begin]; // Inner transaction
// Change the animation duration to five seconds
[CATransaction setValue:[NSNumber numberWithFloat:5.0f]
forKey:kCATransactionAnimationDuration];
// Change the zPosition and opacity
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit]; // Inner transaction
[CATransaction commit]; // Outer transaction
给动画添加透视
给 parent layer 添加透视变化
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0/eyePosition;
// Apply the transform to a parent layer.
myParentLayer.sublayerTransform = perspective;
改变 layer 的默认行为
CAAction 协议
/** Action (event handler) protocol. **/
@protocol CAAction
/* Called to trigger the event named 'path' on the receiver. The object
* (e.g. the layer) on which the event happened is 'anObject'. The
* arguments dictionary may be nil, if non-nil it carries parameters
* associated with the event. */
- (void)runActionForKey:(NSString *)event object:(id)anObject
arguments:(nullable NSDictionary *)dict;
@end
自定义操作对象采用CAAction协议
action 对象必须被安装在 layer 上,才能有效果
核心动画寻找 action 对象的顺序:
- 如果 layer 有一个 delegate, 并且delegate 实现了
actionForLayer:forKey
方法,layer调用这个方法。delegate 必须做下面几件事之一:
- 针对 key 返回 action 对象
- 没有它没有 handle the action 就返回 nil,这种情况会接着寻找
- 搜寻到末尾的时候立即返回 NSNull 对象
- 在 layer 的 actions 字典中寻找给定的 key
- 在 style 字典中寻找包含 key 的 actions 字典(总而言之,
style
字典包含了一个 key 为actions
,value 也是一个字典。layer 在这第二个字典中寻找给定的 key) - layer 调用它的 defaultActionForKey: 类方法
- layer 执行核心动画定义的隐式 action
使用 layer 代理对象提供 action
- (id<CAAction>)actionForLayer:(CALayer *)theLayer
forKey:(NSString *)theKey {
CATransition *theAnimation=nil;
if ([theKey isEqualToString:@"contents"]) {
theAnimation = [[CATransition alloc] init];
theAnimation.duration = 1.0;
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
theAnimation.type = kCATransitionPush;
theAnimation.subtype = kCATransitionFromRight;
}
return theAnimation;
}
使用 CATransaction 类暂时禁用 action
Temporarily disabling a layer’s actions
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
forKey:kCATransactionDisableActions];
[aLayer removeFromSuperlayer];
[CATransaction commit];
提升动画性能
为 OS X Views 选择最佳的重绘策略
在 OS X 中更新 layer 来优化 rendering path
通用技巧和窍门
尽可能使用不透明的 layer
对 CAShapeLayer 对象使用更简单的 path
对相同 layer 明确设置 layer 内容
总是将 layer 的大小设置为 整数值
根据需要使用异步来渲染 layer
当给 layer 添加阴影的时候指定 shadow path
Appendix A: layer style property animation
Layer Style Property Animations