弹性下拉刷新控件PMElasticRefresh
PMElasticRefresh
一款弹性动画的下拉刷新控件,已经发布在GitHub上了,可自行下载。
介绍
PMElasticRefresh
写这个控件是因为公司设计某一天发给我一张Gif效果图并对我说:“来小伙子,咱们玩点好玩的。”表示彻底被骗了,我以为可以出去浪了,说好的好玩的呢。。。
言归正传,先看下效果图吧:

结构
在tableView上添加一个PMElasticView,在这个View上进行动画的绘制。
PMElasticView上的动画绘制分为三部分:
1> 第一部分为下拉时的天蓝色背景,下拉超过动画最大设定距离,抬手时最下面的线条会有回弹效果。
2> 第二部分为原型白色小球的上升动画。
3> 第三部分为环绕白色小球的圆环动画。
实现
下拉背景
tableView上添加PMElasticView是通过runtime运行时进行添加。将当前的tableView绑定到PMElasticView上的bindingScrollView上使用KVO监听contentOffset。通过使用UIBezierPath进行绘制
- (CGPathRef)calculateAnimaPathWithOriginY:(CGFloat)originY {
CGPoint topLeftPoint = CGPointMake(0,0);
CGPoint bottomLeftPoint = CGPointMake(0, self.offSet_Y <= AnimationDISTANCE ? 100 : originY);
CGPoint controlPoint = CGPointMake(self.bindingScrollView.bounds.size.width * .5, originY);
CGPoint bottomRightPoint = CGPointMake(self.bindingScrollView.bounds.size.width, self.offSet_Y <= AnimationDISTANCE ? 100 : originY);
CGPoint topRightPoint = CGPointMake(self.bindingScrollView.bounds.size.width, 0);
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:topLeftPoint];
[bezierPath addLineToPoint:bottomLeftPoint];
[bezierPath addQuadCurveToPoint:bottomRightPoint controlPoint:controlPoint];
[bezierPath addLineToPoint:topRightPoint];
[bezierPath addLineToPoint:topLeftPoint];
return bezierPath.CGPath;
}
controlPoint用于控制产生弧形线条。当达到最大动画距离时,bottomLeftPoint和bottomRightPoint两个点Y坐标恒定等于100。controlPoint的Y坐标继续跟随下拉距离变化。
松手后KVO中判断isDrag
,若为NO,则调用
- (void)elasticLayerAnimation {
self.ballLayer.hidden = NO;
self.lineLayer.hidden = NO;
self.elasticShaperLayer.path = [self calculateAnimaPathWithOriginY:ABS(AnimationDISTANCE)];
NSArray *pathValues = @[
(__bridge id)[self calculateAnimaPathWithOriginY:ABS(self.offSet_Y)],
(__bridge id)[self calculateAnimaPathWithOriginY:ABS(AnimationDISTANCE) * 0.7],
(__bridge id)[self calculateAnimaPathWithOriginY:ABS(AnimationDISTANCE) * 1.3],
(__bridge id)[self calculateAnimaPathWithOriginY:ABS(AnimationDISTANCE) * 0.9],
(__bridge id)[self calculateAnimaPathWithOriginY:ABS(AnimationDISTANCE) * 1.1],
(__bridge id)[self calculateAnimaPathWithOriginY:ABS(AnimationDISTANCE)]
];
CAKeyframeAnimation *elasticAnimation = [CAKeyframeAnimation animationWithKeyPath:@"path"];
elasticAnimation.values = pathValues;
elasticAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
elasticAnimation.duration = 1;
elasticAnimation.fillMode = kCAFillModeForwards;
elasticAnimation.removedOnCompletion = NO;
elasticAnimation.delegate = self;
[self.elasticShaperLayer addAnimation:elasticAnimation forKey:@"elasticAnimation"];
[self.ballLayer startAnimation];
[self.lineLayer startAnimation];
}
自定义Layer动画完成毕竟的弹性回调效果,(__bridge id)用于ARC下连接OC对象和CoreFoundation对象的桥接。
圆形小球
小球通过UIBezierPath绘制
- (void)configShape {
self.hidden = YES;
CGPoint arcCenterPoint = CGPointMake(self.frame.size.width * .5, self.animationHeight + self.bounds.size.height * .5);
CGFloat arcRadius = self.frame.size.width * .5;
CGFloat arcStartAngle = 0;
CGFloat arcEndAngle = M_PI * 2;
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:arcCenterPoint radius:arcRadius startAngle:arcStartAngle endAngle:arcEndAngle clockwise:YES];
self.path = bezierPath.CGPath;
self.fillColor = self.ballColor.CGColor;
self.strokeEnd = 1;
}
动画是通过CoreAnimation实现,刷新开始时从下往上移动,结束刷新从上往下移动并隐藏。
圆环
同样也是通过UIBezierPath绘制
- (void)configShape {
CGPoint arcCenterPoint = CGPointMake(self.frame.size.width * .5, self.frame.size.height * .5);
CGFloat arcRadius = self.frame.size.width * .5 * 1.15;
CGFloat arcStartAngle = -M_PI_2;
CGFloat arcEndAngle = M_PI * 2 - M_PI_2 + M_PI / 8.0;
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:arcCenterPoint radius:arcRadius startAngle:arcStartAngle endAngle:arcEndAngle clockwise:YES];
self.path = bezierPath.CGPath;
self.fillColor = nil;
self.strokeColor = self.arcStrokeColor.CGColor;
self.lineWidth = 3.0;
self.lineCap = kCALineCapRound;
self.strokeStart = 0;
self.strokeEnd = 0;
self.hidden = YES;
}
动画为组合动画,一个旋转效果、一个控制stroke的动画。
- (void)startAnimation {
self.hidden = NO;
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotateAnimation.fromValue = @(0);
rotateAnimation.toValue = @(2 * M_PI);
rotateAnimation.duration = 1;
rotateAnimation.fillMode = kCAFillModeForwards;
rotateAnimation.removedOnCompletion = NO;
rotateAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
rotateAnimation.repeatCount = HUGE;
[self addAnimation:rotateAnimation forKey:@"rotateAnimation"];
[self strokeEndAnimation];
}
strokeEnd动画:
- (void)strokeEndAnimation {
CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeEndAnimation.fromValue = @(0);
strokeEndAnimation.toValue = @(.95);
strokeEndAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
strokeEndAnimation.duration = 2;
strokeEndAnimation.repeatCount = 1;
strokeEndAnimation.fillMode = kCAFillModeForwards;
strokeEndAnimation.removedOnCompletion = NO;
strokeEndAnimation.delegate = self;
[self addAnimation:strokeEndAnimation forKey:@"strokeEndAnimation"];
}
然后通过代码方法,当strokeEnd动画结束后执行strokeStart动画。
- (void)strokeStartAnimation {
CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
strokeStartAnimation.fromValue = @(0);
strokeStartAnimation.toValue = @(.95);
strokeStartAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
strokeStartAnimation.duration = 2;
strokeStartAnimation.repeatCount = 1;
strokeStartAnimation.fillMode = kCAFillModeForwards;
strokeStartAnimation.removedOnCompletion = NO;
strokeStartAnimation.delegate = self;
[self addAnimation:strokeStartAnimation forKey:@"strokeStartAnimation"];
}
代理方法中控制动画:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
if (flag) {
CABasicAnimation *basicAnimation = (CABasicAnimation *)anim;
if ([basicAnimation.keyPath isEqualToString:@"strokeEnd"]) {
[self strokeStartAnimation];
} else if ([basicAnimation.keyPath isEqualToString:@"strokeStart"]){
[self removeAnimationForKey:@"strokeStartAnimation"];
[self removeAnimationForKey:@"strokeEndAnimation"];
[self strokeEndAnimation];
}
}
}
使用
使用非常简单
导入#import "PMElasticRefresh.h"头文件后调用方法向tableView添加动画
[self.mainTableView pm_RefreshHeaderWithBlock:^{
NSLog(@"refreshBlock");
}];
完成数据读取,结束刷新动画时调用方法
[self.mainTableView endRefresh];
总结
附上GitHub地址:PMElasticRefresh,下载运行即可。