废话不多说开始上代码了。首先创建一个CYXRefreshHeader
@interface CYXRefreshHeader : UIView
/*刷新block*/
@property (nonatomic,strong) void(^refresh)(void);
/*开始刷新*/
-(void)startRefresh;
/*结束刷新*/
-(void)endRefresh;
@end
初始化三个点的Layer
-(instancetype)init{
if (self = [super init]) {
self.backgroundColor = [UIColor whiteColor];
self.frame = CGRectMake(0, HeaderHeight, HeaderWidth, HeaderHeight);
[self.layer addSublayer:self.firstPointLayer];
[self.layer addSublayer:self.secondPointLayer];
[self.layer addSublayer:self.thirdPointLayer];
self.firstPointLayer.frame = CGRectMake(HeaderWidth/2-RefreshArcRadius, HeaderHeight/2-RefreshArcRadius, RefreshArcRadius*2, RefreshArcRadius*2);
self.secondPointLayer.frame = CGRectMake(HeaderWidth/2-RefreshArcRadius, HeaderHeight/2-RefreshArcRadius, RefreshArcRadius*2, RefreshArcRadius*2);
self.thirdPointLayer.frame = CGRectMake(HeaderWidth/2-RefreshArcRadius, HeaderHeight/2-RefreshArcRadius, RefreshArcRadius*2, RefreshArcRadius*2);
self.firstPointLayer.path = [self pointPath].CGPath;
self.secondPointLayer.path = [self pointPath].CGPath;
self.thirdPointLayer.path = [self pointPath].CGPath;
}
return self;
}
#pragma mark ---Path
-(UIBezierPath *)pointPath{
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(RefreshArcRadius, RefreshArcRadius) radius:RefreshArcRadius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
return path;
}
在父视图改变的时候设置监听scrollview的滑动偏移量
/*父视图改变的时候*/
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
if ([newSuperview isKindOfClass:[UIScrollView class]]) {
self.scrollView = (UIScrollView *)newSuperview;
self.centerX = self.scrollView.width/2;
self.bottom = 0;
[self.scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
}else {
[self.superview removeObserver:self forKeyPath:@"contentOffset"];
}
}
这里- (void)willMoveToSuperview:(UIView *)newSuperview 方法的调用时机:
当自己重写一个UIView的时候有可能用到这个方法,当本视图的父类视图改变的时候,系统会自动的执行这个方法.newSuperview是本视图的新父类视图.newSuperview有可能是nil.
在监听偏移量的方法里实现随着偏移量三个点的变化:
#pragma mark ---Kvo
/*监听偏移量*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"contentOffset"]) {
self.offSet = self.scrollView.contentOffset.y;
[self setOffSetUI];
}
}
/*设置*/
-(void)setOffSetUI{
//如果到达临界点,则执行刷新动画
if (!self.isAnimating&&self.offSet<=0) {
CGFloat scale = -self.offSet/RefreshFullOffset;
if (scale>1) {scale=1;}
if (scale<0) {scale=0;}
CGFloat centerX = HeaderWidth/2;
CGFloat maxLeftX = centerX-RefreshMaxWidth;
CGFloat maxLeftDistance = centerX - maxLeftX;
CGFloat maxRightX = centerX+RefreshMaxWidth;
CGFloat maxRightDistance = maxRightX - centerX;
self.firstPointLayer.frame = CGRectMake(centerX-maxLeftDistance*scale-RefreshArcRadius, HeaderHeight/2-RefreshArcRadius, RefreshArcRadius*2, RefreshArcRadius*2);
self.thirdPointLayer.frame = CGRectMake(centerX+maxRightDistance*scale-RefreshArcRadius, HeaderHeight/2-RefreshArcRadius, RefreshArcRadius*2, RefreshArcRadius*2);
CGFloat topY =-(RefreshFullOffset/2-RefreshFullOffset/2.0*(1.0-scale))-RefreshArcRadius*2;//y坐标的变化
self.top = topY;
}
if (-self.offSet >= RefreshFullOffset && !self.isAnimating && !self.scrollView.dragging) {
//刷新
[self startAnimation];
if (self.refresh) {
self.refresh();
}
}
}
再达到临界值的时候执行刷新动作并执行刷新动画:
/*执行动画*/
-(void)startAnimation{
self.isAnimating = YES;
[UIView animateWithDuration:0.5 animations:^{
UIEdgeInsets inset = self.scrollView.contentInset;
inset.top = RefreshFullOffset;
self.scrollView.contentInset = inset;
}];
CAKeyframeAnimation * animation = [self opacityAnimation];
[self.firstPointLayer addAnimation:animation forKey:@"opacity"];
animation = [self opacityAnimation];
animation.beginTime = CACurrentMediaTime()+KeyAnimationDuration/2;
[self.secondPointLayer addAnimation:animation forKey:@"opacity"];
animation = [self opacityAnimation];
animation.beginTime = CACurrentMediaTime()+KeyAnimationDuration;
[self.thirdPointLayer addAnimation:animation forKey:@"opacity"];
}
-(CAKeyframeAnimation *)opacityAnimation{
CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
animation.duration = KeyAnimationDuration;
animation.repeatCount = HUGE_VALF;
animation.fillMode = kCAFillModeForwards;
animation.duration = KeyAnimationDuration*2;
animation.values = @[[NSNumber numberWithFloat:1.0f],
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:1.0f],
[NSNumber numberWithFloat:1.0f]];
return animation;
}
最后再实现结束刷新的方法即可:
-(void)endRefresh{
[UIView animateWithDuration:0.5 animations:^{
UIEdgeInsets inset = self.scrollView.contentInset;
inset.top = 0.f;
self.scrollView.contentInset = inset;
} completion:^(BOOL finished) {
[self stopAnimation];
[self initLayerFrame];
}];
}
/*初始化layer坐标*/
-(void)initLayerFrame{
self.firstPointLayer.frame = CGRectMake(HeaderWidth/2-RefreshArcRadius, HeaderHeight/2-RefreshArcRadius, RefreshArcRadius*2, RefreshArcRadius*2);
self.secondPointLayer.frame = CGRectMake(HeaderWidth/2-RefreshArcRadius, HeaderHeight/2-RefreshArcRadius, RefreshArcRadius*2, RefreshArcRadius*2);
self.thirdPointLayer.frame = CGRectMake(HeaderWidth/2-RefreshArcRadius, HeaderHeight/2-RefreshArcRadius, RefreshArcRadius*2, RefreshArcRadius*2);
}
/*停止动画*/
-(void)stopAnimation{
[UIView animateWithDuration:0.5 animations:^{
UIEdgeInsets inset = self.scrollView.contentInset;
inset.top = 0.f;
self.scrollView.contentInset = inset;
} completion:^(BOOL finished) {
[self.thirdPointLayer removeAllAnimations];
[self.firstPointLayer removeAllAnimations];
[self.secondPointLayer removeAllAnimations];
self.isAnimating = NO;
}];
}
本文借鉴:https://www.jianshu.com/p/3c51e4896632
demo:https://github.com/SionChen/CYXBossRefreshDemo/tree/master 欢迎讨论