仿照淘宝直播点赞的动画效果

2020-04-22  本文已影响0人  ImmortalSummer

简单介绍一下, 左边是样式一, 采用的是一个路径动画, 右边是样式二,采用了一组动画组, 相对于第一种形式, 使用动画组扩展性更好, 动画也可以更细腻, 代码后边也贴出了控制器对动画组件的引用和动画组件中涉及到的定时器的类的代码.

动画部分设计的知识点参考

左边是样式一, 右边是样式二

样式一

#import "WLikeAnimationView.h"
#import "WSafeTimer.h"

@interface WLikeAnimationView()
@property(nonatomic,strong) NSTimer *timer;
@end

@implementation WLikeAnimationView

-(instancetype)initWithStartingPoint:(CGPoint)point{
    
    CGFloat w = 80;
    CGFloat x = point.x - w*0.5;
    CGFloat h = 220;
    CGFloat y = point.y - h;
    self = [[WLikeAnimationView alloc] initWithFrame:CGRectMake(x, y, w, h)];
    
    // 添加计时器, 每隔一段时间自动冒泡泡
    self.timer = [WSafeTimer scheduledTimerWithTimeInterval:6 target:self selector:@selector(changeTimer) userInfo:nil repeats:YES];
    
    return self;
}

#pragma mark - 计时器
-(void)changeTimer {
    [self startAnimation];
}

#pragma mark - 执行冒泡动画
-(void)startAnimation {
    // 每次点击随机弹出3~5次
    int count = 3 + arc4random_uniform(3);
    for (int i=0; i<count; i++) {
        float delayInSeconds = 0.4 * i;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self startAnimation_once];
        });
    }
}

#pragma mark - 执行一次冒泡动画
-(void)startAnimation_once {
    CGFloat imgWH = 20;
    CGFloat x_center = (self.bounds.size.width-imgWH) * 0.5;
    CGFloat x_max = self.bounds.size.width - imgWH;
    // 起始位置
    CGFloat x_0 = x_center;
    CGFloat y_0 = self.bounds.size.height;
    
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(x_0, y_0, imgWH, imgWH)];
    [self addSubview:imageView];
    imageView.backgroundColor = [UIColor clearColor];
    
    // 随机获取图片 live_dianzan_anim_icon_01 - 09
    NSString *imageName = [NSString stringWithFormat:@"live_dianzan_anim_icon_%02d",arc4random_uniform(9)+1];
    imageView.image = [UIImage imageNamed:imageName];
    
    // 生成x的随机偏移量, 并创建动画路径的经过的位置
    CGFloat xOffset = self.bounds.size.width*0.4*(arc4random_uniform(10)/10.0);
    // 生成y的随机偏移量, 并创建动画路径的经过的位置
    CGFloat yOffset = self.bounds.size.height*0.2*(arc4random_uniform(10)/10.0);
    CGFloat y_1 = self.bounds.size.height - self.bounds.size.height/10.0*2.0;
    CGFloat y_2 = self.bounds.size.height - self.bounds.size.height/10.0*4.0 - yOffset;
    CGFloat y_3 = self.bounds.size.height - self.bounds.size.height/10.0*6.0 - yOffset;
    CGFloat y_4 = self.bounds.size.height - self.bounds.size.height/10.0*10.0 - yOffset;
    // 生产路径的关键点
    CGPoint p0 = CGPointMake(x_0, y_0);
    CGPoint p1 = CGPointMake(xOffset, y_1);
    CGPoint p2 = CGPointMake(x_max-xOffset, y_2);
    CGPoint p3 = CGPointMake(xOffset, y_3);
    CGPoint p4 = CGPointMake(x_max-xOffset, y_4);
    
    // 生成随机执行时间 3~4秒
    CGFloat duration = 3 + arc4random_uniform(1000)/1000.0;
    
    UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    [bezierPath moveToPoint:p0]; // 起点
    [bezierPath addLineToPoint:p1]; // 经过点
    [bezierPath addLineToPoint:p2]; // 经过点
    [bezierPath addLineToPoint:p3]; // 经过点
    [bezierPath addLineToPoint:p4]; // 终点
    
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    animation.path = bezierPath.CGPath;
    // 动画时间间隔
    animation.duration = duration;
    // 重复次数为最大值
    animation.repeatCount = 1;
    animation.removedOnCompletion = YES;
    animation.fillMode = kCAFillModeForwards;
    // 控制动画的显示节奏 先慢后快再慢
    CAMediaTimingFunction *timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
    [animation setTimingFunction:timingFunction];
    // 为imageView添加路径动画
    [imageView.layer addAnimation:animation forKey:nil];
    // 添加渐隐动画, 执行完成后移除imageView
    [UIView animateWithDuration:duration+0.25 animations:^{
        imageView.alpha = 0;
    } completion:^(BOOL finished) {
        [imageView removeFromSuperview];
    }];
}

