iOS 动画篇 - pop动画库

一款由 facebook 研发的不同于系统的动画库

Posted by LOLITA0164 on November 27, 2018

pop

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 主要有以下几个步骤:

  1. 选择动画类型,并指定需动画的属性
  2. 执行属性的新值
  3. 设置动画事件代理(可选)
  4. 应用动画

动画介绍

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 直接列举了所有可以做的属性动画,并且所针对的对象不再只是图层,是图层也进行了支持(或许是因为作此兼容才会出现上面的不同吧)。

动画结构

动画结构

基础动画

在文章开始的例子就使用了基础动画。这里介绍一下其中的属性和方法。

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,动画不会执行完成事件

示例:我们来继续之前移动视图的例子,不同的是我们设置了时间、速率曲线、重复等效果,并且特地使用核心动画写了一个类似的对比动画。

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"];

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:动力学中使用的张力。可以在弹跳和速度上使用,以便更精细地调整动画效果。
  • dynamicsTension:动力学中使用的摩擦力。可以在弹跳和速度上使用,以便更精细地调整动画效果。
  • dynamicsTension:动力学中使用的质量。可以在弹跳和速度上使用,以便更精细地调整动画效果。

示例:

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/zh-cn)。

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"];
}

动画效果和Spring类似

可选择的动画属性

图层 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"];
    }
}

衰减效果


相关阅读