iOS动画

2020-12-01  本文已影响0人  forping

相关或者无关的

layer

Layers Provide the Basis for Drawing and Animations(Layers是绘图和动画的基础)

Layer是在3D空间中的2D平面,管理几何(例如rotate,transfrom),内容(image等),和可视属性(backgroundColor,alpha)等信息。因为图层主要用来管理数据,可以看作对象模型,
由于基于Layer的绘制是处理静态的Bitmap的,而bitmap的处理又是GPU所擅长的,所以它的效率要比基于View绘制的高很多,因为基于View绘制的每次都要进行drawRect的调用重新绘制。
大部分图层,捕获显示的内容并缓存它们到位图中。位图有时也被称为储备(backing store)。当你改变了一个图层的属性值,只是改变了与图层对象相关联的状态信息。当更改触发了一个动画,Core Animation会将该图层的位图数据和状态信息发送给图形处理硬件。图形处理器所做的工作是根据获得的信息对位图进行渲染,用图形处理硬件操纵位图要比图形处理软件能获得更好的动画效果。

基于Layer的动画过程
因为操纵的是静态的位图,基于Layer的绘图和基于View的绘图在技术上有明显的不同。
基于View的绘图,对View的改变经常会触发视图的drawRect:方法以重绘视图内容。但代价相对较高,因为它是CPU在主线程上的操作。
Core Animation尽可能使用图形硬件操纵缓存后的位图来避免了这种开销,从而完成相同或相似的效果。虽然尽可能使用缓存内容,但也必须提供初始内容并不时地进行内容更新。

图层对象定义了自己的几何结构
图层的一项任务是管理自身内容的几何结构。包含内容的边界bound、在屏幕上的位置origin,是否已被旋转、缩放,或应用了某种变换。图层有frame和bound属性来定位图层和它的内容。也有一些View所没有的属性,比如anchorPoint属性,任何变换操作都是围绕该点运转的

图层可在三维空间中操作
Layer有两个操控图层和内容的变换矩阵,transformsublayerTransform属性。

layer内容

图层是管理app内容的数据对象。图层的内容由包含可视数据的位图构成。使用下述三种方式之一可给提供图层的内容:

  1. 直接赋值一个UIImage对象给图层对象contents属性。(这个技术适用于图层内容从不或几乎不改变的情形。)图片类型必须是CGImageRef类型,对于Retina显示设备,这可能也需要你去调整layer的contentsScale属性。使用[[UIScreen mainScreen] scale]可获取正确的缩放率。
  2. 赋值一个代理给图层,由代理负责绘制图层内容。(该技术适用于图层内容可能偶尔改变,且内容可由外部对象提供,比如视图。)
    代理方法:
@protocol CALayerDelegate <NSObject>
@optional
- (void)displayLayer:(CALayer *)layer;
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
- (void)layerWillDraw:(CALayer *)layer
  API_AVAILABLE(macos(10.12), ios(10.0), watchos(3.0), tvos(10.0));
- (void)layoutSublayersOfLayer:(CALayer *)layer;
- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;
@end
// displayLayer:如果代理实现了这个方法,那么要绘制一个bitmap,然后赋值给contents属性
-(void)displayLayer:(CALayer *)layer
{
    NSString * imageName = @"temp";
    layer.contents = (id)[UIImage imageNamed:imageName].CGImage;
}
@end
//drawLayer:inContext:如果代理实现了这个方法,Core Animation提供一个context来生成bitmap,你所做的只是把想要的内容绘制到context
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddArc(path,NULL,100,100,95,0,M_PI*2, YES);
    CGContextBeginPath(ctx);
    CGContextAddPath(ctx,path);
    CGContextSetLineWidth(ctx, 5);
    CGContextStrokePath(ctx);
    CFRelease(path);
}
  1. 定义一个CALayer的子类并覆盖类的绘图方法,有覆盖的方法返回图层的内容。(该技术适用于你需要创建自定义图层的子类,或者你想改变图层基本的绘图行为。)
    3.1 覆盖图层的display方法并使用在方法中直接设置图层的contents属性。
    3.2 覆盖图层的drawInContext:方法并将需要的内容绘制到提供的图形上下文中。
Layer Tree图层树

Layer Tree分为三种,
Model Layer Tree:树中的对象是模型对象,模型对象存储所有动画的目标值。无论何时改变图层的属性值,你使用的始终是某一个模型对象。比如layer.position = CGPointMake(10.0,10.0)修改的就是Model Layer Tree
Presentation Tree:包含所有运行中的动画的瞬时值。代表显示在屏幕上动画的当前值。你不应该更改这个树中的对象。而是读取当前动画的值,可以创建开始于这些值的新的动画。
Render Tree:Render Tree中的对象执行实际的动画,为CoreAnimation私有的,是CoreAnimation实现使用的私有Tree,
通过-[CALayer presentationLayer]-[CALayer modelLayer]可以访问两种Tree

上述三种Tree的对应关系如下图

image.png
常用的Layer子类
CAEmitterLayer 发射器层,用来控制粒子效果
CAGradientLayer 梯度层,颜色渐变
CAEAGLLayer 用OpenGL ES绘制的层
CAReplicationLayer 用来自动复制sublayer
CAScrollLayer 用来管理可滑动的区域
CAShapeLayer 绘制立体的贝塞尔曲线
CATextLayer 可以绘制AttributeString
CATiledLayer 用来管理一副可以被分割的大图
CATransformLayer 用来渲染3D layer的层次结构

position 和 anchorPoint

anchorPoint :锚点(对layer动画有很大影响)
position : 锚点在superLayer中的位置
视图的center属性和图层的position属性指定了anchorPoint相对父图层的位置。
默认anchorPoint值是(0.5,0.5),位于图层的中点,图层以这个点为中心放置,

当改变frame时,position(center)会改变。
当改变position(center)时,frame会改变。

当改变frame时,archorPoint不变。
当改变archorPoint时,frame随之改变

[self.tmpView setFrame:CGRectMake(50, 60, 70, 80)];
/*
frame:{{50, 60}, {70, 80}},center:{85, 100},position:{85, 100},anchorPoint:{0.5, 0.5}
*/
self.tmpView.layer.anchorPoint = CGPointMake(0, 0);
/*
frame:{{85, 100}, {70, 80}},center:{85, 100},position:{85, 100},anchorPoint:{0, 0}
*/

当图层做旋转操作时,会围绕着anchorPoint对应的点旋转,并且任何你对图层阴影的变换操作也是相对于anchorPoint。

frame

frame是一个派生属性,根据layer的bounds,position(center),archorPoint,transform计算.
当改变bounds,position(center),archorPoint,transform当中的任何一个值时,frame都会发生变化。
当变换后的frame是一个平行四边形或者不平行与边轴时,返回一个最小的包括变换后的frame的四个顶点的矩形,这个矩形就是frame。
当改变frame时,archorPoint,transform不变,bounds和position(center)随之改变。

bounds

通过get和set方法,直接读取layer的bounds属性。
当改变frame的size时,bounds会改变。
当改变bounds的size时,frame随之改变

