Pop 是 iOS,tvOS 和 OS X 的可扩展动画引擎。除了基本的静态动画外,他支持弹性和衰减动画动态动画,使其可用于构建逼真的基于物理学的交互。API 允许与现有的 Objective-C 或 Swift 代码库快速集成,并支持 NSObject 上任何属性的动画。它是一个成熟并且经过良好测试的框架。
本文主要着重介绍 Pop 库的使用,并结合实例作相关动画的演示。
简单示例
我们先看来一下一个简单的示例:移动一个视图的 center。
// 选择动画类型,并指定需要动画的视图view属性或图层layer属性
POPBasicAnimation* animation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewCenter];
// 指定属性的新值
animation.toValue = [NSValue valueWithCGPoint:self.view.center];
// 设置动画的事件回调
animation.delegate = self;
// 向视图view或图层layer添加动画
[self.blueView pop_addAnimation:animation forKey:@"popViewCenterAnimation"];
通过上面的例子,我们发现使用 pop 作动画的过程和 Core Animation 没多少区别,这让我们很容易就能上手。另外,我们注意到,使用 pop 主要有以下几个步骤:
- 选择动画类型,并指定需动画的属性
- 执行属性的新值
- 设置动画事件代理(可选)
- 应用动画
动画介绍
Pop 是使用 Objective-C++ 编写的,该语言是对 C++ 的扩展,就像Objective-C 是 C 的扩展。而至于为什么他们用 Objective-C++ 而不是纯粹的 Objective-C,原因是他们更喜欢 Objective-C++的语法特性所提供的便利。
Pop 支持四种动画类型:
- POPBasicAnimation(基础动画)
- POPSpringAnimation(弹性动画)
- POPDecayAnimation(衰减动画)
- POPCustomAnimation(自定义动画)
我们知道 Core Animation 针对的图层级别的变换,并且变化之后视图的 frame 以及图层的 frame 都不会变化(即使设置 removedOnCompletion 设置为NO,不移除动画,fillMode 填充模式设置为 kCAFillModeForwards)。和 Core Animation 不同的,Pop 所做的动画都真实的改变了 frame,并且都会保持最后一帧的位置。另外一个不同点是,Pop 直接列举了所有可以做的属性动画,并且所针对的对象不再只是图层 CALayer,是视图 UIView 也进行了支持。
动画结构
基础动画
在文章开始的例子就使用了基础动画。这里介绍一下其中的属性和方法。
POPBasicAnimation
- 初始化
POPBasicAnimation 提供了多种初始化基础动画对象的方法,其中包括无任何设置的动画对象方法、指定动画属性的方法、指定时间速率的方法:
+animation
+animationWithPropertyNamed:
+defaultAnimation
+linearAnimation
+easeInAnimation
+easeOutAnimation
+easeInEaseOutAnimation
duration
动画执行的时间,单位为秒,默认为 0.5 秒。
timingFunction
时间速率,是系统的 CAMediaTimingFunction 类型,支持系统提供的五种类型,对应上面的快捷创建方式。通常用在指定动画属性之后来指定运行的速率。
POPPropertyAnimation 和 POPAnimation
此部分是几种动画的父类,在基础动画、弹性动画和衰减动画中都可以使用。
-
property
:用来设置动画属性 -
fromValue
:动画的起始值 -
toValue
:动画的起始值 -
name
:动画的名称 -
beginTime
:和核心动画中一样,动画的开始时间 -
delegate
:动画过程事件的代理,后面会提到 -
tracer
:跟踪器,收集动画信息,调试时使用的。 -
removedOnCompletion
:和核心动画中一样,动画结束是否移除动画 -
autoreverses
:反转动画 -
repeatCount
:重复次数,0和1表示不重复 -
repeatForever
:无限次的重复,设置为YES,动画不会执行完成事件
示例:我们来继续之前移动视图的例子,不同的是我们设置了时间、速率曲线、重复等效果,并且特地使用核心动画写了一个类似的对比动画。
// pop 动画
POPBasicAnimation* animation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerPosition];
animation.toValue = [NSValue valueWithCGPoint:self.view.center];
// 设置动画时间
animation.duration = 1.0;
// 设置动画速率
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
// 设置重复次数
animation.repeatForever = YES;
// 设置动画反转
animation.autoreverses = YES;
[self.blueView.layer pop_addAnimation:animation forKey:@"POPBasicAnimation"];
// Core Animation 动画
CABasicAnimation* ani = [CABasicAnimation animationWithKeyPath:@"position"];
ani.toValue = [NSValue valueWithCGPoint:CGPointMake(self.view.center.x, self.view.center.y+60)];
ani.duration = 1.0;
ani.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
ani.repeatCount = MAXFLOAT;
ani.autoreverses = YES;
[self.blueView2.layer addAnimation:ani forKey:@"CABasicAnimation"];
可以看出,Pop 动画和核心动画在相同的时间速率曲线设置下,运行起来效果还是有细微的差别的。对比了其他几个速率类型,个人觉得还是核心动画的效果要好。
弹性动画
POPSpringAnimation 可以用来做弹性动画,和基础动画不同,弹性动画不能指定速率曲线,也没有动画时间一说,因为弹性动画是由初始速度、弹性能力等多个参数决定的其效果的。
-
velocity
:当前的速度值 -
springBounciness
:有效弹性,值会被转换为相应的动力学常数。较高的值会增加弹簧移动范围,从而产生更多的振荡和弹性。定义为[0,20]范围内的值。 默认为4。 -
springSpeed
:有效速度,较高的值会增加弹簧的阻尼能力,从而导致更快的初始速度和更快的弹跳速度。定义为[0,20]范围内的值。默认为12。 -
dynamicsTension
:动力学中使用的张力。可以在弹跳和速度上使用,以便更精细地调整动画效果。 -
dynamicsFriction
:动力学中使用的摩擦力。可以在弹跳和速度上使用,以便更精细地调整动画效果。 -
dynamicsMass
:动力学中使用的质量。可以在弹跳和速度上使用,以便更精细地调整动画效果。
示例:
POPSpringAnimation* animation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionX];
animation.toValue = @(self.view.center.x);
// 设置弹力
animation.springBounciness = 20.0f;
[self.blueView.layer pop_addAnimation:animation forKey:@"POPSpringAnimation"];
衰减动画
和弹性动画一样,衰减动画同样无法指定动画的时间,因为动画是根据初始速度和衰减因子计算得出动画时间的。另外,你也无法设置toValue
的值,因为此值是由速度和衰减因子计算得出来的,不过,你可以设置fromValue
指定动画的起点。
-
velocity
:只读,动画的初始速度,此速度在动画执行期间会不断变换 -
originalVelocity
:因为velocity
是不断变化的,因此 pop 提供了一个原始速度,记录着动画的初始速度 -
deceleration
:衰减因子,范围[0,1],默认值为0.998 -
duration
:只读,动画时间,初始速度和衰减因子计算得出
示例:
POPDecayAnimation* animation = [POPDecayAnimation animationWithPropertyNamed:kPOPLayerPositionX];
// 设置初始速度
animation.velocity = @(300);
[self.blueView.layer pop_addAnimation:animation forKey:@"POPDecayAnimation"];
自定义动画
POPCustomAnimation 类,它基本上是一个 display link 的转换,它会在每一帧进行回调,以此让你来完成动画每一帧的过程。
POPCustomAnimation 类中有一个 POPCustomAnimationBlock 回调,它包含了所添加动画的视图和当前动画,它需要返回值,YES表示继续执行,此时你可以继续操作你的视图,而 NO 则表示动画结束,POPCustomAnimationBlock 便不会再回调。
想要自定义动画我们就需要有一个自定义的函数曲线,比如我们要实现一个弹簧动画(跟spring动画类似),我们使用如下的时间函数,输出为[0-1](更多的缓动函数可以去这查看:http://easings.net。
float ElasticEaseOut(float p)
{
return sin(-13 * M_PI_2 * (p + 1)) * pow(2, -6 * p) + 1;
}
当有了定义好的缓动曲线后,我们就可以实现自定义动画。
@interface ViewController ()
@property(assign, nonatomic)CGFloat baseTime;
@property(assign, nonatomic)CGFloat duration;
@property(assign, nonatomic)CGPoint from;
@property(assign, nonatomic)CGPoint to;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.from = self.blueView.center;
self.to = self.view.center;
self.duration = 2.0; // 动画总时长
}
float ElasticEaseOut(float p){
return sin(-13 * M_PI_2 * (p + 1)) * pow(2, -6 * p) + 1;
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
POPCustomAnimation* animation = [POPCustomAnimation animationWithBlock:^BOOL(id target, POPCustomAnimation *animation) {
//动画开始的时间,我们可以记录下来作为基准时间
if(self.baseTime == 0){
self.baseTime = animation.currentTime;
}
// 根据当前时间,计算出当前的时间进度,并根据动画周期归一化到[0-1]
double progress = (animation.currentTime - self.baseTime)/self.duration;
//使用ElasticEaseOut自定义曲线根据当前进度计算出新的值,该值大小也为[0-1]
double caculateValue = ElasticEaseOut(progress);
//根据缓动函数的输出,计算新的值,并赋给UI对象
CGPoint current = CGPointZero;
current.x = self.from.x + (self.to.x - self.from.x) * caculateValue;
current.y = self.from.y + (self.to.y - self.from.y) * caculateValue;
self.blueView.center = current;
//如果当前进度小于1,则继续动画
if(progress < 1.0){
return YES;
}
// 结束动画
return NO;
}];
[self.blueView.layer pop_addAnimation:animation forKey:@"POPCustomAnimation"];
}
可选择的动画属性
图层 CALayer | 释义 |
---|---|
kPOPLayerBackgroundColor | 背景色 |
kPOPLayerBounds | 大小 |
kPOPLayerCornerRadius | 圆角大小 |
kPOPLayerBorderWidth | 宽度 |
kPOPLayerBorderColor | 边框颜色 |
kPOPLayerOpacity | 不透明度 |
kPOPLayerPosition | 位置 |
kPOPLayerPositionX | 位置x |
kPOPLayerPositionY | 位置y |
kPOPLayerRotation | 旋转 |
kPOPLayerRotationX | X轴旋转量 |
kPOPLayerRotationY | X轴旋转量 |
kPOPLayerScaleX | X轴缩放量 |
kPOPLayerScaleXY | XY轴缩放量 |
kPOPLayerScaleY | XY轴缩放量 |
kPOPLayerSize | 宽高的大小 |
kPOPLayerSubscaleXY | |
kPOPLayerSubtranslationX | |
kPOPLayerSubtranslationXY | |
kPOPLayerSubtranslationY | |
kPOPLayerSubtranslationZ | |
kPOPLayerTranslationX | X轴平移量 |
kPOPLayerTranslationXY | XY轴平移量 |
kPOPLayerTranslationY | Y轴平移量 |
kPOPLayerTranslationZ | Z轴平移量 |
kPOPLayerZPosition | |
kPOPLayerShadowColor | 阴影颜色 |
kPOPLayerShadowOffset | 阴影偏移大小 |
kPOPLayerShadowOpacity | 阴影不透明度 |
kPOPLayerShadowRadius | 阴影的圆角 |
图层 CAShapeLayer | 释义 |
---|---|
kPOPShapeLayerStrokeStart | 起点端 |
kPOPShapeLayerStrokeEnd | 终点端 |
kPOPShapeLayerStrokeColor | 线条颜色 |
kPOPShapeLayerFillColor | 填充色 |
kPOPShapeLayerLineWidth | 线条的宽度(粗细) |
kPOPShapeLayerLineDashPhase |
NSLayoutConstraint | 释义 |
---|---|
kPOPLayoutConstraintConstant | 约束值 |
视图 UIView | 释义 |
---|---|
kPOPViewAlpha | 透明度 |
kPOPViewBackgroundColor | 背景色 |
kPOPViewBounds | 大小 |
kPOPViewCenter | 中心点 |
kPOPViewFrame | 位置和大小 |
kPOPViewScaleX | X轴的缩放量 |
kPOPViewScaleXY | XY轴的缩放量 |
kPOPViewScaleY | Y轴的缩放量 |
kPOPViewSize | 大小(比例) |
kPOPViewTintColor |
滚动视图 UIScrollView | 释义 |
---|---|
kPOPScrollViewContentOffset | 内容滚动位置 |
kPOPScrollViewContentSize | 内容大小 |
kPOPScrollViewZoomScale | 缩放大小 |
kPOPScrollViewContentInset | 内边距 |
kPOPScrollViewScrollIndicatorInsets | 指示器内边距 |
列表视图 UITableView | 释义 |
---|---|
kPOPTableViewContentOffset | 内容滚动位置 |
kPOPTableViewContentSize | 内容大小 |
集合视图 UICollectionView | 释义 |
---|---|
kPOPCollectionViewContentOffset | 内容滚动位置 |
kPOPCollectionViewContentSize | 内容大小 |
导航视图 UINavigationBar | 释义 |
---|---|
kPOPNavigationBarBarTintColor | 着色 |
工具栏视图 UIToolbar | 释义 |
---|---|
kPOPToolbarBarTintColor | 着色 |
导航视图 UITabBar | 释义 |
---|---|
kPOPTabBarBarTintColor | 着色 |
标签视图 UILabel | 释义 |
---|---|
kPOPLabelTextColor | 文字颜色 |
操作动画
和核心动画一样,Pop 的动画提供了一样的操作动画的方式,你可以添加、删除、获取动画。在默认情况下,Pop 的动画执行完动画之后就被移除了,如果你设置动画的属性removedOnCompletion
为NO的情况下,动画将被保留。
-
-pop_addAnimation:forKey:
:添加动画,这里的 key 用来标识动画身份,后面如果需要获取该动画就有它的用处了。 -
-pop_removeAllAnimations
:移除对象上所有动画 -
-pop_removeAnimationForKey:
:通过 key 移除对象上指定动画 -
-pop_animationKeys
:获取对象上所有的动画 -
-pop_animationForKey:
通过 key 获取对象上指定动画
动画过程事件
在核心动画中,你可以通过设置动画代理来获取到动画开始和结束事件。同样的,你可以设置 Pop 动画的代理以此来获取动画的开始和结束。除此之外,Pop 动画还提供了另外两个事件--到达或超出预期值以及动画过程中的每一帧。
-
-pop_animationDidStart:
:动画开始事件 -
-pop_animationDidStop:finished:
:动画结束事件 -
-pop_animationDidReachToValue:
:动画到达或超出预期值 -
-pop_animationDidApply:
:动画过程中每一帧的更新都会调用
在核心动画中,你可能只能通过代理的方式获取到动画的过程事件,就像上面那种,但是这种方式的缺点就是一旦动画繁多时,就要做特别的区分。在 UIView 动画块中,似乎就意识到代理方法的缺陷,采用了block块的形式来回调动画过程事件,这让我们可以一个动画对应一个动画事件。
Pop 动画库中,不仅提供了代理模式的事件回调,而且也提供了block块的回调方法。
@property (copy, nonatomic) void (^animationDidStartBlock)(POPAnimation *anim);
@property (copy, nonatomic) void (^animationDidReachToValueBlock)(POPAnimation *anim);
@property (copy, nonatomic) void (^completionBlock)(POPAnimation *anim, BOOL finished);
@property (copy, nonatomic) void (^animationDidApplyBlock)(POPAnimation *anim);
动画的实例
本节通过几个小示例来演示 Pop 动画的组合使用。
- 列表的开合
我们要实现上面那种样式的效果。仔细观察动图,你就可以发现,这种效果是使用了弹性动画和基础动画而已。总共有三根线,顶部和底部的线做了中心点位置的移动以及进行了旋转,中线仅仅做了显示隐藏动画。接下来,我们来实现这种效果。
首先是三根线,建议使用图层 CALayer 来做线条。排版的方式很多种,你可以将其放在一个视图中,例如 UIButton。
我们来做第一根线条的动画,之前说了顶部的线条有两种动画,一种是旋转另一种是中心点的位移。
旋转动画:
POPSpringAnimation *transformTopAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotation];
transformTopAnimation.toValue = @(M_PI_4);
transformTopAnimation.springBounciness = 20.f;
transformTopAnimation.springSpeed = 20;
transformTopAnimation.dynamicsTension = 1000;
[self.topLayer pop_addAnimation:transformTopAnimation forKey:@"rotateTopAnimation"];
移动中心点动画
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
POPBasicAnimation *positionTopAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerPosition];
positionTopAnimation.toValue = [NSValue valueWithCGPoint:center];
positionTopAnimation.duration = 0.3;
[self.topLayer pop_addAnimation:positionTopAnimation forKey:@"positionTopAnimation"];
同样的,我们给底部线条加上类似的动画效果,当然,旋转的方向和顶部的是相反的。加完之后的效果如下图:
接下来就是对中间视图的隐藏动画了。
POPBasicAnimation *fadeAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
fadeAnimation.toValue = @(0);
fadeAnimation.duration = 0.3;
[self.middleLayer pop_addAnimation:fadeAnimation forKey:@"fadeAnimation"];
此时,动画效果已经基本完成。
至此我们已经完成了闭合效果,接下来要做的就是再次展开效果。使用的动画基本一致,只是该效果的思路和之前完全的相反的。大家可以自行添加尝试。
- 衰减的球
上面的例子演示了基础动画和弹性动画的结合使用。Pop 动画还有一个衰减动画,为了演示衰减动画效果,这次我们通过手势拖拽一个小球,松手之后让其动过衰减动画自行停止。
首先依旧是前期的准备,我们定义一个视图,并为其添加上拖拽手势,在我们没有添加任何动画的时候是下面的效果:
- (void)handlePan:(UIPanGestureRecognizer *)recognizer{
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
这次我们补上衰减动画:
- (void)handlePan:(UIPanGestureRecognizer *)recognizer{
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
if(recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [recognizer velocityInView:self.view];
POPDecayAnimation *positionAnimation = [POPDecayAnimation animationWithPropertyNamed:kPOPLayerPosition];
positionAnimation.velocity = [NSValue valueWithCGPoint:velocity];
[recognizer.view.layer pop_addAnimation:positionAnimation forKey:@"layerPositionAnimation"];
}
}