#pragma mark - other
-(void)clearTimer{
    if(self.timer != nil){
        [self.timer invalidate];
        self.timer = nil;
    }
}

-(void)dealloc{
    NSLog(@"like animation view dealloc");
    [self clearTimer];
}

@end

样式二

#import "WLikeAnimationView_New.h"
#import "WSafeTimer.h"

@interface WLikeAnimationView_New()
@property(nonatomic,strong) NSTimer *timer;
@end

@implementation WLikeAnimationView_New

-(instancetype)initWithStartingPoint:(CGPoint)point{
    
    CGFloat w = 80;
    CGFloat x = point.x - w*0.5;
    CGFloat h = 220;
    CGFloat y = point.y - h;
    self = [[WLikeAnimationView_New alloc] initWithFrame:CGRectMake(x, y, w, h)];
    
    // 添加计时器, 每隔一段时间自动冒泡泡
    self.timer = [WSafeTimer scheduledTimerWithTimeInterval:6 target:self selector:@selector(changeTimer) userInfo:nil repeats:YES];
    
    return self;
}

#pragma mark - 计时器
-(void)changeTimer {
    [self startAnimation];
}

#pragma mark - 执行冒泡动画
-(void)startAnimation {
    // 每次点击随机弹出3~5次
    int count = 3 + arc4random_uniform(3);
    for (int i=0; i<count; i++) {
        float delayInSeconds = 0.4 * i;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self startAnimation_once];
        });
    }
}

/*
 第二种思路, 执行一个动画组,这个动画组根据经过的点动态生成, 更容易扩展
 */
#pragma mark - 执行一次冒泡动画
-(void)startAnimation_once {
    CGFloat imgWH = 20;
    CGFloat x_center = (self.bounds.size.width-imgWH) * 0.5;
    CGFloat x_max = self.bounds.size.width - imgWH;
    // 起始位置
    CGFloat x_0 = x_center;
    CGFloat y_0 = self.bounds.size.height;
    
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(x_0, y_0, imgWH, imgWH)];
    [self addSubview:imageView];
    imageView.backgroundColor = [UIColor clearColor];
    
    // 随机获取图片 live_dianzan_anim_icon_01 - 09
    NSString *imageName = [NSString stringWithFormat:@"live_dianzan_anim_icon_%02d",arc4random_uniform(9)+1];
    imageView.image = [UIImage imageNamed:imageName];
    
    // 生成x的随机偏移量, 并创建动画路径的经过的位置
    CGFloat xOffset = self.bounds.size.width*0.4*(arc4random_uniform(10)/10.0);
    // 生成y的随机偏移量, 并创建动画路径的经过的位置
    CGFloat yOffset = self.bounds.size.height*0.2*(arc4random_uniform(10)/10.0);
    CGFloat y_1 = self.bounds.size.height - self.bounds.size.height/10.0*2.0;
    CGFloat y_2 = self.bounds.size.height - self.bounds.size.height/10.0*4.0 - yOffset;
    CGFloat y_3 = self.bounds.size.height - self.bounds.size.height/10.0*6.0 - yOffset;
    CGFloat y_4 = self.bounds.size.height - self.bounds.size.height/10.0*10.0 - yOffset;
    // 生产路径的关键点
    CGPoint p0 = CGPointMake(x_0, y_0);
    CGPoint p1 = CGPointMake(xOffset, y_1);
    CGPoint p2 = CGPointMake(x_max-xOffset, y_2);
    CGPoint p3 = CGPointMake(xOffset, y_3);
    CGPoint p4 = CGPointMake(x_max-xOffset, y_4);
    
    NSMutableArray *points = [NSMutableArray array];
    [points addObject:NSStringFromCGPoint(p0)];
    [points addObject:NSStringFromCGPoint(p1)];
    [points addObject:NSStringFromCGPoint(p2)];
    [points addObject:NSStringFromCGPoint(p3)];
    [points addObject:NSStringFromCGPoint(p4)];
    
    CGPoint pStart = p0;
    NSMutableArray *paths = [NSMutableArray array];
    for (int i=1; i<points.count; i++) {
        CGPoint pEnd = CGPointFromString(points[i]);
        
        UIBezierPath *bezierPath = [UIBezierPath bezierPath];
        [bezierPath moveToPoint:pStart]; // 起点
        [bezierPath addLineToPoint:pEnd]; // 终点
        [paths addObject:bezierPath];
        
        pStart = pEnd;
    }
    
    // 生成随机执行时间 3~4秒
    CGFloat duration = 3 + arc4random_uniform(1000)/1000.0;
    CGFloat once = duration / paths.count;
    NSMutableArray *animations = [NSMutableArray array];
    for (int i=0; i<paths.count; i++) {
        UIBezierPath *bezierPath = paths[i];
        
        CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        animation.path = bezierPath.CGPath;
        // 动画时间间隔
        animation.duration = once;
        animation.beginTime = once*i;
        // 重复次数为最大值
        animation.repeatCount = 1;
        animation.removedOnCompletion = YES;
        animation.fillMode = kCAFillModeForwards;
        // 控制动画的显示节奏 先慢后快再慢
        CAMediaTimingFunction *timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
        [animation setTimingFunction:timingFunction];
        
        [animations addObject:animation];
    }
    
    
    CAAnimationGroup *group = [CAAnimationGroup new];
    group.animations = animations;
    group.duration = duration;
    // 为imageView添加路径动画
    [imageView.layer addAnimation:group forKey:nil];
    
    // 添加渐隐动画, 执行完成后移除imageView
    [UIView animateWithDuration:duration+0.25 animations:^{
        imageView.alpha = 0;
    } completion:^(BOOL finished) {
        [imageView removeFromSuperview];
    }];
}