view的transform

view的transform属性是直接调用的layer的affineTransform属性的get和set方法。affineTransform实际上是一个计算属性,当layer调用affineTransform的set方法时,实际上是换算为CATransform3D类型的结构体赋值给layer的transform属性。
同理调用affineTransform的get方法时,也是通过layer的transform属性换算为 CGAffineTransform返回的。
view的transform属性等同于layer的affineTransform属性,是通过get和set方法取的layer的transform的值。

当改变frame时,transform不变。
当改变transform时,frame随之改变

Core Animation 核心动画

Core Animation是iOS平台上负责图形渲染与动画的基础设施。只需配置少量的动画参数(如开始点位置和结束点的位置)即可施展Core Animation魔法。Core Animation将大部分绘图任务交给了图形硬件,图形硬件会加速图形渲染的速度。这种自动化的图形加速技术让动画拥有更高的帧率并且更加的平滑,而且不会加重CPU的负担。
如下图所示,Core Animation位于AppKit和UIKit的底层。Core Animation保留了部分用于扩展其功能的的接口暴露给了应用的视图。使用这些接口能更细粒度地控制应用中的动画。


image.png

Core Animation自身并不是一个绘图系统。它只是一个负责在硬件上合成和操纵应用内容的基础构件。其核心是Layer对象,Layer对象用于管理和操控应用内容。会将捕获的内容放到一副位图中。
更改图层属性会产生动画
使用Core Animation创建的大部分动画都包含对图层属性的配置:frame、origin、size、opacity、transform,backgroundColor以及其他面向可视的属性。
基于图层的动画
图层的位图数据和状态信息从图层内容的可视呈现中被分离。旧的属性值动画到新的属性值的过程中,分离性给了Core Animation介入此过程的机会。
例如:改变position会引起Core Animation将图层从当前的位置移动到新的具体位置。对其他属性做相似的改变将引起适当的动画效果。

image.png
动画期间,Core Animation使用硬件帮你完成绘画每一帧的工作。你只需要指定动画的开始点和结束点,如若需要,也可以指定自定义信息(比如动画运行多久)和动画参数,如果你没有指定, Core Animation会提供适当的默认值。
隐式动画

Core Animation将图层对象属性的变化当做是一个触发器,用以创建和安排一个或多个可执行的隐式动画。大部分属性的值发生了变化都将引起Core Animation为你创建动画对象,动画对象将被安排在下一次更新周期运行。

theLayer.opacity = 0.0;

显式动画

可以显式地使用动画对象呈现相同的变化,创建一个CAAnimation的子类对象并配置该对象的动画参数。在添加动画到图层之前,指定动画属性的键路径,接着设置动画参数:动画的开始值与结束值,改变持续时间,或任何其他动画参数。为了执行一个动画,你使用addAnimation:forKey:方法将动画对象添加到你想要展现动画的图层上。

