iOS development - pull down to refresh animation loading rotation indicator animation effect

iOS development - pull down to refresh animation loading rotation indicator animation effect

In the previous development, the pull-down refresh animation loading rotation indicator animation effect was realized

1. Rendering

insert image description here

2. Basic animation

The use of the CABasicAnimation class is the basic key frame animation.

The so-called key frame animation is to register the property of Layer as KeyPath, specify the start frame and end frame of the animation, and then automatically calculate and realize an animation method in the middle of the transition animation.

The effect of fillMode
: the function of fillMode is to determine the behavior of the current object after the inactive time period. For example, before the animation starts, after the animation ends

  • kCAFillModeRemoved This is the default value, that is to say, when the animation starts and after the animation ends, the animation has no effect on the layer. After the animation ends, the layer will return to the previous state
  • kCAFillModeForwards When the animation ends, the layer will always maintain the final state of the animation
  • kCAFillModeBackwards This is relative to kCAFillModeForwards, that is, before the animation starts, as long as you add the animation to a layer, the layer will immediately enter the initial state of the animation and wait for the animation to start. You can set the test code like this to add an animation to a layer Delay execution for 5 seconds. Then you will find that when the animation has not started, as long as the animation is added to the layer, the layer will be in the initial state of the animation
  • kCAFillModeBoth understands the above two, this is easy to understand, this is actually the synthesis of the above two. Animation

can view

https://blog.csdn.net/gloryFlow/article/details/131991202

3. Realize the code

3.1 Code implementation

Mainly realize CABasicAnimation animation, KeyPath is transform.rotation.z

- (CAAnimation *)rotateAnimation {
    
    
    //初始化一个动画
    CABasicAnimation *animation = [CABasicAnimation animation];
    //动画运动的方式,现在指定的是围绕Z轴旋转
    animation.keyPath = @"transform.rotation.z";
    //动画持续时间
    animation.duration = 0.5;
    
    //结束的角度
    animation.toValue = [NSNumber numberWithFloat:M_PI*2];
    //动画的运动方式
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    //是否反向移动动画
    
    //重复次数
    animation.repeatCount = MAXFLOAT;

    //动画结束后的状态
    animation.fillMode = kCAFillModeBackwards;
    
    //如果为true,动画将在其活动持续时间过后从渲染树中移除。默认为“是”
    animation.removedOnCompletion = NO;

    return animation;
}

The complete code is as follows

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

static CGFloat kRoundSize = 26.0;

@interface INRefreshRoundLoading ()

@property (nonatomic, strong) CAShapeLayer *roundLayer;
@property (nonatomic, strong) CAShapeLayer *loadingLayer;

@property (nonatomic, assign) CGFloat startAngle;
@property (nonatomic, assign) BOOL isLoading;

@end

@implementation INRefreshRoundLoading

- (instancetype)initWithFrame:(CGRect)frame
{
    
    
    self = [super initWithFrame:frame];
    if (self) {
    
    
        self.isLoading = NO;
        
        [self.layer addSublayer:self.roundLayer];
        [self.layer addSublayer:self.loadingLayer];

        [self drawContentShapeLayer];
        [self layoutSubLayersFrame];
        
        self.loadingLayer.strokeStart = 0.0;
    }
    return self;
}

- (void)layoutSubLayersFrame {
    
    
    self.roundLayer.frame = CGRectMake((CGRectGetWidth(self.bounds) - kRoundSize)/2,  (CGRectGetHeight(self.bounds) - kRoundSize)/2, kRoundSize, kRoundSize);
    self.loadingLayer.frame = CGRectMake((CGRectGetWidth(self.bounds) - kRoundSize)/2,  (CGRectGetHeight(self.bounds) - kRoundSize)/2, kRoundSize, kRoundSize);
}

- (void)displayPrecent:(CGFloat)precent {
    
    
    if (precent < 0) {
    
    
        precent = 0;
    }
    
    if (precent > 1.0) {
    
    
        precent = 1.0;
    }
    
    if (!self.isLoading) {
    
    
        self.loadingLayer.strokeStart = precent;
    }
}

- (void)startAnimation {
    
    
    self.isLoading = YES;
    
    self.loadingLayer.strokeStart = 0.7;
    [self.loadingLayer addAnimation:[self rotateAnimation] forKey:@"rotate"];
}

- (void)stopAnimation {
    
    
    self.isLoading = NO;
    [self.loadingLayer removeAnimationForKey:@"rotate"];
}

- (void)drawContentShapeLayer {
    
    
    // Drawing code
    [self drawLoadingLayer];
    [self drawRoundLayer];
}

