首页 > 代码库 > 下拉刷新原理实现

下拉刷新原理实现

http://blog.csdn.net/kqjob/article/details/9891065#comments

在移动应用开发中,无论是Android还是IOS应用,经常可以看到下拉列表松开后自动刷行数据,在IOS中,使用下拉刷新UITableView中的数据用的非常多,最典型的就是新浪微博的客户端,使用下拉的形式来更新最新的微博信息。


首先请点击下载源码,下载完成后里面有个Demo是可以直接运行的Xcode工程,然后就是这个开源项目的源码,如何使用可以参照Demo,这个EGOTableViewPullRefresh我修改了一部分,并添加了一些注释,主要是支持了中英文版本,原生的只支持英文,我添加了中英文支持,然后就是刷新时间的格式,修改后的格式更直观,原生的是使用SDK自带的时间格式。


这时我第一次写博客,写的不好请见谅哈!


开始进入正题。。。。。

这次主要讲解EGOTableViewPullRefresh下拉的实现原理,并对EGOTableViewPullRefresh源代码进行讲解,至于如何使用EGOTableViewPullRefresh,可以参考我上传的Demo。

UITableView继承之UIScrollView,所以利用UIScrollView滚动的位置属性contentOffset,获取用户下拉的位置contentOffset.y,通过计算用户下拉了多少,来实现下拉刷新的功能。

首先看看下拉列表的组成部分,下拉列表就是UITableView了,在UITableView中添加一个子View,用来显示下拉刷新的状态,我把它叫做HeaderView,HeaderView初始化frame的位置是{0,-60,0,60},所以正常情况下我们看不到HeaderView,当用户下拉列表时HeaderView就会显示出来。第二张图的数值表示的是初始化的时候,不是下拉时的数值,为了方便看到HeaderView,便于理解,所以把列表下拉后标注数值。
 
 
UITableView在顶部时UIScrollView的contentoffset.y=0,在用户下拉滑动列表时,

contentoffset.y为负数增大,

当contentoffset.y <= -65时表示HeaderView已经完全显示出来了,此时HeaderView便显示“松开刷新”

,并把下拉的箭头图标向上。

此时若用户松开手,停止下拉,HeaderView的状态就改为等待数据的状态,如下图所示(下图的contentoffset.y=0标错了,是=-60)

 

以上就是顶部下拉刷新数据的原理了,根据这个原理也就不难写出底部上拉刷新数据的实现了。

 

下面我们来分析下EGOTableViewPullRefresh的代码。

EGOTableViewPullRefresh的代码结构

EGORefreshTableHeaderView.h的代码

 

  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
#import <QuartzCore/QuartzCore.h>
 
//下拉状态
typedef enum{
EGOOPullRefreshPulling = 0,
EGOOPullRefreshNormal,
EGOOPullRefreshLoading,
} EGOPullRefreshState;
 
@protocol EGORefreshTableHeaderDelegate;
@interface EGORefreshTableHeaderView : UIView {
 
id _delegate;
EGOPullRefreshState _state;//下拉状态
 
UILabel *_lastUpdatedLabel;//显示最后更新的时间
UILabel *_statusLabel;//显示下拉状态
CALayer *_arrowImage;//显示下拉图标
UIActivityIndicatorView *_activityView;//等待指示器
 
 
}
 
@property(nonatomic,assign) id <EGORefreshTableHeaderDelegate> delegate;
 
- (id)initWithFrame:(CGRect)frame arrowImageName:(NSString *)arrow textColor:(UIColor *)textColor;
 
//刷新最后更新时间
- (void)refreshLastUpdatedDate;
 
//UIScrollView 滑动时调用该方法
- (void)egoRefreshScrollViewDidScroll:(UIScrollView *)scrollView;
 
//UIScrollView 停止拖拽时调用该方法
- (void)egoRefreshScrollViewDidEndDragging:(UIScrollView *)scrollView;
 
//数据载入完成时调用此方法
- (void)egoRefreshScrollViewDataSourceDidFinishedLoading:(UIScrollView *)scrollView;
 
@end
//下拉委托
@protocol EGORefreshTableHeaderDelegate
//指示开始刷新数据
- (void)egoRefreshTableHeaderDidTriggerRefresh:(EGORefreshTableHeaderView*)view;
//是否正在载入数据
- (BOOL)egoRefreshTableHeaderDataSourceIsLoading:(EGORefreshTableHeaderView*)view;
@optional
//返回最后更新时间
- (NSDate*)egoRefreshTableHeaderDataSourceLastUpdated:(EGORefreshTableHeaderView*)view;
@end
 来自CODE的代码片
EGOTableViewPullRefresh.h