CABasicAnimation* fadeAnimation = [CABasicAnimation animationWithKeyPath:@”opacity”;
fadeAnimation.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnimation.toValue = [NSNumber numberWithFloat:0.0];
fadeAnimation.duration = 1.0;
[theLayer addAnimation:fadeAnimation forKey:nil];
//改变图层实际的最后数据值
theLayer.opacity = 0.0;  // 记得更新图层树

不同于隐式动画,隐式动画会更新图层对象的值。而显示动画不会更改图层树中的数据。显示动画仅是创建了一个动画。如果想让显示动画的改变成为永久性的,必须更新图层属性。

CAAnimation

CAAnimation是所有动画对象的父类,实现CAMediaTiming协议,是一个抽象类,不能直接使用。

CAMediaTiming:负责控制动画的时间、速度和时间曲线等等,
/*
如果animation在animation group中,则beginTime就是其parent object——animation group 开始的一个偏移。eg: beginTime为5,则在group aniamtion开始之后的5s开始动画。
如果animation直接添加在layer(layer也遵守CAMediaTiming协议)beginTime同样是parent object——layer 开始的一个偏移,但layer的beginTime是一个过去的时间(可能是其被添加到layer tree的时间),不能简单设置beginTime为5,去延迟动画5s,有可能layer的beginTime加上5s也是过去的时间,当延迟一个添加到layer上的动画的时候,需要获得layer的时间 `animation.beginTime  = [layer convertTime:CACurrentMediaTime() fromLayer:nil] + delay;`
*/
@property CFTimeInterval beginTime; 
@property CFTimeInterval duration; // 动画时间
@property float speed; // 速度,如果一个动画A :duration为1秒,speed为1;而另一个动画B:duration为2秒,speed为2。则两个动画的时间是相同的。不过前提是它们的super layer相同。

/* 活动本地时间的其他偏移量。 从父时间tp转换为活动本地时间t:t =(tp-开始)*speed+timeOffset。 一种用法是通过将“speed”设置为零并将“timeOffset”设置为合适的值来“暂停”动画。 */
@property CFTimeInterval timeOffset;
@property float repeatCount; // 重复count
@property CFTimeInterval repeatDuration; 
@property BOOL autoreverses; // 如果为true,则对象在向前播放后向后播放。 

/* 
kCAFillModeRemoved 是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态
kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态
kCAFillModeBackwards 在动画开始前,只需要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始。
kCAFillModeBoth 这个其实就是上面两个的合成.动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态
*/
@property(copy) CAMediaTimingFillMode fillMode;
CAAnimation提供的属性和方法
/*
速度控制函数(CAMediaTimingFunction)
kCAMediaTimingFunctionLinear(线性):匀速,给你一个相对静态的感觉
kCAMediaTimingFunctionEaseIn(渐进):动画缓慢进入,然后加速离开
kCAMediaTimingFunctionEaseOut(渐出):动画全速进入,然后减速的到达目的地
kCAMediaTimingFunctionEaseInEaseOut(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。这个是默认的动画行为。
*/
@property(nullable, strong) CAMediaTimingFunction *timingFunction;
@property(nullable, strong) id <CAAnimationDelegate> delegate;
//默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards
@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;

@protocol CAAnimationDelegate <NSObject>
@optional
// 动画已经开始
- (void)animationDidStart:(CAAnimation *)anim;

// 当动画结束或从即图层中移除时调用。 如果有效持续时间结束时没有被删除,则“ flag”为true。
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
@end
CAAnimation子类对象的结构

CAPropertyAnimation

是CAAnimation的子类,也是个抽象类,要想创建动画对象,应该使用它的子类.

/* 创建一个新的动画对象,其“ keyPath”属性设置为“ path”。*/
+ (instancetype)animationWithKeyPath:(nullable NSString *)path;
/*
描述要设置动画的属性。
通过指定CALayer的一个属性名称为keyPath,并且对这个属性值修改,达到相应的动画效果
*/
@property(nullable, copy) NSString *keyPath;
//该属性指定该属性动画是否以当前动画效果为基础。
@property(getter=isAdditive) BOOL additive;

//该属性指定动画是否为累加效果。
@property(getter=isCumulative) BOOL cumulative;

//该属性值是一个CAValueFunction对象,该对象负责对属性改变的插值计算,系统已经提供了默认的插值计算方式,因此一般无须指定该属性。
@property(nullable, strong) CAValueFunction *valueFunction;

CABasicAnimation

/ *定义要插值的属性值的对象。
  *所有都是可选的,并且最多不能为两个。 对象类型应与要设置动画的属性的类型匹配(使用CALayer.h中描述的标准规则)。 支持的动画模式为:
  *
  *-“ fromValue”和“ toValue”均为非零。 在“ fromValue”和“ toValue”之间插值。
  *-`fromValue'和`byValue'非零。 在'fromValue'和'fromValue'加上`byValue'之间进行插值。
  *-“ byValue”和“ toValue”非零。 在“ toValue”减去“ byValue”和“ toValue”之间进行插值。
  *-`fromValue'非零。 在`fromValue'和属性的当前表示值之间进行插值。
  *-“ toValue”非零。 在渲染树中图层的当前属性值和“ toValue”之间进行插值。
  *-`byValue'非零。 在渲染树中该图层的当前属性值与加“ byValue”的值之间进行插值。 * /
@property(nullable, strong) id fromValue; // 开始插值的值。
@property(nullable, strong) id toValue;// 结束插值的值。
@property(nullable, strong) id byValue; // 相对插值的值。

CAKeyframeAnimation

//表示动画执行的各个值(各个动作)
@property(nullable, copy) NSArray *values;
/*
基于CGPoint的属性要遵循的路径(比如图层的anchorPoint属性和position属性)
。当为非nil的时候覆盖“values”属性,它默认为nil。表示以恒定的速度沿着路径进行动画,
动画沿路径进行的方式取决于CalculationMode属性中的值
*/
@property(nullable) CGPathRef path;

/* 定义动画执行过程中每个值速度,与values这个属性中的值对应。每个值是浮点数且区间为[0,1],该属性只在calculationMode属性被设置为kCAAnimationLinear,kCAAnimaitonDiscrete,kCAAnimationCubic时被使用*/
@property(nullable, copy) NSArray<NSNumber *> *keyTimes;

/*CAMediaTimingFunction(CAAnimation类中有此属性)数组,如果定义了n个关键帧,则就有n-1个CAMediaTimingFunction对象,描述的是关键帧到帧的节奏(速度)(该属性替换了继承的**timingFunction**属性)。
 */

@property(nullable, copy) NSArray<CAMediaTimingFunction *> *timingFunctions;

/* 
l calculationMode属性定义了计算动画定时的算法。该属性值会影响其他与定时相关属性的使用方式。
l 线性和曲线动画,设置为
kCAAnimationLinear:(默认值,表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算;)
CAAnimationCubic:(对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,对于曲线的形状还可以通过tensionValues,continuityValues,biasValues来进行调整,这里的主要目的是使得运行的轨迹变得圆滑;)
,属性值被用于提供定时器信息以生成动画。这些模式值让你最大化控制动画的定时器。

l 节奏动画,设置为
kCAAnimationPaced:(使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions无效)
kCAAnimationCubicPaced:(在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的.)
这些属性值不依赖由keyTimes或timingFunctions属性提供的额外定时器值。相反,定时器值被隐式地计算以提供一个常速率动画。

l 离散动画,动画的calculationMode属性被设置为kCAAnimationDiscrete(离散的,就是不进行插值计算,所有关键帧直接逐个进行显示;),该值将引起动画属性从一个关键帧跳到另一个没有任何补间动画的下一个关键帧。计算模式使用keyTimes属性值,但忽略**timingFunctions**属性。
*/
@property(copy) CAAnimationCalculationMode calculationMode;
// 动画的张力,此属性仅用于Cubic模式。正值表示较紧的曲线,而负值表示较圆的曲线。第一个值定义了第一个控制点的切线的行为,第二个值控制了第二个点的切线,依此类推。 如果未为给定的控制点指定值,则使用值0。
@property(nullable, copy) NSArray<NSNumber *> *tensionValues;
/*
动画的连续性值,此属性仅用于Cubic模式。正值会导致尖锐的角,而负值会导致倒角。 第一个值定义了第一个控制点的切线的行为,第二个值控制了第二个点的切线,依此类推。 如果未为给定的控制点指定值,则使用值0。
*/
@property(nullable, copy) NSArray<NSNumber *> *continuityValues;
/*
动画的偏斜率,此属性仅用于Cubic模式。 正值将曲线移到控制点之前,而负值将曲线移到控制点之后。 第一个值定义了第一个控制点的切线的行为,第二个值控制了第二个点的切线,依此类推。 如果未为给定的控制点指定值,则使用值0。
*/
@property(nullable, copy) NSArray<NSNumber *> *biasValues;


/*
设置path的动画的对象是否旋转以匹配路径切线。它有两个值kCAAnimationRotateAuto(自动旋转)kCAAnimationRotateAutoReverse(自动倒转)此属性的默认值为nil,它指示对象不应旋转以遵循路径。
未提供path对象时,将此属性设置为非nil值的效果是不确定的。
*/
@property(nullable, copy) CAAnimationRotationMode rotationMode;

CASpringAnimation 弹簧动画

//质量,影响图层运动时的弹簧惯性,质量越大,弹簧拉伸和压缩的幅度越大。默认为1,且设置的时候必须大于0
@property CGFloat mass;

//刚度系数(劲度系数/弹性系数),刚度系数越大,形变产生的力就越大,运动越快。默认为100,且设置的时候必须大于0
@property CGFloat stiffness;

//阻尼系数,阻止弹簧伸缩的系数,阻尼系数越大,停止越快,默认为10,且设置的时候必须大于等于0
@property CGFloat damping;

//初始速率,动画视图的初始速度大小速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反。默认为0
@property CGFloat initialVelocity;

//结算时间,返回弹簧动画到停止时的估算时间,根据当前的动画参数估算,通常弹簧动画的时间使用结算时间比较准确
@property(readonly) CFTimeInterval settlingDuration;

CATransition

/*表示其渐变的效果的类型。有4种分别为
kCATransitionFade(消退)kCATransitionMoveIn(渐入)kCATransitionPush(推动)kCATransitionReveal(揭开)等不同的效果
Defaults to `fade'. */
@property(copy) CATransitionType type;
//此类用于过渡运动方向的转变且分为上下左右四种:
//kCATransitionFromRight、kCATransitionFromLeft、kCATransitionFromTop、kCATransitionFromBottom
@property(nullable, copy) CATransitionSubtype subtype;
// 表示渐变开始的数值且其大小区间为[0,1],注意它必须小于或等于endProgress
// 例如,要在过渡进行到一半时开始过渡,请将startProgress设置为0.5。
@property float startProgress;
//该值必须大于或等于startProgress,并且不大于1.0。 如果endProgress小于startProgress,则行为未定义。 默认值为1.0。
@property float endProgress;

CAAnimationGroup

@property(nullable, copy) NSArray<CAAnimation *> *animations;

同时进行多个动画。
如果你想同时给一个图层对象应用多个动画,你可以使用CAAnimationGroup对象将这些动画放在一个组里。通过使用单独的配置点,使用组对象简化了对多个动画对象的管理。应用于动画组的定时器和持续值将使用相同的值覆盖单个动画对象。

CATransaction

对于分组动画的更高级的方式是使用一个事务对象。事务提供了更加灵活的方式,允许你创建内嵌的动画集合以及给每一个动画赋值不同的动画参数。

对图层属性的每次更改都是事务的一部分。CATransaction类管理动画的创建和分组并在适当的时间执行动画。在大部分情况下,不需要创建事务。无论什么时候,给图层添加显式或隐式动画,Core Animation会自动创建一个隐式事务。然而你也可以创建显式事务以能够更精确的管理动画。

+ (void)begin; // 开始事务
+ (void)commit; // 提交事务

// 提交任何现存的隐式事务。
// 因为隐式事务在runloop即将休眠时自动 flush 提交,如果没有runloop,则此方法手动调用
//应该避免显式调用flush。 通过runloop调用,程序获得更好的性能,原子性得保留,并且事务之间进行事务处理的事务和动画将继续起作用。
/* 
    使用场景
    self.view.backgroundColor = [UIColor blueColor];
    [CATransaction begin];
    self.view.backgroundColor = [UIColor redColor];
    [CATransaction commit];
    [CATransaction flush]
    while (YES) {}  // 模拟耗时操作
*/

+ (void)flush;

/* 锁定和解锁全局锁。该锁是递归自旋锁(即不应长时间保持)。*/
+ (void)lock;
+ (void)unlock;

/*  定义添加到图层的动画的默认持续时间。 默认为1 / 4s。*/
+ (CFTimeInterval)animationDuration;
+ (void)setAnimationDuration:(CFTimeInterval)dur;

/* 
默认值为nil,当设置为非nil值时,添加到图层的所有动画都将此值设置为其“ timingFunction”属性。 
*/

+ (nullable CAMediaTimingFunction *)animationTimingFunction;
+ (void)setAnimationTimingFunction:(nullable CAMediaTimingFunction *)function;

/* 
定义是否使用图层的-actionForKey:方法为每个图层属性更改查找动作(也称为隐式动画)。 默认为否,即启用了隐式动画。
*/
+ (BOOL)disableActions;
+ (void)setDisableActions:(BOOL)flag;
/*
一旦设置为非nil值,就保证此事务组随后添加的所有动画都已完成(或被删除),就立即调用该块(在主线程上)。 如果在提交当前CATransaction之前未添加任何动画(或将完成块设置为其他值),则将立即调用该块。 
*/
+ (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;

在你想提供不同默认值给不同的动画集合的情况下你可以内嵌事务。但是嵌套在内层的事务不会立即commit,会在最外层的事务结束后统一commit。

动画的停止

动画通常直到运行结束才会停止,但是你也可以根据需要提前停止动画:

  1. 调用图层的removeAnimationForKey:方法移除你的动画对象。该方法使用的键要与调用addAnimation:forKey:方法传入的键一致。你指定的键必须不为nil。
  2. 调用图层的removeAllAnimations方法。会立即移除所有进行中的动画,并使用图层当前的状态信息重绘图层。

检测一个动画的结束

Core Animaiton提供对动画开始与结束的检测支持。这些通知是执行所有与动画相关的内务处理的最佳时刻。比如说你可能使用开始通知设置一些相关状态信息,使用对应的结束通知清理这些状态。

有两种不同的方式获取关于动画状态的通知:

  1. 使用setCompletionBlock:方法添加一个完成块给当前的事务。当事务中的所有动画完成后,事务将执行你的完成块。

  2. 给CAAnimaiton对象赋值一个代理,该代理实现了animationDidStart:方法和animaitonDidStop:finished:代理方法。

如果想将两个动画链接在一起,当第一个动画结束之后启动第二个动画。不要使用动画通知。
使用动画对象的beginTime属性在某个时间启动动画。设置第二个动画的开始时间为第一个动画的结束时间。

UIView动画

因为iOS UIView总有一个Layer,UIView类从Layer对象派生了大部分的数据。因此,对layer属性的更改会通过view对象表现出来。因此也可以使用UIVIew接口完成对图层属性的更改。

如果想使用Core Animation初始化动画,你必须在一个基于视图的动画块内部执行所有Core Animaiton调用。
UIView类默认是关闭图层动画的,在动画块之外所做的改变都不是动画,但是你可在动画块中重新启用图层动画。下面的代码,两个动画都开始于相同的时间,但透明度动画使用默认的定时,而位置动画使用动画对象指定的定时。

[UIView animateWithDuration:1.0 animations:^{
myView.layer.opacity = 0.0;
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"];
}];

动画的定时

时间系统是动画的一个重要部分。通过Core Animation方法和CAMediaTiming协议可为动画指定精确的时间信息。共有两个Core Animation类适配该协议。
其一是CAAnimation类,所以你可以在动画对象中指定时间信息。
其二是CALayer,你可以为隐式动画配置一些与时间相关的功能。虽然隐式事务对象包装了这些动画,通常优先使用所提供的默认时间信息。

当考虑时间与动画的时候,理解图层对象如何与时间工作是重要的。每一个图层都有自己用于管理动画定时的本地时间系统。
一般两个不同图层的本地时间系统足够的接近,也可以为每一个图层指定相同的时间值。
图层的本地时间可以被父图层或自己的定时参数更改。比如说,改变图层的speed属性将引起图层或子图层上动画的持续时间成比例的变化。

为了确定一个图层的适当时间值,CALayer类定义了convertTime:fromLayer:以及convertTime:toLayer:方法。你可以使用这些方法转化一个固定的时间值到一个图层的本地时间系统,或者将某一图层的时间值转换为另一个图层的时间值。

// CACurrentMediaTime函数是一个便利函数,返回计算机的当前时钟时间,该方法用于获取并转换至图层的本地时间。
CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];

一旦图层的本地时间系统中有一个时间值,可以使用该值更新与事件相关的动画对象或图层的属性。使用这些定时属性,可以完成一些有趣的动画行为,包括:

  1. 使用beginTime属性设置动画的开始时间。通常,动画开始于下一个更新循环周期。你可以使用beginTime参数延迟动画的开始几秒钟时间。该方式将链接在一起的两个动画设置某一个动画的开始时间为另一个动画的结束时间。

  2. autoreverses属性引起一个动画在指定的持续时间内执行并返回到动画的开始值。你可以将此属性与repeatCount属性联合使用,让动画在开始与结束值之间反复来回。对自动返回动画设置重复计数为一个整数(比如1.0)将引起动画停止在动画的开始值。添加一个半拍值(比如重复计数为1.5)将引起动画停止在它的结束值。

  3. 对动画组中的动画使用timeOffset属性,让动画一开始就出现在以后某时刻才会出现的状态。(一个持续时间为5秒的动画。动画将从时间0到时间5运行。如果将timeOffset设置为2,则动画将在时间2开始,到达时间5,然后“环绕”并在时间0到时间2运行。)

暂停与恢复动画

为了暂停一个动画,可以利用CAMediaTiming协议,设置图层动画的速度为0.0.设置速度为0暂停动画直到你改变该属性值为一个非零值,

-(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;
}

改变一个图层的默认隐式动画

Core Animation使用动作对象为图层实现了隐式动画行为。动作对象遵守CAAction协议并定义了一些运行于图层的相关行为。
所有CAAnimation对象都实现了这个协议。

自定义动作对象

为了创建你自己的动作对象,你需要遵守CAAction协议并实现协议里唯一的方法:runActionForKey:object:arguments:方法。在该方法中执行在图层上的动作。你可能使用该方法给图层添加动画对象或使用该方法执行另外的任务。

当定义了一个动作对象,必须决定动作以何种方式被触发。动作的触发器定义了你用于注册动作的key。动作对象可在下面的情况下被触发:

  1. 图层的某一个属性值被改变。这可以是图层的任何一个属性,不仅仅是可动画的属性。 (你也可以给添加到图层的自定义属性关联动作。)识别动作的键是属性名。

  2. 图层变成可视或被加入到图层层次中。则识别动作的键为kCAOnOrderIn。

  3. 图层从图层层次中被移除。则识别动作的键为kCAOnOrderOut。

  4. 图层是即将包含一个变换动画。则识别动作的键为kCATransition。

@interface CustomAction : NSObject<CAAction>
@property (nonatomic) CGColorRef currentColor;
@end
@implementation CustomAction
- (void)runActionForKey:(NSString *)key object:(id)anObject arguments:(NSDictionary *)dict {
    CustomLayer *layer = anObject;
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
    animation.fromValue = (id)[UIColor greenColor].CGColor;
    animation.toValue = (id)[UIColor redColor].CGColor;
    animation.duration = 5;
    [layer addAnimation:animation forKey:@"backgroundColor"];
}
@end

动作对象设置给图层才产生效果

在动作被执行之前,图层需要找到相应的动作对象。相关的动作的键可以是被更改的属性名或一个特殊的识别动作的字符串。
当事件发生在图层上,图层调用它的actionForKey:方法搜索与键关联的动作对象。我们可以介入,并提供一个与键相关的动作对象。
Core Animation以下面的顺序搜索动作对象:

  1. 如果图层有一个代理,并且代理实现了actionForLayer:forKey:方法,图层调用该方法。代理必须完成下面所述操作之一:
    l 返回给定的键指定的动作对象
    l 如果代理不处理动作则返回nil,而搜索操作将继续。
    l 返回NSNull对象,这将引起搜索操作立即结束。CALayer不会做任何动画;
  2. 图层在图层的action字典内搜索给定的键
  3. 图层在style字典中查询一个包含键的动作字典。(换句话说,style字典包含一个actions键,它的值也是字典。图层在第二个字典中搜索给定的键。)
  4. 图层调用它的defaultActionForKey:类方法。 图层执行由Core Animation定义的隐式动作(如果有)。

如果任何一个搜索点提供了动作对象,图层将停止搜索并执行返回对象的runActionForKey:object:arguments:方法执行动作。
如果动作是一个CAAnimation对象,则把自身添加到layer上,使用默认的方法实现执行动画。如果是一个正确的实现了CAAction协议的对象,则CALayer用这个对象来生成一个CAAnimation,并加到自己身上进行动画。
CAAnimation

CALayer可动画属性

属性 默认动画
anchorPoint 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
backgroundColor 使用默认的隐式CABasicAnimation对象,描述于Table B-2。
backgroundFilters 使用默认的CATransition对象,描述于Table B-3。滤镜的字属性被动画使用默认隐式CABasicAnimation对象,描述于Table B-2。
borderColor 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
borderWidth 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
bounds 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
compositingFilter 使用默认的CATransition对象,描述于Table B-3。滤镜的字属性被动画使用默认隐式CABasicAnimation对象,描述于Table B-2。
contents 使用默认隐式的CABasicAnimation对象,描述于Table B-2)。
contentsRect 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
cornerRadius 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
doubleSided 没有默认的隐式动画
filters 使用默认的CATransition对象,描述于Table B-3。滤镜的字属性被动画使用默认隐式CABasicAnimation对象,描述于Table B-2。
frame 该属性是不可动画的。你可以动画bounds和position属性达到相同的效果。
hidden 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
mask 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
masksToBounds 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
opacity 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
position 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
shadowColor 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
shadowOffset 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
shadowOpacity 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
shadowPath 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
shadowRadius 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
sublayers 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
sublayerTransform 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
transform 使用默认隐式的CABasicAnimation对象,描述于Table B-2。
zPosition 使用默认隐式的CABasicAnimation对象,描述于Table B-2。

