iOS开发-下拉刷新动画四个球旋转缩放效果

iOS开发-下拉刷新动画四个球旋转缩放效果

之前开发中实现下拉刷新动画四个球旋转缩放效果。

一、效果图

在这里插入图片描述

二、基础动画

CABasicAnimation类的使用方式就是基本的关键帧动画。

所谓关键帧动画,就是将Layer的属性作为KeyPath来注册,指定动画的起始帧和结束帧,然后自动计算和实现中间的过渡动画的一种动画方式。

CABasicAnimation动画的使用,也比较简单。

先看下动画的基本属性:

  • duration: 动画时长
  • repeatCount: 重复的次数,不停重复设置为 HUGE_VALF
  • repeatDuration: 设置动画的时间。在该时间内动画一直执行,不计次数。
  • beginTime: 指定动画开始的时间。从开始延迟几秒的话,设置为CACurrentMediaTime() + 秒数 的方式
  • timingFunction: 设置动画的速度变化
  • autoreverses: 动画结束时是否执行逆动画
  • fromValue: 所改变属性的起始值
  • toValue: 所改变属性的结束时的值
  • byValue: 所改变属性相同起始值的改变量
  • byValue: 所改变属性相同起始值的改变量

下面创建一个动画实例

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"scale"];

CAAnimationDelegate 代理方法
首先设置代理[animation setDelegate:self],代理设置完成之后就可以实现CAAnimation的代理方法:

- (void)animationDidStart:(CAAnimation *)anim {
    
    
	
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    
    
	
}

注意:CABasicAnimation的delegate是strong属性,容易导致循环引用而不能将内存释放。

需要注意的一点

__weak typeof(self) weakSelf = self;
CABasicAnimation *animate = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animate.delegate = weakSelf;

三、实现代码

3.1 代码实现动画

四个球旋转缩放效果,实现CAAnimationGroup,包括两个animation,旋转动画与未知动画

四个球向外移动、及旋转动画。

- (void)addBallAnimation {
    
    
    [self removeContainAnimation];
    [self removeBallAnimation];
    
    CGPoint aBallCenter = self.aballImageView.center;
    CGPoint bBallCenter = self.bballImageView.center;
    CGPoint cBallCenter = self.cballImageView.center;
    CGPoint dBallCenter = self.dballImageView.center;
    
    [self showAnimation:self.aballImageView from:aBallCenter toPosition:bBallCenter];
    [self showAnimation:self.bballImageView from:bBallCenter toPosition:aBallCenter];
    [self showAnimation:self.cballImageView from:cBallCenter toPosition:dBallCenter];
    [self showAnimation:self.dballImageView from:dBallCenter toPosition:cBallCenter];
    
    self.type = INCAAnimationTypePostion;
}


- (void)showAnimation:(UIView *)view from:(CGPoint)from toPosition:(CGPoint)to {
    
    
    CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
    
    CAAnimationGroup *animationGroup;
    animationGroup = [CAAnimationGroup animation];
    animationGroup.removedOnCompletion = NO;
    animationGroup.timingFunction = defaultCurve;
    animationGroup.beginTime = CACurrentMediaTime();
    animationGroup.duration = 0.5;
    animationGroup.repeatCount = 1;
    animationGroup.delegate = self;
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:from];
    animation.toValue = [NSValue valueWithCGPoint:to]; // 终了帧
    
    CAKeyframeAnimation *scaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"];
    scaleAnimation.values = @[@1.0,@0.5,@1.0];
    scaleAnimation.keyTimes = @[@0.0,@0.5,@1.0];
    
    NSArray *animations = @[animation,scaleAnimation];
    
    animationGroup.animations = animations;
    
    [view.layer addAnimation:animationGroup forKey:@"animationGroup"];
}

完整代码如下

#import "INRefreshFourBallLoading.h"
#import "UIColor+Addition.h"

static CGFloat kBallSize = 6.0;
static CGFloat kDistance = 10.0;

