Facebook Pop动画框架详细解析(三) —— spring动画简单实例

版本记录

版本号 时间
V1.0 2017.12.17

前言

POP框架是Facebook开发的一个框架,是一个在iOS与OS X上通用的极具扩展性的动画引擎。它在基本的静态动画的基础上增加的弹簧动画与衰减动画,使之能创造出更真实更具物理性的交互动画。POP的API可以快速的与现有的ObjC代码集成并可以作用于任意对象的任意属性。感兴趣的可以看上面几篇文章。相关代码请参考GitHub - 刀客传奇
1. Facebook Pop动画框架详细解析(一) —— 基本概览
2. Facebook Pop动画框架详细解析(二) —— 基本架构

几个重要参数

下面我们看一下springAnimation几个重要参数。

  • Bounciness 反弹-影响动画作用的参数的变化幅度
  • Speed 速度
  • Tension 拉力-影响回弹力度以及速度
  • Friction 摩擦力-如果开启,动画会不断重复,幅度逐渐削弱,直到停止。
  • Mass 质量-细微的影响动画的回弹力度以及速度

这里,我们使用 pop_addAnimation 来让动画开始生效,如果你想删除动画的话,那么你需要调用 pop_removeAllAnimations。 与 iOS 自带的动画不同,如果你在动画的执行过程中删除了物体的动画,那么物体会停在动画状态的最后一个瞬间,而不是闪回开始前的状态。


功能实现

下面我们就看一下功能实现,还是直接看代码。

1. ViewController.m
#import "ViewController.h"
#import "POP.h"
#import "PDAnimationManager.h"

@interface ViewController () <UITableViewDelegate, UITableViewDataSource>

@property (weak, nonatomic) IBOutlet UISlider *bounceSlider;
@property (strong, nonatomic) IBOutlet UISlider *speedSlider;
@property (strong, nonatomic) IBOutlet UISwitch *tensionSwitch;
@property (strong, nonatomic) IBOutlet UISlider *tensionSlider;
@property (weak, nonatomic) IBOutlet UISwitch *FrictionSwitch;
@property (weak, nonatomic) IBOutlet UISlider *frictionSlider;
@property (weak, nonatomic) IBOutlet UISwitch *massSwitch;
@property (weak, nonatomic) IBOutlet UISlider *massSlider;

@property (nonatomic, strong) UITableView * tableView;
@property (nonatomic, assign) CGFloat bouncinessValue;
@property (nonatomic, assign) CGFloat speedTweakValue;
@property (nonatomic, strong) CALayer *popCircle;
@property (nonatomic, assign) BOOL animated;
@property (nonatomic, strong) NSString *animationType;
@property (nonatomic, strong) NSArray *animationTypes;
@property (nonatomic, copy) NSString *exampleID;

@end

@implementation ViewController

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    self.tableView.center = CGPointMake(self.view.center.x, self.view.center.y - 1000.0);
    [self.tableView setContentInset:UIEdgeInsetsMake(66,0,0,0)];
    self.animationType = kPOPLayerScaleXY;
    [self setPopCircle];
    [self.view addSubview:self.tableView];
}

#pragma mark - Object Private Function

- (void)setPopCircle
{
    self.popCircle = [CALayer layer];
    
    [self resetCircle];
    
    [self.view.layer addSublayer:self.popCircle];
    
    self.animationTypes = @[kPOPLayerBackgroundColor,kPOPLayerBounds,kPOPLayerOpacity,kPOPLayerPosition,kPOPLayerRotation,
                            kPOPLayerScaleXY,kPOPLayerSize,kPOPLayerTranslationXY,kPOPLayerRotationX, kPOPLayerRotationY];
    
    
    self.tableView.dataSource = self;
    self.tableView.delegate = self;
    [self.tableView reloadData];
}

- (void)resetCircle
{
    [self.popCircle pop_removeAllAnimations];
    self.popCircle.opacity = 1.0;
    self.popCircle.transform = CATransform3DIdentity;
    [self.popCircle setMasksToBounds:YES];
    [self.popCircle setBackgroundColor:[UIColor redColor].CGColor];
    [self.popCircle setCornerRadius:25.0f];
    [self.popCircle setBounds:CGRectMake(0.0f, 0.0f, 50.0f, 50.0f)];
    self.popCircle.position = CGPointMake(self.view.center.x, 180.0);
}

- (void)hideTableView
{
    [self resetCircle];
    POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter];
    anim.toValue = [NSValue valueWithCGPoint:CGPointMake(self.view.window.center.x, -1000.0)];
    [self.tableView pop_addAnimation:anim forKey:@"AnimationHide"];
    
    anim.completionBlock = ^(POPAnimation *anim, BOOL finished) {
        if (finished) {
            [self performAnimation];
        }
    };
}

