UIView动画一个代码块能实现一个动画,实现一些常规简单动画比较快捷省心。当面对特殊的产品需求,如动画的停止启动、大量的组合嵌套时,晕眩的的大量block能不能实现暂且不说,顺畅高效的的动画效果是不太指望了,这时候就要到核心动画CoreAnimation。
CoreAnimation的官方大佬解释 (貌似只有微信和pc端能打开)
CoreAnimation作用在CALayer,不是UIView。一个UIView持有一个CALayer负责展示,view是这个layer的delegate。改变view的属性实际上是在改变它持有的layer的属性,layer属性发生改变时会调用代理方法actionForLayer: forKey: 来得知此次变化是否需要动画。UIViewAnimate是对CoreAnimation的高级api封装。(UIView和CALayer可看这篇文章:UIView和CALayer是啥关系?
)
先认识一下他的框架QuartzCore.framework
#ifndef COREANIMATION_H
#define COREANIMATION_H
#include
#include
#ifdef __OBJC__
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#endif
#endif /* COREANIMATION_H */
一、CAAnimation,所有的核心动画形式都定义在这个CAAnimation.h头文件中
1、CAAnimation
/* 创建动画:是一个动画抽象类,但是不要直接使用`CAAnimation`类,而是使用它的子类*/
+ (instancetype)animation;
/* 重写修改CALayer或其子类属性的默认值,key为属性名称,如果没有该属性则返回nil */
+ (nullable id)defaultValueForKey:(NSString *)key;
/* 通过传入一个关键字对动画对象 进行序列化本地存储,并且返回是否成功。
*然后使用相同的关键字调用前者(上个方法)来获取这个持久化的对象
*/
- (BOOL)shouldArchiveValueForKey:(NSString *)key;
/ * 动画的动作规则,包含以下值
*kCAMediaTimingFunctionLinear 匀速
*kCAMediaTimingFunctionEaseIn 慢进快出
*kCAMediaTimingFunctionEaseOut 快进慢出
*kCAMediaTimingFunctionEaseInEaseOut 慢进慢出 中间加速
*kCAMediaTimingFunctionDefault 默认
*/
@property(nullable, strong) CAMediaTimingFunction *timingFunction;
/* 动画的代理 */
@property(nullable, strong) id delegate;
/*动画执行完以后是否移除动画,默认YES*/
@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;
2、CAAnimationDelegate
/* 当动画开始的时候被调用 */
- (void)animationDidStart:(CAAnimation *)anim;
/* 当动画结束的时候被调用 */
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
3、CAPropertyAnimation
CAAnimation的子类,自身并不能对layer进行动画操作,需要其子类CABasicAnimation和CAKeyframeAnimation来实现动画操作
/* 创建动画: 该方法仅需要一个参数,该参数只是一个字符串的值,指定CALayer的动画属性名,该设置属性动画控制CALayer的哪个动画属性持续的改变 */
+ (instancetype)animationWithKeyPath:(nullable NSString *)path;
/* 指定接收层动画的关键路径
* keyPath可以使用的key
#define angle2Radian(angle) ((angle)/180.0*M_PI)
transform.rotation.x 围绕x轴翻转 参数:角度 angle2Radian(4)
transform.rotation.y 围绕y轴翻转 参数:同上
transform.rotation.z 围绕z轴翻转 参数:同上
transform.rotation 默认围绕z轴
transform.scale.x x方向缩放 参数:缩放比例 1.5
transform.scale.y y方向缩放 参数:同上
transform.scale.z z方向缩放 参数:同上
transform.scale 所有方向缩放 参数:同上
transform.translation.x x方向移动 参数:x轴上的坐标 100
transform.translation.y x方向移动 参数:y轴上的坐标
transform.translation.z x方向移动 参数:z轴上的坐标
transform.translation 移动 参数:移动到的点 (100,100)
opacity 透明度 参数:透明度 0~1.0
backgroundColor 背景颜色 参数:颜色 (id)[[UIColor redColor] CGColor]
cornerRadius 圆角 参数:圆角半径 5
borderWidth 边框宽度 参数:边框宽度 5
bounds 大小 参数:CGRect
contents 内容 参数:CGImage
contentsRect 可视内容 参数:CGRect 值是0~1之间的小数
hidden 是否隐藏
position 位置
shadowColor 阴影颜色
shadowOffset 阴影偏移量
shadowOpacity 阴影透明度
shadowRadius 阴影圆角
*/
@property(nullable, copy) NSString *keyPath;
/* 如何处理多个动画在同一时间段执行的结果,若为true,同一时间段的动画合成为一个动画,默认为NO。
*使用 CAKeyframeAnimation 时必须将该属性指定为 TRUE ,否则不会出现期待的结果 */
@property(getter=isAdditive) BOOL additive;
/* 下一次动画执行是否接着刚才的动画,默认为NO. */
@property(getter=isCumulative) BOOL cumulative;
/* 该属性值是一个CAValueFunction对象,该对象负责对属性改变的插值计算,默认为nil*/
@property(nullable, strong) CAValueFunction *valueFunction;
4、CABasicAnimation
基础动画,CAPropertyAnimation的子类
/*
* fromValue和toValue不为nil,keyPath属性值在fromValue与toValue之间渐变
* fromValue和byValue不为nil,keyPath属性值在fromValue与(fromValue+byValue)之间渐变
* byValue和toValue不为nil,keyPath属性值在(toValue-byValue)与toValue之间渐变
* fromValue不为nil,keyPath属性值在fromValue与图层对应当前值之间渐变
* toValue不为nil,keyPath属性值在图层对应当前值与toValue之间渐变
* byValue不为nil,keyPath属性值在图层对应当前值与(图层对应当前值+toValue)之间渐变
注意:value的值可以设置为CATransform3D的对象,实现3D动画效果!
*/
/* keyPath相应属性的初始值 */
@property(nullable, strong) id fromValue;
/* keyPath相应属性的相对插值 */
@property(nullable, strong) id toValue;
/* keyPath相应属性的结束值 */
@property(nullable, strong) id byValue;
5、CAKeyframeAnimation
关键帧动画,也是CAPropertyAnimation的子类,与CABasicAnimation的区别是: CABasicAnimation只能从一个数值变到另一个数值,而CAKeyframeAnimation会使用一个NSArray保存这些数值。即CAKeyframeAnimation对keyPath可以使用多值,而且支持路线Path。
/* 里面的元素称为“关键帧”(keyframe)。动画对象会在指定的时间内,依次显示values数组中的每一个关键帧。
eg.
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
NSValue *value1=[NSValue valueWithCGPoint:CGPointMake(100, 100)];
NSValue *value2=[NSValue valueWithCGPoint:CGPointMake(200, 100)];
NSValue *value3=[NSValue valueWithCGPoint:CGPointMake(200, 200)];
animation.values=@[value1,value2,value3];
*/
@property(nullable, copy) NSArray *values;
/* 可以设置一个CGPathRef、CGMutablePathRef,让图层按照路径轨迹移动。
* path只对CALayer的anchorPoint和position起作用。如果设置了path,那么values将被忽略
* eg.
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
CGMutablePathRef path=CGPathCreateMutable();
CGPathAddEllipseInRect(path, NULL, CGRectMake(150, 100, 100, 100));
animation.path=path;
CGPathRelease(path);
*/
@property(nullable) CGPathRef path;
/* 给定关键帧片段的时间,其取值范围为0到1。
*keyTimes中的每一个时间值都对应values中的每一帧。如果没有设置keyTimes,各个关键帧的时间是平分的 */
@property(nullable, copy) NSArray *keyTimes;
/* 时间函数数组,类似于运动的加速度 */
@property(nullable, copy) NSArray *timingFunctions;
/* 计算模式:其主要针对的是每一帧的内容为一个坐标点的情况,也就是对anchorPoint和 position进行的动画
* 当在平面座标系中有多个离散的点的时候,可以是离散的,也可以直线相连后进行插值计算,也可以使用圆滑的曲线将他们相连后进行插值计算
* calculationMode目前提供如下几种模式:
kCAAnimationLinear 默认值,表示当关键帧为坐标点的时候,关键帧之间直接直线相连进行插值计算
kCAAnimationDiscrete 离散的,不进行插值计算,所有关键帧直接逐个进行显示
kCAAnimationPaced 使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions无效
kCAAnimationCubic 对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,这里的主要目的是使得运行的轨迹变得圆滑
kCAAnimationCubicPaced 在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的
*/
@property(copy) NSString *calculationMode;
/* 控制着曲线的紧密度(正值将越紧,负值将越宽松) */
@property(nullable, copy) NSArray *tensionValues;
/* 控制片段之间的链接(正值将有锋利的圆角,负值将是倒立的圆角) */
@property(nullable, copy) NSArray *continuityValues;
/* 定义了曲线发生的地点(正值将在在控制点前移动曲线,负值将在控制点后移动) */
@property(nullable, copy) NSArray *biasValues;
/* 旋转方式
kCAAnimationRotateAuto : 自动
kCAAnimationRotateAutoReverse : 自动翻转 不设置则不旋转
*/
@property(nullable, copy) NSString *rotationMode;
6、CASpringAnimation
CABasicAnimation的子类,弹簧效果动画
/* 质量(>0),影响图层运动时的弹簧惯性,质量越大,弹簧拉伸和压缩的幅度越大,动画的速度变慢.默认值为1*/
@property CGFloat mass;
/* 刚度系数(劲度系数/弹性系数)(>0),刚度系数越大,形变产生的力就越大,运动越快。默认值为100 */
@property CGFloat stiffness;
/* 阻尼系数(>0),阻止弹簧伸缩的系数,阻尼系数越大,停止越快。默认值为10 */
@property CGFloat damping;
/* 初始速度
* 默认0,表示不动的对象;
* 负值,表示速度方向与运动方向相反
* 正值,表示速度方向与运动方向一致
*/
@property CGFloat initialVelocity;
/* 结算时间,返回弹簧动画到停止时的估算时间,根据当前的动画参数估算,通常弹簧动画的时间使用结算时间比较准确 */
@property(readonly) CFTimeInterval settlingDuration;
7、CATransition
CAAnimation的子类,提供了动画的渐变效果
/* 表示其渐变的效果的类型
kCATransitionFade,溶解消失
kCATransitionMoveIn,渐入
kCATransitionPush,推动
kCATransitionReveal,揭开
*/
@property(copy) NSString *type;
/* 此类用于过渡运动方向的转变且分为上下左右四种
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom
*/
@property(nullable, copy) NSString *subtype;
/*表示渐变开始和渐变结束的数值且其大小区间为[0,1],注意endProgress必须小于或等于startProgress */
@property float startProgress;
@property float endProgress;
8、CAAnimationGroup
CAAnimation的子类,添加多种动画的动画组合
/* 动画组 */
@property(nullable, copy) NSArray *animations;
二、CATransform3D(转场动画),针对CALyer进行3D转换(本质是矩阵变换)
官方api解读:
struct CATransform3D
{
CGFloat m11, m12, m13, m14;
CGFloat m21, m22, m23, m24;
CGFloat m31, m32, m33, m34;
CGFloat m41, m42, m43, m44;
};
typedef struct CATransform3D CATransform3D;
一个4*4的CGFloat矩阵,粘上矩阵算法规则,以免被一些资料弄得一脸懵逼
/* The identity transform: [1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1].
*未发生变化保持初始状态
*/
const CATransform3D CATransform3DIdentity
/* 判断一个transform3D的对象是否是最初的状态 */
bool CATransform3DIsIdentity (CATransform3D t)
/* 比较两个transform3D对象ab是否相等 */
bool CATransform3DEqualToTransform (CATransform3D a,
CATransform3D b)
/**** 平移 ****/
/* Returns a transform that translates by '(tx, ty, tz)':
* t' = [1 0 0 0; 0 1 0 0; 0 0 1 0; tx ty tz 1].
*=> 返回一个平移变换的transform3D对象 tx,ty,tz对应x,y,z轴的平移
*/
CATransform3D CATransform3DMakeTranslation (CGFloat tx,
CGFloat ty, CGFloat tz)
/* Translate 't' by '(tx, ty, tz)' and return the result:
* t' = translate(tx, ty, tz) * t.
* 在某个transform3D变换的基础上进行平移变换,t是上一个transform3D,其他参数同上
*/
CATransform3D CATransform3DTranslate (CATransform3D t, CGFloat tx, CGFloat ty, CGFloat tz)
/**** 缩放 ****/
/* Returns a transform that scales by `(sx, sy, sz)':
* t' = [sx 0 0 0; 0 sy 0 0; 0 0 sz 0; 0 0 0 1].
*=>缩放 :x,y,z分别对应x轴,y轴,z轴的缩放比例
*/
CATransform3D CATransform3DMakeScale (CGFloat sx, CGFloat sy, CGFloat sz)
/* Scale 't' by '(sx, sy, sz)' and return the result:
* t' = scale(sx, sy, sz) * t.
*在一个transform3D变换的基础上进行缩放变换,其他参数同上
*/
CATransform3D CATransform3DScale (CATransform3D t, CGFloat sx, CGFloat sy, CGFloat sz)
/**** 旋转 ****/
/* Returns a transform that rotates by 'angle' radians about the vector
* '(x, y, z)'. If the vector has length zero the identity transform is
* returned
*angle参数是旋转的角度(弧度制)
*x,y,z决定了旋转围绕的中轴,取值为-1~1之间,如(1,0,0),则是绕x轴旋转,(0.5,0.5,0),则是绕x轴与y轴中间45度(π/4)为轴旋转
*/
CATransform3D CATransform3DMakeRotation (CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
/* Rotate 't' by 'angle' radians about the vector '(x, y, z)' and return
* the result. If the vector has zero length the behavior is undefined:
* t' = rotation(angle, x, y, z) * t.
* 在一个transform3D的基础上进行旋转变换,其他参数如上
*/
CATransform3D CATransform3DRotate (CATransform3D t, CGFloat angle,CGFloat x, CGFloat y, CGFloat z)
/* 将两个 transform3D对象变换属性进行叠加,返回一个新的transform3D对象 */
CATransform3D CATransform3DConcat (CATransform3D a, CATransform3D b)
/* 类似CGAffineTransform 中的CGAffineTransformInvert 代表 反向变换*/
CATransform3D CATransform3DInvert (CATransform3D t)
/* 将一个CGAffineTransform转化为CATransform3D */
CATransform3D CATransform3DMakeAffineTransform (CGAffineTransform m)
/* 判断一个CATransform3D是否可以转换为CGAffineTransfor */
bool CATransform3DIsAffine (CATransform3D t)
/* 将CATransform3D转换为CGAffineTransform */
CGAffineTransform CATransform3DGetAffineTransform (CATransform3D t)
三、CADisplayLink(屏幕刷新频率同步绘图)
CADisplayLink是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器。
与NSTimer对比:
<1>原理不同
CADisplayLink是一个能让我们以和屏幕刷新率同步的频率将特定的内容画到屏幕上的定时器类。 CADisplayLink以特定模式注册到runloop后, 每当屏幕显示内容刷新结束的时候,runloop就会向 CADisplayLink指定的target发送一次指定的selector消息, CADisplayLink类对应的selector就会被调用一次。
NSTimer以指定的模式注册到runloop后,每当设定的周期时间到达后,runloop会向指定的target发送一次指定的selector消息。
<2>周期设置方式不同
iOS设备的屏幕刷新频率(FPS)是60Hz,因此CADisplayLink的selector 默认调用周期是每秒60次,这个周期可以通过frameInterval属性设置, CADisplayLink的selector每秒调用次数=60/ frameInterval。比如当 frameInterval设为2,每秒调用就变成30次。因此, CADisplayLink 周期的设置方式略显不便。
NSTimer的selector调用周期可以在初始化时直接设定,相对就灵活的多。
<3>精确度不同
iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。
NSTimer的精确度就显得低了点,比如NSTimer的触发时间到的时候,runloop如果在阻塞状态,触发时间就会推迟到下一个runloop周期。并且 NSTimer新增了tolerance属性,让用户可以设置可以容忍的触发的时间的延迟范围。
<4>使用场景
CADisplayLink使用场合相对专一,适合做UI的不停重绘,比如自定义动画引擎或者视频播放的渲染。
NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。
/* 初始化一个新的 CADisplayLink对象,把它添加到一个runloop中,并给它提供一个 target和selector 在屏幕刷新的时候调用 */
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
/* 如果想开启link需要把link加入到runloop中
*除非计时器被停止,否则每次屏幕刷新时,计时器的方法都会被触发.
*每个计时器对象只能加入到一个runloop中,但是可以被添加到不同的模式中,当CADisplayLink被加入到runloop时,会被runloop隐式retain.如果想从所有的模式中移除计时器,需要执行-invalidate()方法
*/
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
/* 从runloop中移除 移除计时器
*将接收者从给定的模式中移除,这个方法会对计时器进行隐式的release.
*在调用removeFromRunloop方法,需要做判断,如果当期计时器不在runloop的话,会出现野指针的crash.
*出现crash的原因是runloop多次调用了release方法,进行了over-release */
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
/* 从runloop所有模式中移除计时器,并取消计时器和target的关联关系.
*多次调用这个方法,不会出现crash */
- (void)invalidate;
/* 时间戳,这个属性用来返回上一次屏幕刷新的时间戳.
*如果视频播放的应用,可以通过时间戳来获取上一帧的具体时间,来计算下一帧*/
@property(readonly, nonatomic) CFTimeInterval timestamp;
/* 屏幕刷新时间间隔,(不掉帧时刷新频率是60HZ, 刷新时间间隔是16.7ms)
*duration属性用于提供屏幕最大刷新频率(maximumFramesPerSecond)下每一帧的时间间隔.
*这个属性可以用于在应用中获取帧率 */
@property(readonly, nonatomic) CFTimeInterval duration;
/* 下一次屏幕刷新被调用到时间 */
@property(readonly, nonatomic) CFTimeInterval targetTimestamp CA_AVAILABLE_IOS_STARTING(10.0, 10.0, 3.0);
/* When true the object is prevented from firing. Initial state is false.
*设置为YES的时候会暂停事件的触发 */
@property(getter=isPaused, nonatomic) BOOL paused;
/* Defines how many display frames must pass between each time the
* display link fires. Default value is one, which means the display
* link will fire for every display frame. Setting the interval to two
* will cause the display link to fire every other display frame, and
* so on. The behavior when using values less than one is undefined.
* DEPRECATED - use preferredFramesPerSecond.
*改变每秒运行帧数,如设置为2,意味CADisplayLink每隔一帧运行一次,有效的逻辑每秒运行30次 iOS10以后用preferredFramesPerSecond
*/
@property(nonatomic) NSInteger frameInterval CA_AVAILABLE_BUT_DEPRECATED_IOS (3.1, 10.0, 9.0, 10.0, 2.0, 3.0, "use preferredFramesPerSecond");
/* Defines the desired callback rate in frames-per-second for this
* display link. The default value is 60. If set to zero, the
* display link will fire at the native cadence of the display hardware.
* The display link will make a best-effort attempt at issuing callbacks
* at the requested rate.
* 修改帧率 : 如果在特定帧率内无法提供对象的操作,可以通过降低帧率解决.
*一个拥有持续稳定但是较慢帧率的应用要比跳帧的应用顺滑的多. 可以通过preferredFramesPerSecond来设置每秒刷新次数.
* preferredFramesPerSecond默认值为屏幕最大帧率(maximumFramesPerSecond),目前是60.
* 实际的屏幕帧率会和preferredFramesPerSecond有一定的出入,结果是由设置的值和屏幕最大帧率(maximumFramesPerSecond)相互影响产生的.
*规则大概如下:
*如果屏幕最大帧率(preferredFramesPerSecond)是60,实际帧率只能是15, 20, 30, 60中的一种.
*如果设置大于60的值,屏幕实际帧率为60.如果设置的是26~35之间的值,实际帧率是30.
* 如果设置为0,会使用最高帧率 */
@property(nonatomic) NSInteger preferredFramesPerSecond CA_AVAILABLE_IOS_STARTING(10.0, 10.0, 3.0);
NSTimer.
/* 实例化方法, 响应事件用的NSInvocation, 需要手动添加到RunLoop中才会生效 */
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
/** 实例化方法, 响应事件用的NSIvocation, 系统为自动帮你将timer添加到currentRunLoop中,defaultMode
* 所以我们通常使用scheduledTimerWithTimeInterval构造NSTimer而不是timerWithTimeInterval
*/
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
/* 实例化方法, 响应事件用的PerformanceSelector, userInfo中可以用来传参数,需要手动添加到RunLoop中 */
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
/* 实例化方法,响应事件用的PerformanceSelector, userInfo可以用来传递参数,
* 系统会自动帮你将timer添加到currentRunLoop中, defaultMode */
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
/*
/// Creates and returns a new NSTimer object initialized with the specified block object. This timer needs to be scheduled on a run loop (via -[NSRunLoop addTimer:]) before it will fire.
/// - parameter: timeInterval The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead
/// - parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
/// - parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references
//实例化方法, 以block的方式传入要执行的内容, 需要手动添加到RunLoop中
*/
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
/*
/// Creates and returns a new NSTimer object initialized with the specified block object and schedules it on the current run loop in the default mode.
/// - parameter: ti The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead
/// - parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
/// - parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references
///-实例化方法, 以block的方式传入要执行的内容,系统会自动帮你将timer添加到currentRunLoop中,defaultMode
*/
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
/*
/// Initializes a new NSTimer object using the block as the main body of execution for the timer. This timer needs to be scheduled on a run loop (via -[NSRunLoop addTimer:]) before it will fire.
/// - parameter: fireDate The time at which the timer should first fire.
/// - parameter: interval The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead
/// - parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
/// - parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references
///-跟上面类似, 只是多指定了一个开始时间
*/
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
/* 跟上面类似, 只是多指定了一个开始时间 */
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER;
/* 立即执行一次定时器方法, 注意不是立即开启定时器
* 当加入到runloop中timer不需要激活即可按照设定的时间触发事件。fire只是相当于手动让timer触发一次事件。
* 如果timer设置的repeat为NO,则fire之后timer立即销毁。
* 如果timer的repeat为YES,则到了之前设置的时间他依旧会按部就班的触发事件。
* fire只是单独触发了一次事件,并不影响原timer的节奏
*/
- (void)fire;
/* 设置当前timer的事件的触发时间。通常我们使用这个属性来做计时器的暂停与恢复 */
@property (copy) NSDate *fireDate;
egg.
///暂停计时器
self.timer.fireDate = [NSDate distantFuture];
///恢复计时器
self.timer.fireDate = [NSDate distantPast];
/* 只读属性, 获取当前timer的触发间隔 */
@property (readonly) NSTimeInterval timeInterval;
/**允许误差时间。
*我们知道NSTimer事件的触发事件是不准确的,完全取决于当前runloop处理的时间。
*如果当前runloop在处理复杂运算,则timer执行时间将会被推迟,直到复杂运算结束后立即执行触发事件,之后再按照初始设置的节奏去执行。
*当设置tolerance之后在允许范围内的延迟可以触发事件,超过的则不触发。
*默认是时间间隔的1/10
*/
@property NSTimeInterval tolerance API_AVAILABLE(macos(10.9), ios(7.0), watchos(2.0), tvos(9.0));
/** 销毁计时器
* [self.timer invalidate];
* self.timer = nil;
*/
- (void)invalidate;
/**有效性
*当你用NSTimer时,若在另一个线程中调用了invalid方法,那么这个定时器对象将很有可能被释放掉。此时再调用其isValid方法会即刻崩溃。
*解决方法是,当你调用invalid方法将定时器无效化之后,马上将主线程中所持有的定时器对象引用置空。然后在主线程中通过判断其定时器对象引用是否为空来判定时器是否有效
*/
@property (readonly, getter=isValid) BOOL valid;
/* 用户信息 --携带参数 */
@property (nullable, readonly, retain) id userInfo;
四、Transaction(Animation中的事物)
事务类,可以对多个layer的属性同时进行修改.它分隐式事务,和显式事务.
区分隐式动画和隐式事务:隐式动画通过隐式事务实现动画。
区分显式动画和显式事务:显式动画有多种实现方式,显式事务是一种实现显式动画的方式。
/** Begin a new transaction for the current thread; nests.
*开启事务
*/
+ (void)begin;
/* 提交当前事务期间进行的所有更改。 如果不存在当前事务,则引发异常 */
+ (void)commit;
/* 提交任何现存的隐式事务。 将延迟实际的commit until,任何嵌套的显式事务已经完成。*/
+ (void)flush;
/** 锁定和解锁全局锁的方法。
* Layer方法自动在修改共享状态时获得这一点,但是调用者可能需要锁定多个操作以确保一致性。
* 锁是一个递归自旋锁(即不应长时间保持)*/
+ (void)lock;
+ (void)unlock;
/**每个线程事务属性的访问器。
*定义添加到图层的动画的默认持续时间。 默认为1 / 4s。*/
+ (CFTimeInterval)animationDuration;
+ (void)setAnimationDuration:(CFTimeInterval)dur;
/* * 每线程事务属性的访问器.
* 默认值为nil,当设置为非nil值时,添加到图层的任何动画都将此值设置为其“timingFunction”属性 */
+ (nullable CAMediaTimingFunction *)animationTimingFunction;
+ (void)setAnimationTimingFunction:(nullable CAMediaTimingFunction *)function;
/**每线程事务属性的访问器。
*定义图层的-actionForKey:方法是否用于为每个图层属性更改找到一个操作(也称为implicitanimation)。 默认为NO,即启用了隐式动画 */
+ (BOOL)disableActions;
+ (void)setDisableActions:(BOOL)flag;
/**每个线程事务属性的“completionBlock”访问器。
*设置为非nil值后,一旦此事务组随后添加的所有动画都已完成(或已删除),块就被保证被调用(在主线程上) )。
*如果在提交当前事务组之前没有添加动画(或者完成块被设置为不同的值),则将立即调用该块。
*/
#if __BLOCKS__
+ (nullable void (^)(void))completionBlock;
+ (void)setCompletionBlock:(nullable void (^)(void))block;
#endif
/**将任意键控数据与当前事务(即与当前线程)关联。
*嵌套事务具有嵌套数据作用域,即读取一个键,搜索已设置它的最内层作用域,设置键总是将其设置在最内层作用域。
*当前支持的事务属性包括:“animationDuration”,“animationTimingFunction”,“completionBlock”,“disableActions”。
* 尝试将属性设置为非文档类型以外的类型具有未定义的结果
*/
+ (nullable id)valueForKey:(NSString *)key;
+ (void)setValue:(nullable id)anObject forKey:(NSString *)key;
@end
/** Transaction property ids. **/
CA_EXTERN NSString * const kCATransactionAnimationDuration
CA_EXTERN NSString * const kCATransactionDisableActions
CA_EXTERN NSString * const kCATransactionAnimationTimingFunction
CA_EXTERN NSString * const kCATransactionCompletionBlock
隐式事务
除显式事务外,任何对于CALayer属性的修改,都是隐式事务.这样的事务会在run-loop中被提交.
- (void)viewDidLoad {
//初始化一个layer,添加到主视图
layer=[CALayer layer];
layer.bounds = CGRectMake(0, 0, 200, 200);
layer.position = CGPointMake(160, 250);
layer.backgroundColor = [UIColor redColor].CGColor;
layer.borderColor = [UIColor blackColor].CGColor;
layer.opacity = 1.0f;
[self.view.layer addSublayer:layer];
[super viewDidLoad];
}
- (IBAction)changeLayerProperty {
//设置变化动画过程是否显示,默认为YES不显示
[CATransaction setDisableActions:NO];
//设置圆角
layer.cornerRadius = (layer.cornerRadius == 0.0f) ? 30.0f : 0.0f;
//设置透明度
layer.opacity = (layer.opacity == 1.0f) ? 0.5f : 1.0f;
}
-
显式事务
通过明确的调用begin,commit来提交动画
修改执行时间[CATransaction begin]; //显式事务默认开启动画效果,kCFBooleanTrue关闭 [CATransaction setValue:(id)kCFBooleanFalse forKey:kCATransactionDisableActions]; //动画执行时间 [CATransaction setValue:[NSNumber numberWithFloat:5.0f] forKey:kCATransactionAnimationDuration]; //[CATransaction setAnimationDuration:[NSNumber numberWithFloat:5.0f]]; anotherLayer.cornerRadius = (anotherLayer.cornerRadius == 0.0f) ? 30.0f : 0.0f; layer.opacity = (layer.opacity == 1.0f) ? 0.5f : 1.0f; [CATransaction commit];
3.事务嵌套
[CATransaction begin];
[CATransaction setDisableActions:YES];
layer.cornerRadius = (layer.cornerRadius == 0.0f) ? 30.0f : 0.0f;
[CATransaction commit];
//上面的动画并不会立即执行,需要等最外层的commit
[NSThread sleepForTimeInterval:10];
//显式事务默认开启动画效果,kCFBooleanTrue关闭
[CATransaction setValue:(id)kCFBooleanFalse forKey:kCATransactionDisableActions];
//动画执行时间
[CATransaction setValue:[NSNumber numberWithFloat:10.0f] forKey:kCATransactionAnimationDuration];
//[CATransaction setAnimationDuration:[NSNumber numberWithFloat:5.0f]];
anotherLayer.cornerRadius = (anotherLayer.cornerRadius == 0.0f) ? 30.0f : 0.0f;
[CATransaction commit];
未完待续。。。。。。