typedef NS_ENUM(NSInteger, INCAAnimationType) {
    
    
    INCAAnimationTypeNone,      // 没有动画
    INCAAnimationTypeRotate,
    INCAAnimationTypePostion,
} ;

@interface INRefreshFourBallLoading ()<CAAnimationDelegate>

@property (nonatomic, strong) UIImageView *contentBGImageView;

@property (nonatomic, strong) UIImageView *aballImageView;
@property (nonatomic, strong) UIImageView *bballImageView;
@property (nonatomic, strong) UIImageView *cballImageView;
@property (nonatomic, strong) UIImageView *dballImageView;

@property (nonatomic, assign) INCAAnimationType type;

@end

@implementation INRefreshFourBallLoading

- (instancetype)initWithFrame:(CGRect)frame
{
    
    
    self = [super initWithFrame:frame];
    if (self) {
    
    
        
        self.type = INCAAnimationTypeNone;
        
        [self addSubview:self.contentBGImageView];
        
        [self.contentBGImageView addSubview:self.aballImageView];
        [self.contentBGImageView addSubview:self.bballImageView];
        [self.contentBGImageView addSubview:self.cballImageView];
        [self.contentBGImageView addSubview:self.dballImageView];
        [self layoutBallFrame];
    }
    return self;
}

- (void)layoutBallFrame {
    
    
    self.contentBGImageView.center = self.center;
    
    CGFloat width = CGRectGetWidth(self.contentBGImageView.frame);
    
    CGPoint showPoint = CGPointMake(width/2, width/2);
    self.aballImageView.center = showPoint;
    self.bballImageView.center = showPoint;
    self.cballImageView.center = showPoint;
    self.dballImageView.center = showPoint;
}

- (void)displayPrecent:(CGFloat)precent {
    
    
    if (precent < 0) {
    
    
        precent = 0;
    }
    
    if (precent > 1.0) {
    
    
        precent = 1.0;
    }
    
    CGFloat width = CGRectGetWidth(self.contentBGImageView.frame);
    CGPoint centerCenter = CGPointMake(width/2, width/2);

    CGPoint aBallCenter = self.aballImageView.center;
    CGPoint bBallCenter = self.bballImageView.center;

    aBallCenter.x = centerCenter.x - precent*kDistance;
    self.aballImageView.center = aBallCenter;
    self.aballImageView.transform = CGAffineTransformMakeScale(precent, precent);

    bBallCenter.x = centerCenter.x + precent*kDistance;
    self.bballImageView.center = bBallCenter;
    self.bballImageView.transform = CGAffineTransformMakeScale(precent, precent);


    CGPoint cBallCenter = self.cballImageView.center;
    CGPoint dBallCenter = self.dballImageView.center;

    cBallCenter.y = centerCenter.y - precent*kDistance;
    self.cballImageView.center = cBallCenter;
    self.cballImageView.transform = CGAffineTransformMakeScale(precent, precent);

    dBallCenter.y = centerCenter.y + precent*kDistance;
    self.dballImageView.center = dBallCenter;
    self.dballImageView.transform = CGAffineTransformMakeScale(precent, precent);
}

- (void)startAnimation {
    
    
    [self showAnimation];
}

- (void)stopAnimation {
    
    
    self.type = INCAAnimationTypeNone;
    [self removeContainAnimation];
    [self removeBallAnimation];
}

- (void)removeContainAnimation {
    
    
    [self.contentBGImageView.layer removeAnimationForKey:@"kHeaderRotation"];
}

- (void)removeBallAnimation {
    
    
    [self.aballImageView.layer removeAnimationForKey:@"animationGroup"];
    [self.bballImageView.layer removeAnimationForKey:@"animationGroup"];
    [self.cballImageView.layer removeAnimationForKey:@"animationGroup"];
    [self.dballImageView.layer removeAnimationForKey:@"animationGroup"];
}