EGORefreshTableHeaderView.m的代码

   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
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
#define TEXT_COLOR [UIColor colorWithRed:87.0/255.0 green:108.0/255.0 blue:137.0/255.0 alpha:1.0]
#define FLIP_ANIMATION_DURATION 0.18f
 
 
@interface EGORefreshTableHeaderView (Private)
 
//设置下拉状态
- (void)setState:(EGOPullRefreshState)aState;
 
@end
 
@implementation EGORefreshTableHeaderView
 
@synthesize delegate=_delegate;
 
 
- (id)initWithFrame:(CGRect)frame arrowImageName:(NSString *)arrow textColor:(UIColor *)textColor {
if((self = [super initWithFrame:frame])) {
//设置UIView为的缩放模式
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.backgroundColor = [UIColor colorWithRed:226.0/255.0 green:231.0/255.0 blue:237.0/255.0 alpha:1.0];
 
//初始化 用来显示最后更新日期的控件
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, frame.size.height - 30.0f, self.frame.size.width, 20.0f)];
label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
label.font = [UIFont systemFontOfSize:12.0f];
label.textColor = textColor;
label.shadowColor = [UIColor colorWithWhite:0.9f alpha:1.0f];
label.shadowOffset = CGSizeMake(0.0f, 1.0f);
label.backgroundColor = [UIColor clearColor];
label.textAlignment = UITextAlignmentCenter;
[self addSubview:label];
_lastUpdatedLabel=label;
[label release];
 
//初始化 用来显示下拉状态的控件
label = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, frame.size.height - 48.0f, self.frame.size.width, 20.0f)];
label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
label.font = [UIFont boldSystemFontOfSize:13.0f];
label.textColor = textColor;
label.shadowColor = [UIColor colorWithWhite:0.9f alpha:1.0f];
label.shadowOffset = CGSizeMake(0.0f, 1.0f);
label.backgroundColor = [UIColor clearColor];
label.textAlignment = UITextAlignmentCenter;
[self addSubview:label];
_statusLabel=label;
[label release];
 
 
//初始化 用来显示指示图标的图层
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(25.0f, frame.size.height - 65.0f, 30.0f, 55.0f);
layer.contentsGravity = kCAGravityResizeAspect;//图像显示模式
layer.contents = (id)[UIImage imageNamed:arrow].CGImage;
 
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
layer.contentsScale = [[UIScreen mainScreen] scale];
}
#endif
 
[[self layer] addSublayer:layer];
_arrowImage=layer;
 
//初始化 等待指示器
UIActivityIndicatorView *view = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
view.frame = CGRectMake(25.0f, frame.size.height - 38.0f, 20.0f, 20.0f);
[self addSubview:view];
_activityView = view;
[view release];
 
 
//初始化下拉状态
[self setState:EGOOPullRefreshNormal];
 
}
 
return self;
 
}
 
- (id)initWithFrame:(CGRect)frame {
return [self initWithFrame:frame arrowImageName:@"blueArrow.png" textColor:TEXT_COLOR];
}
 
#pragma mark -
#pragma mark Setters
 
//更新最后刷新时间
- (void)refreshLastUpdatedDate {
 
if ([_delegate respondsToSelector:@selector(egoRefreshTableHeaderDataSourceLastUpdated:)]) {
 
//通过委托egoRefreshTableHeaderDataSourceLastUpdated:返回时间,并格式化时间
 
NSDate *date = [_delegate egoRefreshTableHeaderDataSourceLastUpdated:self];
 
[NSDateFormatter setDefaultFormatterBehavior:NSDateFormatterBehaviorDefault];
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
// [dateFormatter setDateStyle:NSDateFormatterShortStyle];
// [dateFormatter setTimeStyle:NSDateFormatterShortStyle];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
 
//更新最后显示的时间
NSString *lastUpdated = NSLocalizedString(@"last updated", @"");
_lastUpdatedLabel.text = [NSString stringWithFormat:@"%@ %@",lastUpdated, [dateFormatter stringFromDate:date]];
 
//保存最后刷新的时间,以便下次打开应用的时候还有
[[NSUserDefaults standardUserDefaults] setObject:_lastUpdatedLabel.text forKey:@"EGORefreshTableView_LastRefresh"];
[[NSUserDefaults standardUserDefaults] synchronize];
 
} else {
 
_lastUpdatedLabel.text = nil;
 
}
 
}
 