表B-2 默认隐式的基本动画

描述
CABasicAnimation
持续时间 0.25秒,或是当前事务的持续时间
键路径 设置图层的属性名

表B-3 默认隐式过渡

描述
CATransition
持续时间 0.25秒,或当前事务的持续时间
类型 减淡(kCATransitionFade)
开始进度 0.0
结束进度 1.0

CATransform3D键路径

可以使用键路径检索一个CATransform3D数据类型的属性的变换值。可使用字符串值transform或sublayerTransform,后面跟上表C-2中中的域作为键路径。比如,transform.rotation.z。

表C-2 变换域的键路径

域的键路径 描述
rotation.x 设置一个NSNumber类型的绕x方向旋转的弧度值。
rotation.y 设置一个NSNumber类型的绕y方向旋转的弧度值。
rotation.z 设置一个NSNumber类型的绕z方向旋转的弧度值。
rotation 设置一个NSNumber类型的绕z方向旋转的弧度值。该设置和ratation.z是功能是一样的。
scale.x 设置一个NSNumber对象,它的值是在x轴上缩放因子。
scale.y 设置一个NSNumber对象,它的值是在y轴上缩放因子。
scale.z 设置一个NSNumber对象,它的值是在z轴上缩放因子。
scale 设置一个NSNumber对象,它的值是三个缩放因子的平均值。
translation.x 设置一个NSNumber对象,它的值是x轴方向的平移因子。
translation.y 设置一个NSNumber对象,它的值是y轴方向的平移因子。
translation.z 设置一个NSNumber对象,它的值是z轴方向的平移因子。
translation 设置一个包含NSSize或CGSize数据类型的NSValue对象。该数据类型表明在x和y轴上的平移数量。
//设置x轴的平移因子为10点,图层将按指定的坐标轴方向平移给定的点数量。
[myLayer setValue:[NSNumber numberWithFloat:10.0] forKeyPath:@”transform.translation.x”];