#pragma mark - other
-(void)clearTimer{
    if(self.timer != nil){
        [self.timer invalidate];
        self.timer = nil;
    }
}

-(void)dealloc{
    NSLog(@"like animation view dealloc");
    [self clearTimer];
}

@end

其他文件:

控制器

#import "ViewController.h"
#import "WLikeAnimationView.h"
#import "WLikeAnimationView_New.h"

@interface ViewController ()
@property(nonatomic,strong) WLikeAnimationView *likeAnimationView;
@property(nonatomic,strong) WLikeAnimationView_New *likeAnimationView_new;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(100, 500, 40, 40)];
    [button setImage:[UIImage imageNamed:@"xiaoxinxin"] forState:UIControlStateNormal];
    [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [button addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    
    self.likeAnimationView = [[WLikeAnimationView alloc] initWithStartingPoint:CGPointMake(120, 500)];
    [self.view addSubview:self.likeAnimationView];
    
    UIButton *button2 = [[UIButton alloc]initWithFrame:CGRectMake(300, 500, 40, 40)];
    [button2 setImage:[UIImage imageNamed:@"xiaoxinxin"] forState:UIControlStateNormal];
    [button2 setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [button2 addTarget:self action:@selector(button2Clicked) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button2];
    
    self.likeAnimationView_new = [[WLikeAnimationView_New alloc] initWithStartingPoint:CGPointMake(320, 500)];
    [self.view addSubview:self.likeAnimationView_new];
}


-(void)buttonClicked{
    [self.likeAnimationView startAnimation];
}

-(void)button2Clicked{
    [self.likeAnimationView_new startAnimation];
}
@end

WSafeTimer

#import "WSafeTimer.h"

@interface WSafeTimer()
@property(nonatomic,weak) NSTimer *timer;
@property(nonatomic,weak) id target;
@property(nonatomic,assign) SEL selector;
@end

@implementation WSafeTimer
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    WSafeTimer *safeTimer = [WSafeTimer new];
    
    safeTimer.target = aTarget;
    safeTimer.selector = aSelector;
    safeTimer.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:safeTimer selector:@selector(timerHandle) userInfo:userInfo repeats:yesOrNo];
    
    return safeTimer.timer;
}

-(void)timerHandle{
    if (self.target && [self.target respondsToSelector:self.selector]) {
        [self.target performSelector:self.selector];
    }else {
        [self.timer invalidate];
    }
}
@end

上一篇下一篇

猜你喜欢

热点阅读