//设置下拉状态
- (void)setState:(EGOPullRefreshState)aState{
 
switch (aState) {
case EGOOPullRefreshPulling://如果是下拉状态,则更新_statusLabel内容,并把下拉图标旋转180度
 
// _statusLabel.text = NSLocalizedString(@"Release to refresh...", @"Release to refresh status");
_statusLabel.text = NSLocalizedString(@"release to refresh", @"");
[CATransaction begin];
[CATransaction setAnimationDuration:FLIP_ANIMATION_DURATION];
_arrowImage.transform = CATransform3DMakeRotation((M_PI / 180.0) * 180.0f, 0.0f, 0.0f, 1.0f);
[CATransaction commit];
 
break;
case EGOOPullRefreshNormal://普通状态,恢复默认值
 
if (_state == EGOOPullRefreshPulling) {
[CATransaction begin];
[CATransaction setAnimationDuration:FLIP_ANIMATION_DURATION];
_arrowImage.transform = CATransform3DIdentity;//把transform设为默认值
[CATransaction commit];
}
 
// _statusLabel.text = NSLocalizedString(@"Pull down to refresh...", @"Pull down to refresh status");
_statusLabel.text = NSLocalizedString(@"pull down to refresh", @"");
[_activityView stopAnimating];
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
_arrowImage.hidden = NO;
_arrowImage.transform = CATransform3DIdentity;
[CATransaction commit];
 
[self refreshLastUpdatedDate];
 
break;
 
case EGOOPullRefreshLoading://如果是下拉状态,则显示为载入中,显示等待指示器,并隐藏箭头
 
// _statusLabel.text = NSLocalizedString(@"Loading...", @"Loading Status");
_statusLabel.text = NSLocalizedString(@"loading", @"");
[_activityView startAnimating];
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
_arrowImage.hidden = YES;
[CATransaction commit];
 
break;
default:
break;
}
 
_state = aState;
}
 
 
#pragma mark -
#pragma mark ScrollView Methods
 
//UIScrollView 滑动时调用该方法
- (void)egoRefreshScrollViewDidScroll:(UIScrollView *)scrollView {
 
if (_state == EGOOPullRefreshLoading) {//如果正在载入中,则判断如果是上拉则不改变scrollView,如果是下拉,则保持HeaderView在顶部
 
CGFloat offset = MAX(scrollView.contentOffset.y * -1, 0);
offset = MIN(offset, 60);
scrollView.contentInset = UIEdgeInsetsMake(offset, 0.0f, 0.0f, 0.0f);
 
} else if (scrollView.isDragging) {//scrollView停止拖拽时计算下拉状态
 
BOOL _loading = NO;
//委托egoRefreshTableHeaderDataSourceIsLoading:返回用户载入数据的情况
if ([_delegate respondsToSelector:@selector(egoRefreshTableHeaderDataSourceIsLoading:)]) {
_loading = [_delegate egoRefreshTableHeaderDataSourceIsLoading:self];
}
 
//如果现在时上拉状态,并且没有数据在载入中,则恢复默认状态
if (_state == EGOOPullRefreshPulling && scrollView.contentOffset.y > -65.0f && scrollView.contentOffset.y < 0.0f && !_loading) {
[self setState:EGOOPullRefreshNormal];
} //如果当前为默认状态,下拉大于65,并且不是在载入数据状态,则设置为Pull状态
else if (_state == EGOOPullRefreshNormal && scrollView.contentOffset.y < -65.0f && !_loading) {
[self setState:EGOOPullRefreshPulling];
}
if (scrollView.contentInset.top != 0) {
scrollView.contentInset = UIEdgeInsetsZero;
}
 
}
 
}
 
//UIScrollView 停止拖拽时调用该方法
- (void)egoRefreshScrollViewDidEndDragging:(UIScrollView *)scrollView {
 
BOOL _loading = NO;
if ([_delegate respondsToSelector:@selector(egoRefreshTableHeaderDataSourceIsLoading:)]) {
_loading = [_delegate egoRefreshTableHeaderDataSourceIsLoading:self];
}
 
//如果还没有载入数据,则通知载入数据
if (scrollView.contentOffset.y <= - 65.0f && !_loading) {
 
if ([_delegate respondsToSelector:@selector(egoRefreshTableHeaderDidTriggerRefresh:)]) {
[_delegate egoRefreshTableHeaderDidTriggerRefresh:self];
}
 
[self setState:EGOOPullRefreshLoading];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.2];
scrollView.contentInset = UIEdgeInsetsMake(60.0f, 0.0f, 0.0f, 0.0f);
[UIView commitAnimations];
 
}
 
}
 
//数据载入完成是调用此方法
- (void)egoRefreshScrollViewDataSourceDidFinishedLoading:(UIScrollView *)scrollView {
 
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.3];
[scrollView setContentInset:UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f)];
[UIView commitAnimations];
 
[self setState:EGOOPullRefreshNormal];
 
}
 
 
#pragma mark -
#pragma mark Dealloc
 
- (void)dealloc {
 
_delegate=nil;
_activityView = nil;
_statusLabel = nil;
_arrowImage = nil;
_lastUpdatedLabel = nil;
[super dealloc];
}
 
 
@end