CGPoint键路径

如果属性的值是CGPoint数据类型,你可以选择表C-3其中的域名追加给属性以获得值或设置值 。
表C-3 CGPoint数据结构

结构域 描述
x 一个点的x坐标。
y 一个点的y坐标。

CGSize键路径

如果属性值是一个CGSize数据类型,你可以选择表C-4中其中一个域名追加给属性以获得值或设置值。

表C-4 CGSize数据结构域

结构域 描述
width size的width属性。
height size的height属性。

CGRect键路径

如果给定的属性是CGRect数据类型值,你可以选择表C-5中其中一个域名追加给属性以获取或设置值。

表C-5 CGRect数据结构的域名

结构体域 描述
orign 矩形的原始点
origin.x 矩形原始点的x坐标
origin.y 矩形原始点的y坐标
size 矩形的尺寸
size.width 矩形尺寸的宽度属性
size.height 矩形尺寸的高度属性

UIView动画

UIView动画是iOS开发中最廉价也是最常用的动画。
UIView动画能够设置的动画属性有:

UIView动画使用方式

类似于事务的方式(在iOS13 废弃了)

创建动画

// 第一个参数: 动画标识
// 第二个参数: 附加参数,在设置代理情况下,
//此参数将发送到setAnimationWillStartSelector和setAnimationDidStopSelector所指定的方法,
//大部分情况,设置为nil.
[UIView beginAnimations:(nullable NSString *) context:(nullable void *)];