- (CAAnimation *)rotateAnimation {
    
    
    //初始化一个动画
    CABasicAnimation *animation = [CABasicAnimation animation];
    //动画运动的方式,现在指定的是围绕Z轴旋转
    animation.keyPath = @"transform.rotation.z";
    //动画持续时间
    animation.duration = 0.5;
    
    //结束的角度
    animation.toValue = [NSNumber numberWithFloat:M_PI*2];
    //动画的运动方式
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    //是否反向移动动画
    
    //重复次数
    animation.repeatCount = MAXFLOAT;

    //动画结束后的状态
    animation.fillMode = kCAFillModeBackwards;
    
    /*
 大意:fillMode的作用就是决定当前对象过了非active时间段的行为. 比如动画开始之前,动画结束之后
 kCAFillModeRemoved 这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态
 kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态
 kCAFillModeBackwards 这个和kCAFillModeForwards是相对的,就是在动画开始前,你只要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始.你可以这样设定测试代码,将一个动画加入一个layer的时候延迟5秒执行.然后就会发现在动画没有开始的时候,只要动画被加入了layer,layer便处于动画初始状态
 kCAFillModeBoth 理解了上面两个,这个就很好理解了,这个其实就是上面两个的合成.动画
     */
    
    //如果为true,动画将在其活动持续时间过后从渲染树中移除。默认为“是”
    animation.removedOnCompletion = NO;

    return animation;
}


#pragma mark - DrawShapeLayer
- (void)drawLoadingLayer {
    
    
    
    UIBezierPath* path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(kRoundSize/2, kRoundSize/2) radius:kRoundSize/2 startAngle:0 endAngle:M_PI*2 clockwise:YES];
    
    UIGraphicsBeginImageContext(self.loadingLayer.frame.size);
    [path stroke];
    UIGraphicsEndImageContext();
    
    self.loadingLayer.path = path.CGPath;
}

- (void)drawRoundLayer {
    
    
    
    UIBezierPath* path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(kRoundSize/2, kRoundSize/2) radius:kRoundSize/2 startAngle:0 endAngle:M_PI*2 clockwise:YES];
    
    UIGraphicsBeginImageContext(self.roundLayer.frame.size);
    [path stroke];
    UIGraphicsEndImageContext();
    
    self.roundLayer.path = path.CGPath;
}

#pragma mark - SETTER/GETTER
- (CAShapeLayer *)roundLayer {
    
    
    if (!_roundLayer) {
    
    
        _roundLayer = [CAShapeLayer layer];
        _roundLayer.backgroundColor = [UIColor clearColor].CGColor;
        //设置线条的宽度和颜色
        _roundLayer.lineWidth = 3.0f;
        _roundLayer.strokeColor = [UIColor colorWithHexString:@"dcdcdc" alpha:1.0].CGColor;
        _roundLayer.fillColor = [UIColor clearColor].CGColor;
        _roundLayer.lineCap     = kCALineCapRound;
    }
    return _roundLayer;
}

- (CAShapeLayer *)loadingLayer {
    
    
    if (!_loadingLayer) {
    
    
        _loadingLayer = [CAShapeLayer layer];
        _loadingLayer.backgroundColor = [UIColor clearColor].CGColor;
        //设置线条的宽度和颜色
        _loadingLayer.lineWidth = 3.0f;
        _loadingLayer.strokeColor = [UIColor colorWithHexString:@"ff7e48" alpha:1.0].CGColor;
        _loadingLayer.fillColor = [UIColor clearColor].CGColor;
        _loadingLayer.lineCap     = kCALineCapRound;
    }
    return _loadingLayer;
}

@end

3.2 MJRefresh uses this animation

I inherit MJRefreshStateHeader here

It is necessary to perform start animation and end animation operations according to the state of the refresh control

The state of the refresh control is as follows

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

INRefreshHeader.h

#import "MJRefresh.h"
#import "INRefreshRoundLoading.h"

@interface INRefreshHeader : MJRefreshStateHeader

@property (nonatomic, assign) BOOL showInsetTop;

@property (nonatomic, strong) INRefreshRoundLoading *roundLoading;

@end

INRefreshHeader.m

@implementation INRefreshHeader

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

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



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

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

            [self.roundLoading stopAnimation];

        } else {
    
    
            [self.roundLoading stopAnimation];

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

    } else if (state == MJRefreshStateRefreshing) {
    
    
        self.roundLoading.alpha = 1.0;
    }
}

- (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.roundLoading.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.roundLoading displayIndicator: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 Specific use of TableView

Need to set the pull-down refresh operation of 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.editView.tableView.mj_header = header;
    self.editView.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.editView.tableView.mj_header endRefreshing];
        [self.editView.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.editView.tableView.mj_header endRefreshing];
        [self.editView.tableView.mj_footer endRefreshing];
    });
}

Four. Summary

iOS development-pull down to refresh animation loading rotation indicator animation effect, use mjrefresh a useful up and down pull down refresh control. Realize the basic effect of CABasicAnimation, and perform animation effects according to different mjrefresh pull-down refresh operations.

Learning records, keep improving every day.

Guess you like

Origin blog.csdn.net/gloryFlow/article/details/131993419