- (void)showAnimation {
    
    
    [self removeContainAnimation];
    [self removeBallAnimation];
    
    __weak typeof(self) weakSelf = self;
    CABasicAnimation *animate = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    animate.toValue = [NSNumber numberWithFloat: M_PI * 2.0];
    animate.duration = 0.5;
    animate.repeatCount = 1;
    animate.removedOnCompletion = NO;
    animate.delegate = weakSelf;
    [self.contentBGImageView.layer addAnimation:animate
                                 forKey:@"kHeaderRotation"];
    
    self.type = INCAAnimationTypeRotate;
}

- (void)addBallAnimation {
    
    
    [self removeContainAnimation];
    [self removeBallAnimation];
    
    CGPoint aBallCenter = self.aballImageView.center;
    CGPoint bBallCenter = self.bballImageView.center;
    CGPoint cBallCenter = self.cballImageView.center;
    CGPoint dBallCenter = self.dballImageView.center;
    
    [self showAnimation:self.aballImageView from:aBallCenter toPosition:bBallCenter];
    [self showAnimation:self.bballImageView from:bBallCenter toPosition:aBallCenter];
    [self showAnimation:self.cballImageView from:cBallCenter toPosition:dBallCenter];
    [self showAnimation:self.dballImageView from:dBallCenter toPosition:cBallCenter];
    
    self.type = INCAAnimationTypePostion;
}

- (void)showAnimation:(UIView *)view from:(CGPoint)from toPosition:(CGPoint)to {
    
    
    CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
    
    CAAnimationGroup *animationGroup;
    animationGroup = [CAAnimationGroup animation];
    animationGroup.removedOnCompletion = NO;
    animationGroup.timingFunction = defaultCurve;
    animationGroup.beginTime = CACurrentMediaTime();
    animationGroup.duration = 0.5;
    animationGroup.repeatCount = 1;
    animationGroup.delegate = self;
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:from];
    animation.toValue = [NSValue valueWithCGPoint:to]; // 终了帧
    
    CAKeyframeAnimation *scaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"];
    scaleAnimation.values = @[@1.0,@0.5,@1.0];
    scaleAnimation.keyTimes = @[@0.0,@0.5,@1.0];
    
    NSArray *animations = @[animation,scaleAnimation];
    
    animationGroup.animations = animations;
    
    [view.layer addAnimation:animationGroup forKey:@"animationGroup"];
}

#pragma mark - CAAnimationDelegate
- (void)animationDidStart:(CAAnimation *)anim {
    
    
    
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    
    
    
    CAAnimation *aBallAnimate = [self.aballImageView.layer animationForKey:@"animationGroup"];
    CAAnimation *bBallAnimate = [self.bballImageView.layer animationForKey:@"animationGroup"];
    CAAnimation *cBallAnimate = [self.cballImageView.layer animationForKey:@"animationGroup"];
    CAAnimation *dBallAnimate = [self.dballImageView.layer animationForKey:@"animationGroup"];
    CAAnimation *containAnimate = [self.contentBGImageView.layer animationForKey:@"kHeaderRotation"];
    
    
    NSLog(@"aBallAnimate:%@",aBallAnimate);
    NSLog(@"bBallAnimate:%@",bBallAnimate);
    NSLog(@"cBallAnimate:%@",cBallAnimate);
    NSLog(@"dBallAnimate:%@",dBallAnimate);
    NSLog(@"containAnimate:%@",containAnimate);
    NSLog(@"anim:%@",anim);

    if (!(aBallAnimate == anim ||
          bBallAnimate == anim ||
          cBallAnimate == anim ||
          dBallAnimate == anim ||
          containAnimate == anim)) {
    
    
        return;
    }
    
    NSLog(@"animationDidStop animationType:%ld",self.type);
    
    if (flag) {
    
    
        [self removeContainAnimation];
        [self removeBallAnimation];
        
        if (self.type == INCAAnimationTypeNone) {
    
    
            return;
        }
        
        if (self.type == INCAAnimationTypeRotate) {
    
    
            [self addBallAnimation];
        } else if (self.type == INCAAnimationTypePostion) {
    
    
            [self showAnimation];
        }
    }
}