提交动画

[UIView commitAnimations];

相关方法

//动画持续时间
[UIView setAnimationDuration:(NSTimeInterval)];
//动画的代理对象 
[UIView setAnimationDelegate:(nullable id)];
//设置动画将开始时代理对象执行的SEL
[UIView setAnimationWillStartSelector:(nullable SEL)];
//设置动画延迟执行的时间
[UIView setAnimationDelay:(NSTimeInterval)];
//设置动画的重复次数
[UIView setAnimationRepeatCount:(float)];
//设置动画的曲线
/*
UIViewAnimationCurve的枚举值:
UIViewAnimationCurveEaseInOut,         // 慢进慢出(默认值)
UIViewAnimationCurveEaseIn,            // 慢进
UIViewAnimationCurveEaseOut,           // 慢出
UIViewAnimationCurveLinear             // 匀速
*/
[UIView setAnimationCurve:(UIViewAnimationCurve)];
//设置是否从当前状态开始播放动画
/*假设上一个动画正在播放,且尚未播放完毕,我们将要进行一个新的动画:
当为YES时:动画将从上一个动画所在的状态开始播放
当为NO时:动画将从上一个动画所指定的最终状态开始播放(此时上一个动画马上结束)*/
[UIView setAnimationBeginsFromCurrentState:YES];
//设置动画是否继续执行相反的动画
[UIView setAnimationRepeatAutoreverses:(BOOL)];
//是否禁用动画效果(对象属性依然会被改变,只是没有动画效果)
[UIView setAnimationsEnabled:(BOOL)];
//设置视图的过渡效果
/* 第一个参数:UIViewAnimationTransition的枚举值如下
     UIViewAnimationTransitionNone,              //不使用动画
     UIViewAnimationTransitionFlipFromLeft,      //从左向右旋转翻页
     UIViewAnimationTransitionFlipFromRight,     //从右向左旋转翻页
     UIViewAnimationTransitionCurlUp,            //从下往上卷曲翻页
     UIViewAnimationTransitionCurlDown,          //从上往下卷曲翻页
 第二个参数:需要过渡效果的View
 第三个参数:是否使用视图缓存,YES:视图在开始和结束时渲染一次;NO:视图在每一帧都渲染*/
[UIView setAnimationTransition:(UIViewAnimationTransition) forView:(nonnull UIView *) cache:(BOOL)];
Block 动画
@interface UIView(UIViewAnimation)
// 动画是否可用
+ (void)setAnimationsEnabled:(BOOL)enabled; 
@property(class, nonatomic, readonly) BOOL areAnimationsEnabled;
// 在没有动画的情况下执行block里的代码。
+ (void)performWithoutAnimation:(void (NS_NOESCAPE ^)(void))actionsWithoutAnimation API_AVAILABLE(ios(7.0));
//当前动画的持续时间。?
@property(class, nonatomic, readonly) NSTimeInterval inheritedAnimationDuration API_AVAILABLE(ios(9.0));

@end

@interface UIView(UIViewAnimationWithBlocks)
/*
 UIViewAnimationOptionLayoutSubviews            //进行动画时布局子控件
 UIViewAnimationOptionAllowUserInteraction      //进行动画时允许用户交互
 UIViewAnimationOptionBeginFromCurrentState     //从当前状态开始动画
 UIViewAnimationOptionRepeat                    //无限重复执行动画
 UIViewAnimationOptionAutoreverse               //执行动画回路
 UIViewAnimationOptionOverrideInheritedDuration //忽略嵌套动画的执行时间设置
 UIViewAnimationOptionOverrideInheritedCurve    //忽略嵌套动画的曲线设置
 UIViewAnimationOptionAllowAnimatedContent      //转场:进行动画时重绘视图
 UIViewAnimationOptionShowHideTransitionViews   //转场:移除(添加和移除图层的)动画效果
 UIViewAnimationOptionOverrideInheritedOptions  //不继承父动画设置

 UIViewAnimationOptionCurveEaseInOut            //时间曲线,慢进慢出(默认值)
 UIViewAnimationOptionCurveEaseIn               //时间曲线,慢进
 UIViewAnimationOptionCurveEaseOut              //时间曲线,慢出
 UIViewAnimationOptionCurveLinear               //时间曲线,匀速

 UIViewAnimationOptionTransitionNone            //转场,不使用动画
 UIViewAnimationOptionTransitionFlipFromLeft    //转场,从左向右旋转翻页
 UIViewAnimationOptionTransitionFlipFromRight   //转场,从右向左旋转翻页
 UIViewAnimationOptionTransitionCurlUp          //转场,下往上卷曲翻页
 UIViewAnimationOptionTransitionCurlDown        //转场,从上往下卷曲翻页
 UIViewAnimationOptionTransitionCrossDissolve   //转场,交叉消失和出现
 UIViewAnimationOptionTransitionFlipFromTop     //转场,从上向下旋转翻页
 UIViewAnimationOptionTransitionFlipFromBottom  //转场,从下向上旋转翻页
*/
// 可以设置延时时间和过渡效果的Block动画
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion API_AVAILABLE(ios(4.0));
// 带有动画提交回调的Block动画
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion API_AVAILABLE(ios(4.0)); // delay = 0.0, options = 0

// 最简洁的Block动画:包含时间和动画:
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations API_AVAILABLE(ios(4.0)); // delay = 0.0, options = 0, completion = NULL

// 弹簧动画
// 参数依次是
//动画持续时间
//动画延迟执行的时间
//震动效果,范围0~1,数值越小震动效果越明显
//初始速度,数值越大初始速度越快
//动画的过渡效果
//执行的动画
//动画执行提交后的操作
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion API_AVAILABLE(ios(7.0));

// 转场动画 
// 单个视图的过渡效果
+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion API_AVAILABLE(ios(4.0));

