打开开源中国iOS客户端应用程序第一步就是加载数据,经常我们在第二次以后打开的时候,我们界面显示的是上一次更新的数据,此时我们想看最新内容就需要去刷新数据加载这些内容,加载需要一个等待过程,如何能让用户在等待过程中不焦急,能够等待这个过程完成,这就需要给用户一个心里安慰,让用户知道该软件正在很努力很努力的执行自己命令,这就需要我们为自己应用程序添加一些特效;
开源中国iOS客户端用到了不少特效,这些特效在当前很多应用软件中都比较流行,基本上这些特效都属于第三方类库,本次想说的是下拉刷新特效,EGOTableViewPullRefresh最开始是在Twitter中使用,最后做了开源,然后很多应用添加这个特效,常作为加载数据时将等待时间作为一个动画来过渡;
下拉刷新类库EGOTableViewPullRefresh资源文件下载地址:
先这个特效的效果图
在EGOTableViewPullRefresh资源文件中有两个文件,.m和.h文件,还有资源图片,就是下拉刷新箭头
资源图片一共4种色,可以根据喜好选用不同色的箭头,只需在EGORefreshTableHeaderView.m文件中修改一下。按照大小尺寸又可分两种,较大尺寸是用于iPad上使用的。
针对这些第三方类库,我们没必要去深入研究它们内部实现机制原理,只要知道怎么用就可以。不过,看一看别人实现原理,学学别人的方法还是很不错的,了解下人家牛人程序是怎么写的;
EGORefreshTableHeaderView.h
- #import <UIKit/UIKit.h>
- #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;
- - (void)egoRefreshScrollViewDidScroll:(UIScrollView *)scrollView;
- - (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
EGORefreshTableHeaderDelegate;表示声明有这个协议,该协议里面声明了一些方法,只要其他的类遵循了这个协议(也就是遵循了它的规定),就可以去实现协议里面方法,协议里的方法是留给遵循这个协议的类去实现的,也是留给外部实现接口;
EGORefreshTableHeaderView成员变量定义两个label用于提示下拉过程所处状态,和显示的刷新时间。定义的CALayer类对象装载显示图片。UIActivityIndicatorView类对象显示一个等待动画;
@property(nonatomic,assign)id <EGORefreshTableHeaderDelegate> delegate;声明一个协议对象;
接着下面的是EGORefreshTableHeaderView类成员函数,用于实现类库中下拉刷新的效果;
最后定义了4个协议方法,其中最后一个协议方法为可选实现;
下面是EGORefreshTableHeaderView.m文件,想说的都在注释里
- #import "EGORefreshTableHeaderView.h"
- #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])) {
- // self.view自动适应bounds的宽度
- self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
- // self.view背景色和透明度设置
- 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文本阴影颜色
- 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在view上以某种形式适应
- layer.contentsGravity = kCAGravityResizeAspect;
- layer.contents = (id)[UIImage imageNamed:arrow].CGImage;
- // 判断设备版本,因为一些iOS特性是在最后新增的,要求设备配置高一些,所以做一下判断
- #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;
- }
- //初始化当前视图的frame
- - (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:)]) {
- NSDate *date = [_delegate egoRefreshTableHeaderDataSourceLastUpdated:self];
- // NSDateFormatter实例创建字符串,来表示NSDate和NSCalendarDate对象,已预订格式化字符串输出
- [NSDateFormatter setDefaultFormatterBehavior:NSDateFormatterBehaviorDefault];
- NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
- // 设置日期输出格式
- [dateFormatter setDateStyle:NSDateFormatterShortStyle];
- // 设置时间显示格式
- [dateFormatter setTimeStyle:NSDateFormatterShortStyle];
- // _lastUpdatedLabel.text = [NSString stringWithFormat:@"Last Updated: %@", [dateFormatter stringFromDate:date]];
- _lastUpdatedLabel.text = [NSString stringWithFormat:@"最后更新: %@", [dateFormatter stringFromDate:date]];
- // 存储_lastUpdatedLabel.text内容,放到字典中
- [[NSUserDefaults standardUserDefaults] setObject:_lastUpdatedLabel.text forKey:@"EGORefreshTableView_LastRefresh"];
- // 将NSUserDefaults存储数据放到磁盘
- [[NSUserDefaults standardUserDefaults] synchronize];
- } else {
- _lastUpdatedLabel.text = nil;
- }
- }
- - (void)setState:(EGOPullRefreshState)aState{
- switch (aState) {
- /*触摸屏幕下拉状态*/
- case EGOOPullRefreshPulling:
- // _statusLabel.text = NSLocalizedString(@"Release to refresh...", @"Release to refresh status");
- _statusLabel.text = @"松开即可刷新";
- // 设置下拉刷新过程,箭头的图片的一个动画过程
- [CATransaction begin];
- // 动画时间
- [CATransaction setAnimationDuration:FLIP_ANIMATION_DURATION];
- // 下拉刷新箭头一个翻转过程,(M_PI / 180.0)是角度转换为弧度
- _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;
- [CATransaction commit];
- }
- // _statusLabel.text = NSLocalizedString(@"Pull down to refresh...", @"Pull down to refresh status");
- _statusLabel.text = @"下拉可以刷新";
- [_activityView stopAnimating];
- [CATransaction begin];
- // 因为下拉刷新完成好就不需要下拉动画,此时_activityView动画显示
- // 显示事物关闭动画效果 kCFBooleanTrue关闭 kCFBooleanFalse开启
- [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 = @"加载中";
- [_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
- - (void)egoRefreshScrollViewDidScroll:(UIScrollView *)scrollView {
- if (_state == EGOOPullRefreshLoading) {
- 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) {
- BOOL _loading = NO;
- 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];
- } else if (_state == EGOOPullRefreshNormal && scrollView.contentOffset.y < -65.0f && !_loading) {
- [self setState:EGOOPullRefreshPulling];
- }
- // 设置下拉属性scrollView框架恢复初始位置
- if (scrollView.contentInset.top != 0) {
- // A UIEdgeInsets struct whose top, left, bottom, and right fields are all set to the value 0.
- scrollView.contentInset = UIEdgeInsetsZero;
- }
- }
- }
- - (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恢复位置大小
- [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
当我们想使用这个下拉刷新类库的时候,在使用类里声明这个协议<EGORefreshTableHeaderDelegate>,把当前类self交付给下拉刷新库的协议对象,也就是xx.delegate=self;
怎样让其他类来使用这里面效果,这时我们就可以委托另一个类来实现协议的方法。
选中一个协议方法,右键选择Jump to Definition就可以看到哪些类被委托了,怎样使用了这个类的协议方法:
正在学习过程中,错误之处请指正,欢迎交流,共同学习;