下载开源框架的地址:https://github.com/enormego/EGOTableViewPullRefresh
一,为什么使用EGORefreshTableHeaderView框架
应用中如果要展示大量数据列表,肯定不会一次都加载进来的,常规的方法都是从服务器翻页请求,每次请求n条,用户选择加载更多的时候再请求n条,如果使用下拉刷新的话就会方便很多,用户体验也会好。现在下拉刷新应该很多人都采用了EGORefreshTableHeaderView,具体的UI效果当然会根据自己产品的设计,再进行修改。
例如:QQ空间客户端,新浪微博客服端,我们就是使用下拉刷新方式来更新空间数据。所以这次主要是对EGORefreshTableHeaderView下拉刷新原理进行分析。
UITableView继承于UIScrollView,所以可以使用UIScrollView中y的位移量contentOffset.y 计算用户当前下拉的位置,从而判断是否开始刷新数据。
首先在TableView视图中,我们首先给视图添加一个view用来显示下拉刷新的状态,我们可以把它叫做refreshHeardView。在普通状态下,我们看不到refreshHeardView,因此我们可以将其frame设置到屏幕以外;当用户下拉列表时refreshHeardView就会一点一点显示出来。
普通状态下,contentOffset.y = 0;当用户下拉时,contentOffset.y的值不断变小,refreshHeardView上显示的箭头朝下,内容为:下拉加载.......;当contentOffset.y <= -65 -offset.Y时,refreshHeardView上拉控件就会完全显示出来,此时箭头朝上,变为:加载QQ空间; 此时放手,refreshHeardView上内容变为:正在加载...........;效果图如 下图所示
二,EGORefreshTableHeaderView的原理
下面在根据代码将解他的原理 :
1.首先是在初始化的时候创建我们所需要的视图view中的各个控件。_lastUpdateLabel用来显示时间;_statusLabel用来显示下拉的状态;_arrowImage用来显示箭头;_activityView是指示等待器,当加载数据是显示。
- (id)initWithFrame:(CGRect)frame {
if (self = [superinitWithFrame:frame])
{
//自动调整自己的宽度,保证与superView左边和右边的距离不变。
self.autoresizingMask =UIViewAutoresizingFlexibleWidth;
self.backgroundColor = [UIColorcolorWithRed:226.0/255.0 green:231.0/255.0 blue:237.0/255.0 alpha:1.0];
//这是最后刷新时间的label
UILabel *label = [[UILabelalloc]initWithFrame:CGRectMake(0.0f, frame.size.height - 30.0f,self.frame.size.width, 20.0f)];
label.autoresizingMask =UIViewAutoresizingFlexibleWidth;
label.font = [UIFontsystemFontOfSize:12.0f];
label.textColor =TEXT_COLOR;
label.backgroundColor = [UIColorclearColor];
label.textAlignment =NSTextAlignmentCenter;
[self addSubview:label];
_lastUpdatedLabel=label;
[label release];
//这里是显示状态的label
label = [[UILabelalloc]initWithFrame:CGRectMake(0.0f, frame.size.height - 48.0f,self.frame.size.width, 20.0f)];
label.autoresizingMask =UIViewAutoresizingFlexibleWidth;
label.font = [UIFontboldSystemFontOfSize:13.0f];
label.textColor =TEXT_COLOR;
label.backgroundColor = [UIColorclearColor];
label.textAlignment =NSTextAlignmentCenter;
[self addSubview:label];
_statusLabel=label;
[label release];
//创建下拉刷新的图标
CALayer *layer = [CALayerlayer];
layer.frame =CGRectMake(25.0f, frame.size.height - 65.0f, 30.0f, 55.0f);
//设置layer在view上以某种形式适应
layer.contentsGravity =kCAGravityResizeAspect;
layer.contents = (id)[UIImageimageNamed:@"blackArrow.png"].CGImage;
//这里是判断系统版本,来设置layer的属性
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000//条件编译
if ([[UIScreenmainScreen]respondsToSelector:@selector(scale)]) {
layer.contentsScale = [[UIScreenmainScreen]scale];
}
#endif
[[selflayer]addSublayer:layer];
_arrowImage=layer;
//创建一个旋转的视图
UIActivityIndicatorView *view = [[UIActivityIndicatorViewalloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
view.frame =CGRectMake(25.0f, frame.size.height - 38.0f, 20.0f, 20.0f);
[self addSubview:view];
_activityView = view;
[view release];
//UI弄完后设置纬初始状态
[selfsetState:EGOOPullRefreshNormal];
}
return self;
}
2.下面代码用来显示最后的更新时间
- (void)refreshLastUpdatedDate {
if ([_delegate respondsToSelector:@selector(egoRefreshTableHeaderDataSourceLastUpdated:)]) {
NSDate *date = [_delegate egoRefreshTableHeaderDataSourceLastUpdated:self];
/*,相应的NSDateFormatter就相当于是NSDate的转换类,将NSDate转换为另一种格式,或转换回来。NSDate没有自己的输出,需要借助NSDateFormatter以相应格式输出。这差不多就是NSDateFormatter的作用了吧。*/
//实例化一个对象
// NSDateFormatter实例创建字符串,来表示NSDate和NSCalendarDate对象,已预 订格式化字符串输出
NSDateFormatter *formatter = [[NSDateFormatteralloc]init];
[formattersetAMSymbol:@"AM"];
[formattersetPMSymbol:@"PM"];
//设置时间的格式
[formatter setDateFormat:@"MM/dd/yyyy HH:mm"];
_lastUpdatedLabel.text = [NSStringstringWithFormat:@"上次刷新: %@", [formatter stringFromDate:date]];
//存储_lastUpdatedLabel.text内容,放到字典中
[[NSUserDefaultsstandardUserDefaults] setObject:_lastUpdatedLabel.textforKey:@"EGORefreshTableView_LastRefresh"];
//将NSUserDefaults存储数据放到磁盘
[[NSUserDefaultsstandardUserDefaults]synchronize];
[formatterrelease];
}else {
_lastUpdatedLabel.text =nil;
}
}
3. 然后是设置当前视图的状态;首先在.h文件中定义一个枚举
typedef enum{
//松开即可刷新
EGOOPullRefreshPulling = 0,
//下拉即可刷新
EGOOPullRefreshNormal,
//载入中
EGOOPullRefreshLoading,
} EGOPullRefreshState;
4.并且定义个状态属性——state并给设置一个set方法,初始化时_state=EGOOPullRefreshNormal。当_state的值变换时,视图中各个控件也相应地变换,
- (void)setState:(EGOPullRefreshState)aState{
switch (aState) {
/*触摸屏幕下拉状态*/
caseEGOOPullRefreshPulling:
_statusLabel.text =NSLocalizedString(@"松开即可刷新...",@"Release to refresh status");
//设置下拉刷新过程,箭头的图片的一个动画过程
[CATransactionbegin];
//动画时间
[CATransactionsetAnimationDuration:FLIP_ANIMATION_DURATION];
//下拉刷新箭头一个翻转过程,(M_PI / 180.0)是角度转换为弧度
_arrowImage.transform =CATransform3DMakeRotation((M_PI / 180.0) * 180.0f, 0.0f, 0.0f, 1.0f);
//动画结束
[CATransactioncommit];
break;
/*刚开始触摸屏幕准备下拉的时候的状态*/
caseEGOOPullRefreshNormal:
if (_state ==EGOOPullRefreshPulling) {
[CATransactionbegin];
//因为下拉刷新完成好就不需要下拉动画,此时_activityView动画显示
//显示事物关闭动画效果 kCFBooleanTrue关闭 kCFBooleanFalse开启
[CATransactionsetAnimationDuration:FLIP_ANIMATION_DURATION];
_arrowImage.transform =CATransform3DIdentity;
[CATransactioncommit];
}
_statusLabel.text =NSLocalizedString(@"下拉可以刷新...",@"Pull down to refresh status");
[_activityViewstopAnimating];
[CATransactionbegin];
[CATransactionsetValue:(id)kCFBooleanTrueforKey:kCATransactionDisableActions];
_arrowImage.hidden =NO;
_arrowImage.transform =CATransform3DIdentity;
[CATransactioncommit];
//更新下时间
[selfrefreshLastUpdatedDate];
break;
/*触摸手指松开,完成下拉操作的状态*/
caseEGOOPullRefreshLoading:
_statusLabel.text =NSLocalizedString(@"载入中...",@"Loading Status");
[_activityViewstartAnimating];
[CATransactionbegin];
[CATransactionsetValue:(id)kCFBooleanTrueforKey:kCATransactionDisableActions];
_arrowImage.hidden =YES;
[CATransactioncommit];
break;
default:
break;
}
_state = aState;
}
//开始更新的方法
- (void)egoRefreshScrollViewDidScroll:(UIScrollView *)scrollView;
//当停止拖拽时调用
- (void)egoRefreshScrollViewDidEndDragging:(UIScrollView *)scrollView;
//数据加载完成后调用此方法
- (void)egoRefreshScrollViewDataSourceDidFinishedLoading:(UIScrollView *)scrollView;
6. 当用户刚开始滑动调用第一个方法,将_loading设置为no;此时处于视图为“下拉加载”状态;此时用户滑动位移值contentOffset.y大于-65-offsetY小于0;当contentOffset.y<-65-offsetY时_state由EGOOPullRefreshNormal变为EGOOPullRefreshPulling;此时视图为“加载新微博”;但两种状态只有在_loading为NO时互相转换,因为处于加载时视图是不能变化的;当ontentOffset.y<-65-offsetY并停止拖拽时调用第二个方法,将_statue的值变为EGOOPullRefreshLoading;此时处于加载状态,为了防止在加载时滑动影响加载状态,可以用_loading 约束,当_loading 为YES时以上操作都不能执行;因为加载的完成不能由该类中控制,所以_loading指的设置和获取可以通过
设置代理来实现,因此在.h设置了一个代理。
//定义协议方法
@protocol EGORefreshTableHeaderDelegate
//实现数据刷新
- (void)egoRefreshTableHeaderDidTriggerRefresh:(EGORefreshTableHeaderView*)view;
//判断刷新状态情况,正在刷新或者是没刷新
- (BOOL)egoRefreshTableHeaderDataSourceIsLoading:(EGORefreshTableHeaderView*)view;
@optional
//返回刷新时间,回调方法
- (NSDate*)egoRefreshTableHeaderDataSourceLastUpdated:(EGORefreshTableHeaderView*)view;
@end
//数据加载完成后调用此方法
- (void)egoRefreshScrollViewDataSourceDidFinishedLoading:(UIScrollView *)scrollView {
//设置表示图的填充距离为0,即隐藏了下拉视图
[UIViewbeginAnimations:nilcontext:NULL];
[UIViewsetAnimationDuration:.3];
//数据加载完成后,scrollView恢复位置大小
[scrollViewsetContentInset:UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f)];
[UIViewcommitAnimations];
// 数据加载完成,
[selfsetState:EGOOPullRefreshNormal];
}