/* 
toView已添加到fromView.superview,fromView已从其超级视图中删除注意转场动画的作用对象是父视图(过渡效果体现在父视图上)。调用该方法相当于执行下面两句代码:
[fromView.superview addSubview:toView];
[fromView removeFromSuperview];
*/
+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion API_AVAILABLE(ios(4.0));

/* 在一个或多个视图上执行请求的系统提供的动画。 在parallelAnimations块中指定其他动画。 这些附加动画将与系统动画一起运行,并具有与系统动画定义/继承相同的时间和持续时间。 其他动画不应修改在其上执行系统动画的视图的属性。 并非所有的系统动画支持所有的options。
 */
+ (void)performSystemAnimation:(UISystemAnimation)animation onViews:(NSArray<__kindof UIView *> *)views options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))parallelAnimations completion:(void (^ __nullable)(BOOL finished))completion API_AVAILABLE(ios(7.0));

/* 
从动画块中调用此方法以重复动画,否则无效。 重复动画的总持续时间可以通过(outerAnimationDuration * repeatCount * autoreverses?2:1)计算。
 */
+ (void)modifyAnimationsWithRepeatCount:(CGFloat)count autoreverses:(BOOL)autoreverses animations:(void(NS_NOESCAPE ^)(void))animations API_AVAILABLE(ios(12.0),tvos(12.0));

@end

@interface UIView (UIViewKeyframeAnimations)
/*
UIViewAnimationOptionLayoutSubviews           //进行动画时布局子控件
UIViewAnimationOptionAllowUserInteraction     //进行动画时允许用户交互
UIViewAnimationOptionBeginFromCurrentState    //从当前状态开始动画
UIViewAnimationOptionRepeat                   //无限重复执行动画
UIViewAnimationOptionAutoreverse              //执行动画回路
UIViewAnimationOptionOverrideInheritedDuration //忽略嵌套动画的执行时间设置
UIViewAnimationOptionOverrideInheritedOptions //不继承父动画设置

UIViewKeyframeAnimationOptionCalculationModeLinear     //运算模式 :连续
UIViewKeyframeAnimationOptionCalculationModeDiscrete   //运算模式 :离散
UIViewKeyframeAnimationOptionCalculationModePaced      //运算模式 :均匀执行
UIViewKeyframeAnimationOptionCalculationModeCubic      //运算模式 :平滑
UIViewKeyframeAnimationOptionCalculationModeCubicPaced //运算模式 :平滑均匀
*/
// 关键帧动画,支持属性关键帧,不支持路径关键帧
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion API_AVAILABLE(ios(7.0));
//动画开始的时间(占总时间的比例),
// 动画持续时间(占总时间的比例)
+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations API_AVAILABLE(ios(7.0)); // start time and duration are values between 0.0 and 1.0 specifying time and duration relative to the overall time of the keyframe animation

@end

UIView 动画原理

之前已经介绍过,隐式动画的执行过程,是通过找到一个 实现了 CAAction协议的类对象,调用协议方法实现的。
当直接修改View的属性,也会反馈到layer上,但并没有动画,是因为,view是layer的代理,返回了NSNull对象,不会产生动画
当使用UIView提供的api进行动画时,会返回一个Action对象,然后该对象添加了动画。

/** UIView 动画产生原理 */
- (void)uiviewAnimation {
    NSLog(@"%@",[self.view.layer.delegate actionForLayer:self.view.layer forKey:@"position"]); // <null>

    [UIView animateWithDuration:1.25 animations:^{
        NSLog(@"%@",[self.view.layer.delegate actionForLayer:self.view.layer forKey:@"position"]); // <_UIViewAdditiveAnimationAction: 0x600001ff0d40>
    }];
}

UIViewController转场动画

简单描述下UIViewController转场动画

自定义转场动画

在 Navigation Controller 中,实现一个自定义的 push 动画效果。为了完成这个任务,需要实现 UINavigationControllerDelegate 中的方法:

//可以根据不同的 operation(Push 或 Pop)返回不同的 animator。我们可以把 animator 存到一个属性中,从而在多个 operation 之间实现共享,或者我们也可以为每个 operation 都创建一个新的 animator 对象
- (id<UIViewControllerAnimatedTransitioning>)
                   navigationController:(UINavigationController *)navigationController
        animationControllerForOperation:(UINavigationControllerOperation)operation
                     fromViewController:(UIViewController*)fromVC
                       toViewController:(UIViewController*)toVC
{
    if (operation == UINavigationControllerOperationPush) {
        return self.animator;
    }
    return nil;
}

我们需要创建遵守UIViewControllerAnimatedTransitioning协议的对象。

@protocol UIViewControllerAnimatedTransitioning <NSObject>

//询问动画对象过渡动画的持续时间(以秒为单位)。
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
/*
在呈现或关闭视图控制器时,会调用此方法。可以配置与自定义过渡相关联的动画。您可以使用UIView动画或“核心动画”来配置动画。
所有动画都必须在transitionContext的containerView属性指定的视图中进行。
将要显示的视图添加到容器视图的层次结构中,并设置您想要使该视图移动到位的所有动画。
如果要在没有视图的情况下直接绘制到屏幕,请使用此方法配置CADisplayLink对象。
您可以从transitionContext的viewControllerForKey:方法检索与转换有关的视图控制器。
*/
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;

@optional

//如果转场可以被中断,则实现此方法(例如UIViewPropertyAnimator对象)
- (id <UIViewImplicitlyAnimating>) interruptibleAnimatorForTransition:(id <UIViewControllerContextTransitioning>)transitionContext API_AVAILABLE(ios(10.0));

// 这是一种便利,如果在调用过渡上下文的completeTransition:方法时实现,则将由系统调用该实现。
- (void)animationEnded:(BOOL) transitionCompleted;

@end

系统调用上述协议的时候,参数是一个遵守UIViewControllerContextTransitioning协议的上下文对象

@protocol UIViewControllerContextTransitioning <NSObject>

// 容器视图充当动画序列期间所有其他视图(包括消失和呈现的视图控制器的视图)的父视图。
@property(nonatomic, readonly) UIView *containerView;
//允许自定义过渡向容器视图添加或删除子视图。对于除UIModalPresentationCustom样式以外的模态表示样式,此属性的值始终为YES。 
// 当模式表示样式为UIModalPresentationCustom时,如果过渡应设置动画,则值为YES;否则,则为NO。 
// 使用此值可以确定是否需要对自定义过渡进行动画处理,或者是否应在不对更改进行动画处理的情况下将最终视图安装到容器中。
@property(nonatomic, readonly, getter=isAnimated) BOOL animated;

// 这表明过渡当前是否是交互式的。
@property(nonatomic, readonly, getter=isInteractive) BOOL interactive; 
// 转场是否取消
@property(nonatomic, readonly) BOOL transitionWasCancelled;
// 控制器过渡的表示样式。
@property(nonatomic, readonly) UIModalPresentationStyle presentationStyle;

// 更新
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
 // 完成