- (void)setAnimationWithBounciness:(CGFloat)bounciness andSpeed:(CGFloat)speed
{
    [self.popCircle pop_removeAllAnimations];
    POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:self.animationType];
    
    [PDAnimationManager springObject:self.popCircle configAnimation:anim WithType:self.animationType andAnimated:self.animated];
    
    self.animated = !self.animated;
    anim.springBounciness = bounciness;
    anim.springSpeed = speed;
    
    if (self.FrictionSwitch.isOn) {
        anim.dynamicsFriction = self.frictionSlider.value;
    }
    
    if (self.tensionSwitch.isOn) {
        anim.dynamicsTension = self.tensionSlider.value;
    }
    
    if (self.massSwitch.isOn) {
        anim.dynamicsMass = self.massSlider.value;
    }
    
    anim.completionBlock = ^(POPAnimation *anim, BOOL finished) {
        if (finished) {
            
            [self performAnimation];
        }
    };
    
    [self.popCircle pop_addAnimation:anim forKey:@"Animation"];
    
}

- (void)performAnimation
{
    [self setAnimationWithBounciness:self.bounceSlider.value andSpeed:self.speedSlider.value];
}

#pragma mark - Action && Notification

- (IBAction)showEffect:(id)sender
{
    [self resetCircle];
    
    if (self.tableView.center.y > 0) {
        [self hideTableView];
    }
    else{
        POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter];
        anim.toValue = [NSValue valueWithCGPoint:self.view.center];
        [self.tableView pop_addAnimation:anim forKey:@"AnimationShow"];
    }
}

- (IBAction)sliderValueChanged:(id)sender
{
    [self resetCircle];
    [self performAnimation];
}

#pragma mark - UITableViewDelegate, UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.animationTypes count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"UITableViewCell"];
    cell.textLabel.text = [self.animationTypes objectAtIndex:indexPath.row];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    self.animationType = [self.animationTypes objectAtIndex:indexPath.row];
    [self hideTableView];
}

@end
2. PDAnimationManager.h
#import <UIKit/UIKit.h>
#import <POP/POP.h>


@interface PDAnimationManager : NSObject

+ (void)springObject:(CALayer*)layer configAnimation:(POPPropertyAnimation *)animation WithType:(NSString *)type andAnimated:(BOOL)animated;

+ (void)decayObject:(CALayer*)layer configAnimation:(POPDecayAnimation *)animation WithType:(NSString *)type andAnimated:(BOOL)animated andVelocitySlider:(UISlider *)slider;

@end
3.PDAnimationManager.m
#import "PDAnimationManager.h"


@implementation PDAnimationManager

+ (void)springObject:(CALayer*)layer configAnimation:(POPPropertyAnimation *)animation WithType:(NSString *)type andAnimated:(BOOL)animated
{
    //For scale
    if ([type isEqualToString:kPOPLayerScaleXY]) {
        if (animated) {
            animation.toValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)];
        }
        else{
            animation.toValue = [NSValue valueWithCGPoint:CGPointMake(2.0, 2.0)];
        }
    }
    else if ([type isEqualToString:kPOPLayerBackgroundColor])
    {
        if (animated) {
            animation.toValue = [UIColor purpleColor];
        }
        else{
            animation.toValue = [UIColor colorWithRed:0.16 green:0.72 blue:1 alpha:1];
        }
    }
    else if ([type isEqualToString:kPOPLayerBounds])
    {
        if (animated) {
            animation.toValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 30, 30)];
        }
        else{
            animation.toValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 150, 150)];
        }
    }
    else if ([type isEqualToString:kPOPLayerOpacity])
    {
        if (animated) {
            animation.toValue = @0;
        }
        else{
            animation.toValue = @1.0;
        }
    }
    else if ([type isEqualToString:kPOPLayerPosition])
    {
        if (animated) {
            animation.toValue = [NSValue valueWithCGPoint:CGPointMake(60, 150)];
        }
        else{
            animation.toValue = [NSValue valueWithCGPoint:CGPointMake(260, 150)];
        }
    }
    else if ([type isEqualToString:kPOPLayerRotation])
    {
        layer.cornerRadius = 10.0;
        if (animated) {
            animation.toValue = @(M_PI_2);
        }
        else{
            animation.toValue = @(0);
        }
    }
    else if ([type isEqualToString:kPOPLayerSize])
    {

        if (animated) {
            animation.toValue = [NSValue valueWithCGSize:CGSizeMake(50.0, 100.0)];
        }
        else{
            animation.toValue = [NSValue valueWithCGSize:CGSizeMake(50.0, 50)];
        }
    }
    else if ([type isEqualToString:kPOPLayerTranslationXY])
    {
        
        if (animated) {
            animation.toValue = [NSValue valueWithCGPoint:CGPointMake(100.0f,0.0f)];
        }
        else{
            animation.toValue = [NSValue valueWithCGPoint:CGPointMake(-100.0f,0.0f)];
        }
    }
    else if ([type isEqualToString:kPOPLayerRotationX])
    {
        if (animated) {
            animation.toValue = @(M_PI_2);
        }
        else{
            animation.toValue = @(0);
        }
    }
    else if ([type isEqualToString:kPOPLayerRotationY])
    {
        if (animated) {
            animation.toValue = @(M_PI_2);
        }else{
            animation.toValue = @(0);
        }
    }
    
}

