iOS 动画篇 - pop动画库

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 直接列举了所有可以做的属性动画,并且所针对的对象不再只是图层 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"];
}
动画效果和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"];
    }
}
衰减效果

相关阅读

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,874评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,151评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,270评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,137评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,116评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,935评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,261评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,895评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,342评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,854评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,978评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,609评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,181评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,182评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,402评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,376评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,677评论 2 344

推荐阅读更多精彩内容