MJRefresh是流行的下拉刷新控件,前段时间为了修复一个BUG,读了它的源码,本文总结一下实现的原理
下拉刷新的基本原理
大部分的下拉刷新控件,都是用contentInset实现的。默认情况下,如果一个UIScrollView的左上角在导航栏的正下方,那么它的contentInset是64,而contentOffset是-64。继续下拉的话,contentOffset就会越来越小,如果上滑,contentOffset就会增大,直到左上角达到屏幕的左上角时,contentOffset刚好为0
默认情况下,如果下拉一个UIScrollView,在松手之后,会弹回初始的位置(导航栏下方)。而大部分的下拉刷新控件,都是将自己放在UIScrollView的上方,起始y设置成负数,所以平时不会显示出来,只有下拉的时候才会出现,放开又会弹回去。然后在loading的时候,临时把contentInset增大,相当于把UIScrollView往下挤,于是下拉刷新的控件就会显示出来,然后刷新完成之后,再把contentInset改回原来的值,实现回弹的效果
基本上,MJRefresh也是这么实现的
创建下拉刷新控件实例
从创建实例的代码开始:
MJRefreshNormalHeader *header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
[myController loadCollectionDataNeedReset:YES withBlock:^{
[self.header endRefreshing];
[self reloadData];
}];
}];
调用的是一个工厂方法headerWithRefreshingBlock,这个方法定义在各种header控件的基类MJRefreshHeader里:
+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
{
MJRefreshHeader *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
}
然后会调用init方法,由于MJRefreshHeader里并没有定义init方法,而它的基类MJRefreshComponent里定义了,所以会进入到基类的初始化方法里:
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
// 准备工作
[self prepare];
// 默认是普通状态
self.state = MJRefreshStateIdle;
}
return self;
}
这里的关键是prepare方法,这个方法是第一个扩展点,具体的header(包括库提供的原生header,和用户自定义的header)有哪些属性,样式是怎么样,都是在这个方法里实现的。每个子类的prepare方法,都会调用父类的prepare方法。所以在扩展的时候,公共的属性写在父类的prepare方法里,特有的属性写在子类的prepare方法里。比如,我们看一下MJRefreshStateHeader的:
- (void)prepare
{
[super prepare];
// 初始化文字
[self setTitle:MJRefreshHeaderIdleText forState:MJRefreshStateIdle];
[self setTitle:MJRefreshHeaderPullingText forState:MJRefreshStatePulling];
[self setTitle:MJRefreshHeaderRefreshingText forState:MJRefreshStateRefreshing];
}
总之,调用headerWithRefreshingBlock方法以后,就得到了一个UIView的实例,也就是下拉