在移动应用开发中,无论是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的代码
#import
//下拉状态
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
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
EGORefreshTableHeaderView.m的代码
#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
这是我发的处女文章,写的不好请见谅哈,如果觉得还可以,就转载下哈。