- (void)finishInteractiveTransition;
// 取消
- (void)cancelInteractiveTransition;
// 暂停
- (void)pauseInteractiveTransition API_AVAILABLE(ios(10.0));

// 每当转换完成(或取消转换)时,都必须调用此方法。
// 通常,此方法由符合由转换委托售卖的UIViewControllerAnimatedTransitioning协议的对象调用。 对于纯交互式过渡,应由交互控制器调用。 
- (void)completeTransition:(BOOL)didComplete;


//目前,只有两个键
//-UITransitionContextToViewControllerKey,
// UITransitionContextFromViewControllerKey。
//不应操纵视图,而应使用viewForKey:获取视图。
- (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key;

//目前,系统仅定义了两个键-
// UITransitionContextFromViewKey和UITransitionContextToViewKey
// 可能返回nil,这表明不应该操作关联的视图控制器的视图。
- (nullable __kindof UIView *)viewForKey:(UITransitionContextViewKey)key API_AVAILABLE(ios(8.0));
// 指示在过渡期间要应用的旋转量。
@property(nonatomic, readonly) CGAffineTransform targetTransform API_AVAILABLE(ios(8.0));

/*
此方法返回的矩形代表转换开始时相应视图的大小。 对于已经在屏幕上的视图控制器,此矩形通常与容器视图的框架矩形匹配。 对于即将显示的视图控制器,此方法返回的值通常为CGRectZero,因为视图尚未显示在屏幕上。
*/
- (CGRect)initialFrameForViewController:(UIViewController *)vc;
/*
此方法返回的矩形表示转换结束时相应视图的大小。 对于演示过程中涉及的视图,此方法返回的值可能是CGRectZero,但也可能是有效的框架矩形。
*/
- (CGRect)finalFrameForViewController:(UIViewController *)vc;
@end

交互式的转场动画

想要动画变地可以交互非常简单,我们只需要覆盖另一个 UINavigationControllerDelegate 的方法:

- (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController*)navigationController
                          interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>)animationController
{
    return self.interactionController;
}

注意,在非交互式动画效果中,该方法返回 nil。
这里返回的 interaction controller 是 UIPercentDrivenInteractiveTransition 类的一个实例。
而关于UIViewControllerInteractiveTransitioning

@protocol UIViewControllerInteractiveTransitioning <NSObject>
/*
此方法的实现应使用transitionContext参数中的数据为过渡配置用户交互性,然后启动动画。 在跟踪用户互动时,应定期调用上下文对象的updateInteractiveTransition:方法,以报告现在完成了多少转换。 
如果事件指示用户已取消转换,请调用cancelInteractiveTransition方法。 如果事件指示过渡已完成,请调用finishInteractiveTransition方法。
*/
- (void)startInteractiveTransition:(id <UIViewControllerContextTransitioning>)transitionContext;

@optional
// 在交互部分完成后,系统需要完成交互转换的速度时调用。
@property(nonatomic, readonly) CGFloat completionSpeed;
// 当系统需要动画完成曲线以进行交互式视图控制器转换时调用
@property(nonatomic, readonly) UIViewAnimationCurve completionCurve;
/*
当过渡从开始就具有交互性时,此属性的值为YES。 当过渡以非交互方式开始时,该属性为NO。 但是,即使过渡开始时是非交互的,如果它实现UIViewControllerAnimatedTransitioning协议的interruptibleAnimatorForTransition:方法,则以后也可能变为交互式。
*/
@property (nonatomic, readonly) BOOL wantsInteractiveStart API_AVAILABLE(ios(10.0));

@end

UIPercentDrivenInteractiveTransition

UIKIT_EXTERN API_AVAILABLE(ios(7.0)) @interface UIPercentDrivenInteractiveTransition : NSObject <UIViewControllerInteractiveTransitioning>

// 这是在过渡开始时调用<UIViewControllerAnimatedTransitioning>`transitionDuration:`方法时返回的非交互持续时间。
@property (readonly) CGFloat duration;

/// 由updateInteractiveTransition指定的最后一个percentComplete值:
@property (readonly) CGFloat percentComplete;

//过渡动画的速度。
/*
此属性的默认值为1.0,产生一个实时进行的动画。 可以更改此值以加快或减缓过渡中的动画。 例如,在过渡结束时或取消动画时更改动画速度,在这种情况下,您将在停止跟踪用户事件并要调用cancelInteractiveTransition或finishInteractiveTransition方法时设置速度。
速度是当前动画速度的乘数,因此,大于1.0的值会使动画加速,而小于1.0的值会使动画减速。 此属性中的值必须始终大于0.0。
*/
@property (nonatomic,assign) CGFloat completionSpeed;

//指示交互式过渡的动画完成曲线。
@property (nonatomic,assign) UIViewAnimationCurve completionCurve;

/*
驱动动画时要使用的时序曲线。
对于可中断的动画,可以指定在继续过渡时要使用的其他时序曲线提供者。 
*/
@property (nullable, nonatomic, strong)id <UITimingCurveProvider> timingCurve API_AVAILABLE(ios(10.0));
// 一个布尔值,指示动画最初是否是交互式的。
//当此属性的值为YES时,交互式动画将暂停播放,从而使您可以从头开始驱动动画。 当您想不交互地开始动画时,可以将此属性设置为NO。 
@property (nonatomic) BOOL wantsInteractiveStart API_AVAILABLE(ios(10.0));

/*
这是调用上下文对象的pauseInteractiveTransition方法的便捷方法。 
您可以调用此方法,以便可以开始交互式驱动动画。 
例如,当用户的手指触摸屏幕时,手势处理程序将调用此方法以停止动画,然后使用对触摸位置的更改来更新percentComplete属性。
*/
- (void)pauseInteractiveTransition API_AVAILABLE(ios(10.0));


/*
调用上下文对象的updateInteractiveTransition:方法的便捷方法。
在跟踪用户事件时,代码应定期调用此方法以更新当前进度以完成过渡。
如果在跟踪过程中,交互作用超过了您认为表示过渡已完成或取消的阈值,停止跟踪事件并调用finishInteractiveTransition或cancelInteractiveTransition方法。
*/
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
/*
调用上下文对象的cancelInteractiveTransition方法的便捷方法。
在跟踪用户交互时,当交互提示用户希望取消或中止视图控制器转换时,手势识别器或事件处理代码将调用此方法。 
例如,如果用户反转了滑动方向,然后触摸事件结束,建议用户决定是否进行过渡,则可以调用此方法。
*/
- (void)cancelInteractiveTransition;
/*
这是调用上下文对象的finishInteractiveTransition方法的便捷方法。
在跟踪用户交互时,如果交互表明转换已完成,则手势识别器或事件处理代码应调用此方法。
 例如,如果用户滑动手指,并且触摸事件指示滑动距离超过了完成手势所需的阈值,则在相应的触摸事件结束时调用此方法,以使系统知道它现在可以完成过渡。
*/
- (void)finishInteractiveTransition;

@end

参考文章
https://blog.csdn.net/hello_hwc/article/details/42580773

上一篇下一篇

猜你喜欢

热点阅读