#pragma mark - SETTER/GETTER
- (UIImageView *)contentBGImageView {
    
    
    if (!_contentBGImageView) {
    
    
        _contentBGImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 2*(kBallSize + kDistance), 2*(kBallSize + kDistance))];
        _contentBGImageView.clipsToBounds = YES;
        _contentBGImageView.userInteractionEnabled = YES;
        _contentBGImageView.backgroundColor = [UIColor clearColor];
    }
    return _contentBGImageView;
}

- (UIImageView *)aballImageView {
    
    
    if (!_aballImageView) {
    
    
        _aballImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, kBallSize, kBallSize)];
        _aballImageView.clipsToBounds = YES;
        _aballImageView.userInteractionEnabled = YES;
        _aballImageView.backgroundColor = [UIColor colorWithHexString:@"ff7e48" alpha:0.35];
        _aballImageView.layer.cornerRadius = kBallSize/2;
        _aballImageView.layer.masksToBounds = YES;
    }
    return _aballImageView;
}

- (UIImageView *)bballImageView {
    
    
    if (!_bballImageView) {
    
    
        _bballImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, kBallSize, kBallSize)];
        _bballImageView.clipsToBounds = YES;
        _bballImageView.userInteractionEnabled = YES;
        _bballImageView.backgroundColor = [UIColor colorWithHexString:@"ff7e48" alpha:0.75];
        _bballImageView.layer.cornerRadius = kBallSize/2;
        _bballImageView.layer.masksToBounds = YES;
    }
    return _bballImageView;
}

- (UIImageView *)cballImageView {
    
    
    if (!_cballImageView) {
    
    
        _cballImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, kBallSize, kBallSize)];
        _cballImageView.clipsToBounds = YES;
        _cballImageView.userInteractionEnabled = YES;
        _cballImageView.backgroundColor = [UIColor colorWithHexString:@"ff7e48" alpha:0.75];
        _cballImageView.layer.cornerRadius = kBallSize/2;
        _cballImageView.layer.masksToBounds = YES;
    }
    return _cballImageView;
}

- (UIImageView *)dballImageView {
    
    
    if (!_dballImageView) {
    
    
        _dballImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, kBallSize, kBallSize)];
        _dballImageView.clipsToBounds = YES;
        _dballImageView.userInteractionEnabled = YES;
        _dballImageView.backgroundColor = [UIColor colorWithHexString:@"ff7e48" alpha:0.75];
        _dballImageView.layer.cornerRadius = kBallSize/2;
        _dballImageView.layer.masksToBounds = YES;
    }
    return _dballImageView;
}


@end

3.2 MJRefresh使用该动画

我这里继承MJRefreshStateHeader

需要根据刷新控件的状态来执行开启动画与结束动画操作

刷新控件的状态如下

typedef NS_ENUM(NSInteger, MJRefreshState) {
    
    
    // 普通闲置状态
    MJRefreshStateIdle = 1,
    // 松开就可以进行刷新的状态
    MJRefreshStatePulling,
    // 正在刷新中的状态
    MJRefreshStateRefreshing,
    // 即将刷新的状态
    MJRefreshStateWillRefresh,
    // 所有数据加载完毕,没有更多的数据了
    MJRefreshStateNoMoreData
};

INRefreshHeader.h

#import "MJRefresh.h"
#import "INRefreshFourBallLoading.h"

@interface INRefreshHeader : MJRefreshStateHeader

@property (nonatomic, assign) BOOL showInsetTop;

@property (nonatomic, strong) INRefreshFourBallLoading *fourBallLoading;

@end
INRefreshHeader.m

#import "INRefreshHeader.h"

@implementation INRefreshHeader

- (instancetype)initWithFrame:(CGRect)frame {
    
    
    if (self = [super initWithFrame:frame]) {
    
    
        self.lastUpdatedTimeLabel.hidden = YES;
        self.stateLabel.hidden = YES;
        
        [self addSubview:self.fourBallLoading];
    }
    return self;
}

