iOS开发-下拉刷新动画依次渐隐渐显Indicator指示器效果
之前开发中实现下拉刷新动画三个球依次渐隐渐显指示器效果。
一、效果图
二、基础动画
CABasicAnimation类的使用方式就是基本的关键帧动画。
所谓关键帧动画,就是将Layer的属性作为KeyPath来注册,指定动画的起始帧和结束帧,然后自动计算和实现中间的过渡动画的一种动画方式。
可以查看
https://blog.csdn.net/gloryFlow/article/details/131991202
三、实现代码
3.1 代码实现动画
主要三个球实现CABasicAnimation动画,KeyPath是opacity
- (CAAnimation *)fadeInAnimation:(CFTimeInterval)delay {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = @(0.2f);
animation.toValue = @(1.0f);
animation.duration = 0.25f + delay;
animation.beginTime = CACurrentMediaTime() + delay;
animation.autoreverses = YES;
animation.repeatCount = HUGE_VAL;
return animation;
}
- (CAAnimation *)scaleFadeInAnimation:(CFTimeInterval)delay {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"];
animation.fromValue = @(1.0f);
animation.toValue = @(0.5f);
animation.duration = 0.25f;
animation.beginTime = CACurrentMediaTime() + delay;
animation.autoreverses = YES;
animation.repeatCount = HUGE_VAL;
return animation;
}
完整代码如下
#import "INRefreshIndicatorView.h"
#import "UIColor+Addition.h"
#import "UIImageView+WebCache.h"
static CGFloat kBallSize = 12.0;
static CGFloat kDistanceCenter = 25.0;
@interface INRefreshIndicatorView ()
@property (nonatomic, strong) UIImageView *contentBGImageView;
@property (nonatomic, strong) UIImageView *aballImageView;
@property (nonatomic, strong) UIImageView *bballImageView;
@property (nonatomic, strong) UIImageView *cballImageView;
@property (nonatomic, assign) BOOL animating;
@end
@implementation INRefreshIndicatorView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.hidesWhenStopped = NO;
[self addSubview:self.contentBGImageView];
[self.contentBGImageView addSubview:self.aballImageView];
[self.contentBGImageView addSubview:self.bballImageView];
[self.contentBGImageView addSubview:self.cballImageView];
[self layoutBallFrame];
}
return self;
}
- (void)layoutBallFrame {
self.aballImageView.center = self.contentBGImageView.center;
self.bballImageView.center = self.contentBGImageView.center;
self.cballImageView.center = self.contentBGImageView.center;
}
#pragma mark - Display
- (void)displayIndicator:(CGFloat)precent {
if (precent > 1.0) {
precent = 1.0;
}
if (precent < 0.0) {
precent = 0.0;
}
CGPoint cBallCenter = self.cballImageView.center;
CGPoint aBallCenter = self.aballImageView.center;
CGPoint bBallCenter = self.bballImageView.center;
aBallCenter.x = cBallCenter.x - precent*kDistanceCenter;
self.aballImageView.center = aBallCenter;
self.aballImageView.transform = CGAffineTransformMakeScale(precent, precent);
bBallCenter.x = cBallCenter.x + precent*kDistanceCenter;
self.bballImageView.center = bBallCenter;
self.bballImageView.transform = CGAffineTransformMakeScale(precent, precent);
}
- (void)startAnimation {
if (_animating) {
return;
}
[self.aballImageView.layer addAnimation:[self scaleFadeInAnimation:0] forKey:@"fadeIn"];
[self.cballImageView.layer addAnimation:[self scaleFadeInAnimation:0.1] forKey:@"fadeIn"];
[self.bballImageView.layer addAnimation:[self scaleFadeInAnimation:0.2] forKey:@"fadeIn"];
self.animating = YES;
}
- (void)stopAnimation {
if (!_animating) {
return;
}
self.animating = NO;
[self.aballImageView.layer removeAnimationForKey:@"fadeIn"];
[self.bballImageView.layer removeAnimationForKey:@"fadeIn"];
[self.cballImageView.layer removeAnimationForKey:@"fadeIn"];
}
- (CAAnimation *)fadeInAnimation:(CFTimeInterval)delay {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = @(0.2f);
animation.toValue = @(1.0f);
animation.duration = 0.25f + delay;
animation.beginTime = CACurrentMediaTime() + delay;
animation.autoreverses = YES;
animation.repeatCount = HUGE_VAL;
return animation;
}
- (CAAnimation *)scaleFadeInAnimation:(CFTimeInterval)delay {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"];
animation.fromValue = @(1.0f);
animation.toValue = @(0.5f);
animation.duration = 0.25f;
animation.beginTime = CACurrentMediaTime() + delay;
animation.autoreverses = YES;
animation.repeatCount = HUGE_VAL;
return animation;
}
#pragma mark - SETTER/GETTER
- (UIImageView *)contentBGImageView {
if (!_contentBGImageView) {
_contentBGImageView = [[UIImageView alloc] initWithFrame:self.bounds];
_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"];
_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"];
_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"];
_cballImageView.layer.cornerRadius = kBallSize/2;
_cballImageView.layer.masksToBounds = YES;
}
return _cballImageView;
}
@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) INRefreshIndicatorView *indicatorView;
@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.indicatorView];
}
return self;
}
- (INRefreshIndicatorView *)indicatorView {
if (!_indicatorView) {
_indicatorView = [[INRefreshIndicatorView alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth([UIScreen mainScreen].bounds), self.bounds.size.height)];
}
return _indicatorView;
}
- (void)setState:(MJRefreshState)state {
MJRefreshCheckState
// 根据状态做事情
if (state == MJRefreshStateIdle) {
if (oldState == MJRefreshStateRefreshing) {
self.indicatorView.alpha = 1.0;
// 如果执行完动画发现不是idle状态,就直接返回,进入其他状态
if (self.state != MJRefreshStateIdle) return;
self.indicatorView.alpha = 1.0;
[self.indicatorView stopAnimation];
} else {
[self.indicatorView stopAnimation];
}
} else if (state == MJRefreshStatePulling) {
[self.indicatorView stopAnimation];
} else if (state == MJRefreshStateRefreshing) {
self.indicatorView.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.indicatorView.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.indicatorView 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开发-下拉刷新动画依次渐隐渐显Indicator指示器效果,使用mjrefresh一个好用的上下拉刷新的控件。实现CABasicAnimation基础效果,根据不同的mjrefresh下拉刷新操作来执行动画效果。
学习记录,每天不停进步。