仿淘宝下拉刷新效果(基于MJRefresh控件封装)

1、先看几张效果图

simulator-screen-shot-2016%e5%b9%b411%e6%9c%8817%e6%97%a5-%e4%b8%8b%e5%8d%883-45-32simulator-screen-shot-2016%e5%b9%b411%e6%9c%8817%e6%97%a5-%e4%b8%8b%e5%8d%883-45-34 simulator-screen-shot-2016%e5%b9%b411%e6%9c%8817%e6%97%a5-%e4%b8%8b%e5%8d%883-45-36

2、设计思路

2.1、对比MJRefresh下拉刷新,类淘宝下拉效果去掉了上下调换箭头及菊花加载效果,添加了动态画圆圈的轨迹动画;
2.2、线上有很多下拉刷新播放动态图的动画效果,那些是通过帧动画来实现,在UIImageView中多张图片快速替换实现;
2.3、上述效果不能控制动画“播放进度”,只能循环播放,无法实现类淘宝“画圈”效果;
2.4、图片无法实现就只能通过程序来动态画,这里涉及到一个关键性的类CAShapeLayer;
2.5、CAShapeLayer有两个重要属性strokeStart与strokeEnd,这两个属性可以实现画线的起始点,支持动画;
2.6、通过贝塞尔曲线画圆圈轨迹,将轨迹赋值给CAShapeLayer;
2.7、在UITableView下拉时,监听下拉距离(MJRefresh已实现)调整画圈的弧度;
2.8、下拉刷新的其他状态通过修改背景小图标来辅助动画效果。

3、关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//
//  SARefreshHeader.m
//  fisher
//
//  Created by 余西安 on 16/1/29.
//  Copyright ? 2016年 Sian. All rights reserved.
//
 
#define SAColor(rgb)  [UIColor colorWithRed:((rgb&0xFF0000)>>16)/255.0 green:((rgb&0xFF00)>>8)/255.0 blue:(rgb&0xFF)/255.0 alpha:1.0]
#import "SARefreshHeader.h"
 
@implementation SARefreshHeader
 
- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]){
        self.stateLabel.font = [UIFont systemFontOfSize:11];
        self.stateLabel.textColor = [UIColor lightGrayColor];
        self.lastUpdatedTimeLabel.font = [UIFont systemFontOfSize:11];
        self.lastUpdatedTimeLabel.textColor = [UIColor lightGrayColor];
        [self setTitle:@"下拉刷新" forState:MJRefreshStateIdle];
        [self setTitle:@"释放刷新" forState:MJRefreshStatePulling];
        [self setTitle:@"更新中..." forState:MJRefreshStateRefreshing];
        [self setValue:@"0" forKeyPath:@"arrowView.alpha"];
    }
    return self;
}
 
// 重新布局子控件
- (void)placeSubviews
{
    [super placeSubviews];
    self.stateLabel.mj_y = MJRefreshHeaderHeight * 0.3;
    self.lastUpdatedTimeLabel.mj_y = MJRefreshHeaderHeight * 0.65;
    CGFloat refreshX = self.mj_w * 0.5 - 40;
    CGFloat refreshY = MJRefreshHeaderHeight * 0.55;
    self.refreshIcon.center = CGPointMake(refreshX, refreshY);
}
 
// RefreshState状态
- (void)setState:(MJRefreshState)state
{
    [super setState:state];
    // 隐藏原控件LoadingView(小菊花效果)
    [self setValue:@(YES) forKeyPath:@"loadingView.hidden"];
    // 不动的状态给出不同的底部图标,辅助动画效果
    switch (state) {
        case MJRefreshStateIdle:{       // 下拉刷新
            self.refreshIcon.image = [UIImage imageNamed:@"icon_public_refresh_normal.png"];
        }break;
        case MJRefreshStatePulling:{    // 释放刷新
            self.refreshIcon.image = [UIImage imageNamed:@"icon_public_refresh_highlight.png"];
        }break;
        case MJRefreshStateRefreshing:{ // 更新中...
            self.refreshIcon.image = [UIImage imageNamed:@"icon_public_refresh_highlight.png"];
        }break;
        default:break;
    }
}
 
// KVO监听ScrollView ContentOffset值
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
    [super scrollViewContentOffsetDidChange:change];
    // 当下拉时执行相关绘图动画
    if (self.state == MJRefreshStateIdle){
        CGFloat distance = self.scrollView.contentOffset.y;
        [self refreshWithDistance:distance];
    }
}
 
// 下拉距离(根据该值来画圆的弧度大小)
- (void)refreshWithDistance:(CGFloat)distance
{
    // 下拉最大距离为常量MJRefreshHeaderHeight,strokeEnd取值0~1
    CGFloat strokeEnd = distance / MJRefreshHeaderHeight;
    if (strokeEnd < 0) strokeEnd *= -1;
    self->_shapeLayer.strokeEnd = strokeEnd;
    [self.shapeLayer setNeedsDisplay];
}
 
// 修复自动刷新时无效果的Bug
- (void)beginRefreshing
{
    [super beginRefreshing];
    self.state = MJRefreshStateRefreshing;
}
 
#pragma mark - 懒加载成员属性
- (UIImageView *)refreshIcon
{
    if (self->_refreshIcon == nil){
        UIImage *image = [UIImage imageNamed:@"icon_public_refresh_normal.png"];
        self->_refreshIcon = [[UIImageView alloc] initWithImage:image];
        [self addSubview:self->_refreshIcon];
    }
    return self->_refreshIcon;
}
// 动画图层
- (CAShapeLayer *)shapeLayer
{
    if (self->_shapeLayer == nil){
        // 创建形状图层,图层大小与覆盖的小图标大小一致
        self->_shapeLayer = [CAShapeLayer layer];
        self->_shapeLayer.frame = self.refreshIcon.bounds;
        //  从0度开始、线度1像素、边框蓝色、内填充无色
        self->_shapeLayer.strokeStart =  0;
        self->_shapeLayer.lineWidth = 1.0f;
        self->_shapeLayer.fillColor = [[UIColor clearColor] CGColor];
        self->_shapeLayer.strokeColor = [SAColor(0x007AFF) CGColor];
        // 动画轨迹通过贝塞尔曲线实现,慢慢调出与图标重叠的轨迹
        CGFloat width = self.refreshIcon.bounds.size.width - 4;
        CGRect rect = CGRectMake(2, 2, width, width);
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
        self->_shapeLayer.path = path.CGPath;
        [self.refreshIcon.layer addSublayer:self->_shapeLayer];
        // 逆时钟旋转90度,使动画从最顶端开始
        [self->_shapeLayer setTransform:CATransform3DMakeRotation(M_PI * -0.5, 0, 0, 1)];
    }
    return self->_shapeLayer;
}
@end

4、Demo下载SARefresh

参考链接:http://www.cocoachina.com/ios/20160711/17007.html

One thought on “仿淘宝下拉刷新效果(基于MJRefresh控件封装)

  1. Sian Post author

    关于要隐藏菊花和小箭头,可以重写父类的懒加载方法简单实现
    // 隐藏原控件LoadingView(小菊花效果)
    – (UIActivityIndicatorView *)loadingView
    {
    return nil;
    }
    – (UIImageView *)arrowView
    {
    return nil;
    }

Leave a Reply