iOS开发iOS技术专题iOS开发技术分享

弹性下拉刷新控件PMElasticRefresh

2016-05-10  本文已影响788人  a1b1aa041abd

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,下载运行即可。

上一篇 下一篇

猜你喜欢

热点阅读