iOS开发-下拉刷新动画loading旋转指示器动画效果
之前开发中实现下拉刷新动画loading旋转指示器动画效果
一、效果图
二、基础动画
CABasicAnimation类的使用方式就是基本的关键帧动画。
所谓关键帧动画,就是将Layer的属性作为KeyPath来注册,指定动画的起始帧和结束帧,然后自动计算和实现中间的过渡动画的一种动画方式。
fillMode
大意:fillMode的作用就是决定当前对象过了非active时间段的行为. 比如动画开始之前,动画结束之后
- kCAFillModeRemoved 这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态
- kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态
- kCAFillModeBackwards 这个和kCAFillModeForwards是相对的,就是在动画开始前,你只要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始.你可以这样设定测试代码,将一个动画加入一个layer的时候延迟5秒执行.然后就会发现在动画没有开始的时候,只要动画被加入了layer,layer便处于动画初始状态
- kCAFillModeBoth 理解了上面两个,这个就很好理解了,这个其实就是上面两个的合成.动画
可以查看
https://blog.csdn.net/gloryFlow/article/details/131991202
三、实现代码
3.1 代码实现动
主要实现CABasicAnimation动画,KeyPath是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;
}
完整代码如下
#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使用该动画
我这里继承MJRefreshStateHeader
需要根据刷新控件的状态来执行开启动画与结束动画操作
刷新控件的状态如下
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 具体的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.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];
});
}
四、小结
iOS开发-下拉刷新动画loading旋转指示器动画效果,使用mjrefresh一个好用的上下拉刷新的控件。实现CABasicAnimation基础效果,根据不同的mjrefresh下拉刷新操作来执行动画效果。
学习记录,每天不停进步。