- (INRefreshFourBallLoading *)fourBallLoading {
    
    
    if (!_fourBallLoading) {
    
    
        _fourBallLoading = [[INRefreshFourBallLoading alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth([UIScreen mainScreen].bounds), self.bounds.size.height)];
    }
    return _fourBallLoading;
}

- (void)setState:(MJRefreshState)state {
    
    
    MJRefreshCheckState
    
    // 根据状态做事情
    if (state == MJRefreshStateIdle) {
    
    
        if (oldState == MJRefreshStateRefreshing) {
    
    
            self.fourBallLoading.alpha = 1.0;

            // 如果执行完动画发现不是idle状态,就直接返回,进入其他状态
            if (self.state != MJRefreshStateIdle) return;
            
            self.fourBallLoading.alpha = 1.0;

            [self.fourBallLoading stopAnimation];

        } else {
    
    
            [self.fourBallLoading stopAnimation];

        }
    } else if (state == MJRefreshStatePulling) {
    
    
        [self.fourBallLoading stopAnimation];

    } else if (state == MJRefreshStateRefreshing) {
    
    
        [self.fourBallLoading startAnimation];
    }
}

- (void)prepare {
    
    
    [super prepare];
    self.mj_h = 60.0;
}

- (void)placeSubviews {
    
    
    [super placeSubviews];
    
    CGFloat centerX = self.mj_w * 0.5;
    CGFloat centerY = self.mj_h * 0.5;
    self.fourBallLoading.center = CGPointMake(centerX, centerY);
}

/** 当scrollView的contentOffset发生改变的时候调用 */
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change {
    
    
    [super scrollViewContentOffsetDidChange:change];
    NSLog(@"change:%@",change);
    
    CGPoint old = [change[@"old"] CGPointValue];
    CGPoint new = [change[@"new"] CGPointValue];
    
    CGFloat precent = -new.y/self.mj_h;
    [self.fourBallLoading displayPrecent:precent];
}

/** 当scrollView的contentSize发生改变的时候调用 */
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change {
    
    
    [super scrollViewContentSizeDidChange:change];
}

/** 当scrollView的拖拽状态发生改变的时候调用 */
- (void)scrollViewPanStateDidChange:(NSDictionary *)change {
    
    
    [super scrollViewPanStateDidChange:change];
}

- (void)setShowInsetTop:(BOOL)showInsetTop {
    
    
    _showInsetTop = showInsetTop;
    
}

- (void)backInitState {
    
    
    
}

@end

3.3 具体的TableView使用

需要设置UITableView的下拉刷新操作:tableView.mj_header = header

- (void)configureRefresh {
    
    
    __weak typeof(self) weakSelf = self;
    INRefreshHeader *header = [INRefreshHeader headerWithRefreshingBlock:^{
    
    
        [weakSelf refreshData];
    }];
    
    INRefreshFooter *footer = [INRefreshFooter footerWithRefreshingBlock:^{
    
    
        [weakSelf loadMoreData];
    }];
    self.mcnView.tableView.mj_header = header;
    self.mcnView.tableView.mj_footer = footer;
}

- (void)refreshData {
    
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    
        [self.mcnView.tableView.mj_header endRefreshing];
        [self.mcnView.tableView.mj_footer endRefreshing];
    });
}

- (void)loadMoreData {
    
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    
        [self.mcnView.tableView.mj_header endRefreshing];
        [self.mcnView.tableView.mj_footer endRefreshing];
    });
}

四、小结

iOS开发-下拉刷新动画四个球旋转缩放效果,使用mjrefresh一个好用的上下拉刷新的控件。实现CABasicAnimation、CAAnimationGroup动画组合效果,根据不同的mjrefresh下拉刷新操作来执行动画效果。

学习记录,每天不停进步。

猜你喜欢

转载自blog.csdn.net/gloryFlow/article/details/131991202