+ (void)decayObject:(CALayer*)layer configAnimation:(POPDecayAnimation *)animation WithType:(NSString *)type andAnimated:(BOOL)animated andVelocitySlider:(UISlider *)slider;
{
    //For scale
    if ([type isEqualToString:kPOPLayerScaleXY]) {
        if (animated) {
            animation.velocity = [NSValue valueWithCGSize:CGSizeMake(-slider.value/100.0, -slider.value/100.0)];
        }
        else{
            animation.velocity = [NSValue valueWithCGSize:CGSizeMake(slider.value/100.0, slider.value/100.0)];
            animation.fromValue = [NSValue valueWithCGSize:CGSizeMake(0.5, 0.5)];
        }
        
            
    }
    else if ([type isEqualToString:kPOPLayerBackgroundColor])
    {
        if (animated) {
            animation.velocity = [UIColor colorWithRed:-slider.value/100.0 green:-slider.value/200.0 blue:-slider.value/300.0 alpha:1.0];
        }
        else{
            animation.velocity = [UIColor colorWithRed:slider.value/100.0 green:slider.value/200.0 blue:slider.value/300.0 alpha:1.0];
            animation.fromValue  = [UIColor colorWithRed:0.2 green:0.1 blue:0.3 alpha:1];
        }
        
    }
    else if ([type isEqualToString:kPOPLayerBounds])
    {
        if (animated) {
            animation.velocity = [NSValue valueWithCGRect:CGRectMake(0, 0, -slider.value, -slider.value)];
        }
        else{
            animation.velocity = [NSValue valueWithCGRect:CGRectMake(0, 0, slider.value, slider.value)];
            animation.fromValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 50.0, 50.0)];
        }
        
        
    }
    else if ([type isEqualToString:kPOPLayerOpacity])
    {
        if (animated) {
            animation.velocity = @(-slider.value/50.0);
        }
        else{
            animation.velocity = @(slider.value/50.0);
            
            animation.fromValue = @0.0;
        }
        

    }
    else if ([type isEqualToString:kPOPLayerPosition])
    {
        if (animated) {
            animation.velocity = [NSValue valueWithCGPoint:CGPointMake(-slider.value, -slider.value)];
        }
        else{
            animation.velocity = [NSValue valueWithCGPoint:CGPointMake(slider.value, slider.value)];
            
            animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(160, 150)];
        }
        

    }
    else if ([type isEqualToString:kPOPLayerPositionX])
    {
        if (animated) {
            animation.velocity = @(-slider.value);
        }
        else{
            animation.velocity = @(slider.value);
            animation.fromValue =  @(25.0);
        }
        
        
    }
    else if ([type isEqualToString:kPOPLayerRotation])
    {
        layer.cornerRadius = 10.0;
        if (animated) {
            animation.velocity = @(-slider.value);
        }
        else{
            animation.velocity = @(slider.value);
            animation.fromValue = @(M_PI_2);
        }
        
    }
    else if ([type isEqualToString:kPOPLayerSize])
    {
       
        
        if (animated) {
            animation.velocity =  [NSValue valueWithCGSize:CGSizeMake(-slider.value/10.0, -slider.value*3)];
        }
        else{
            animation.velocity =  [NSValue valueWithCGSize:CGSizeMake(slider.value/10.0, slider.value*3)];
            animation.fromValue = [NSValue valueWithCGSize:CGSizeMake(50.0, 50.0)];
        }
    }
    else if ([type isEqualToString:kPOPLayerTranslationXY])
    {
        
        if (animated) {
            animation.velocity =  [NSValue valueWithCGPoint:CGPointMake(-slider.value, -slider.value)];
        }
        else{
            animation.velocity =  [NSValue valueWithCGPoint:CGPointMake(slider.value, slider.value)];
            animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0.0f,0.0f)];
        }
    }
    else if ([type isEqualToString:kPOPLayerRotationX])
    {
        
        if (animated) {
            animation.velocity = @(-slider.value/10.0);
        }
        else{
            animation.velocity = @(slider.value/10.0);
            animation.fromValue = @(0);
        }
        
        
    }
    else if ([type isEqualToString:kPOPLayerRotationY])
    {
        if (animated) {
            animation.velocity = @(-slider.value/10.0);
        }
        else{
            animation.velocity = @(slider.value/10.0);
            animation.fromValue = @(0);
        }
    }
    
}

@end

功能效果

下面我们就看一下功能效果。

参考文章

1. Facebook POP 进阶指南
2. pop-handapp

后记

未完,待续~~~

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

推荐阅读更多精彩内容