我对MJRefresh框架的了解 -> MJRefresh
正如源码中注释的一样,这个类的作用是:负责监控用户下拉的状态;
pragma mark - 一、在.h文件中,提供了两种类方法实例化对象,分别是带block回调和target响应的方法,用户可根据自身习惯去选择,达到的目的都是相同的。
- (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
{
MJRefreshHeader *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
} - (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action
{
MJRefreshHeader *cmp = [[self alloc] init];
[cmp setRefreshingTarget:target refreshingAction:action];
return cmp;
}
不管是refreshingBlock,还是target和action,在执行beginRefreshing方法之后都有机会回调MJRefreshComponent中的executeRefreshingCallback方法。在executeRefreshingCallback中,都会去判断以及执行回调方法。
pragma mark - 二、MJRefreshHeader通过重写父类的prepare和placeSubviews方法,来做一些基本的设置,代码如下
- (void)prepare
{
[super prepare];
// 设置key
self.lastUpdatedTimeKey = MJRefreshHeaderLastUpdatedTimeKey;
// 设置高度
self.mj_h = MJRefreshHeaderHeight;//这里默认设置为54
} - (void)placeSubviews
{
[super placeSubviews];
// 设置y值(当自己的高度发生改变了,肯定要重新调整Y值,所以放到placeSubviews方法中设置y值)
self.mj_y = - self.mj_h - self.ignoredScrollViewContentInsetTop;
}
pragma mark - 三、MJRefreshHeader的主要作用
1、重写父类scrollViewContentOffsetDidChange方法,以达到监听scrollView的contentOffset发生变化的目的,并做一些实际的操作。
需要指出的地方有:
a、首先检测self.state的状态是否处于正在刷新的状态(也就是否等于枚举MJRefreshStateRefreshing),如果在刷新,那么直接结束,不做任何操作。
b、CGFloat happenOffsetY = - self.scrollViewOriginalInset.top;
疑问:为什么在UITableViewController管理的UITableView (这里我暂时只试了这个,其它情况不清楚)状态下,这个值一直是64,不管怎么上下拖动tableView都不会发生变化。
c、CGFloat offsetY = self.scrollView.mj_offsetY;
疑问:在tableView启动默认状态下,这个值为-64,往上移动时,这个值会变大; 往下移动时,这个值会变小;但其它一般的scrollView,确不是这么变化的。(我没怎么认真研究过tableView的contentOffset的变化情况,涨姿势了)
d、当偏移量(也就是offsetY)值变大时,只要大于happenOffsetY(操作时,这个值至始至终是-64),该方法就直接结束。
e、CGFloat normal2pullingOffsetY = happenOffsetY - self.mj_h;
设置了一个即将刷新的临界值,因为happenOffsetY至始至终为-64,self.mj_h在prepare方法中设置为54,所以这个临界值为-118 。
f、 CGFloat pullingPercent = (happenOffsetY - offsetY) / self.mj_h;
计算拉伸的比例,当offsetY为-64时,也就是tableView启动默认状态下的情况,pullingPercent值是为0的;
往上拖动时,由于offsetY变大,pullingPercent会为负数;
往下拖动时,offsetY变小,当offsetY在-64和-118中间时,pullingPercent是一个小数值。 当offsetY小于-118时,pullingPercent就会大于1 。
g、self.scrollView.isDragging判断该scrollView(就是MJRefreshComponent的父类)是否在拖动状态。
源码:if (self.scrollView.isDragging) { // 如果正在拖拽
NSLog(@"isDragging");
self.pullingPercent = pullingPercent;
if (self.state == MJRefreshStateIdle && offsetY < normal2pullingOffsetY) {
// 转为即将刷新状态
self.state = MJRefreshStatePulling;
} else if (self.state == MJRefreshStatePulling && offsetY >= normal2pullingOffsetY) {
// 转为普通状态
self.state = MJRefreshStateIdle;
}
}
else if (self.state == MJRefreshStatePulling) {// 即将刷新 && 手松开
// 开始刷新
[self beginRefreshing];
}
else if (pullingPercent < 1) {
self.pullingPercent = pullingPercent;
}
为真,在拖动状态:
如果偏移量offsetY值小于(注意往下拉是负数)临界点normal2pullingOffsetY(也就是-118)时,同时这个时候的self.state等于空闲状态时(也就是MJRefreshStateIdle),那么将state置位刷新状态(也就是MJRefreshStatePulling);
就这样,当该scrollView在拖动的时候,self.state来回在MJRefreshStatePulling 和 MJRefreshStateIdle之间切换,并且相应的执行self.state的setter方法(后面会对setter方法有进一步分析);
为假,放手了,不在拖动状态:
如果self.state等于MJRefreshStatePulling状态,放手就开始执行beginRefreshing方法。
源码:scrollViewContentOffsetDidChange方法代码如下:
屏幕快照 2015-09-24 下午4.17.11.png2、MJRefreshComponent的beginRefreshing方法,代码如下:
- (void)beginRefreshing
{
[UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
self.alpha = 1.0;
}];
self.pullingPercent = 1.0;
// 只要正在刷新,就完全显示
NSLog(@"self.window->%@",self.window);
if (self.window) {
self.state = MJRefreshStateRefreshing;
} else {
NSLog(@"else /self.window->%@",self.window);
self.state = MJRefreshStateWillRefresh;
// 刷新(预防从另一个控制器回到这个控制器的情况,回来要重新刷新一下)
[self setNeedsDisplay];
}
}
在前文提到的放手就开始执行beginRefreshing方法,内部就再次对self.state进行MJRefreshStateRefreshing赋值,执行self.state的setter方法。
疑问:为什么在刷新的时候跳转到另外一个控制器self.window会成为空,而且还会第二次执行beginRefreshing方法,从而将self.state赋值为MJRefreshStateWillRefresh。
3、根据scrollViewContentOffsetDidChange内部执行的操作,来设置MJRefreshState的状态。MJRefreshHeader内部有重写父类中state的setter方法,
a、注意点这两句代码:MJRefreshState oldState = self.state; if (state == oldState) return;
一开始我没理解这个逻辑,后来一想,如果在前面加一段_state = state;那么这就是我最初理解的了。只能说MJ让我涨姿势了~~~
b、下拉刷新时,当执行了beginRefreshing方法,内部将self.state设置为MJRefreshStateRefreshing,并且调用self.state的setter方法,这个时候执行这串代码:
[UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
// 增加滚动区域
CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
self.scrollView.mj_insetT = top;//top值将会为118
// 设置滚动位置
self.scrollView.mj_offsetY = - top;
} completion:^(BOOL finished) {
[self executeRefreshingCallback];
}];
这里修改UIScrollView的contentInset属性,通过增加UIScrollView额外的滚动区域来达到显示的效果。
同时,将contentOffset的Y值设置为-118;
c、当下拉刷新完成时,假如执行了endRefreshing操作,内部会通过setter方法将state置位MJRefreshStateIdle,这个时候就会执行这串代码:
// 保存刷新时间
[[NSUserDefaults standardUserDefaults] setObject: [NSDate date] forKey:self.lastUpdatedTimeKey];
[[NSUserDefaults standardUserDefaults] synchronize];
// 恢复inset和offset
[UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
self.scrollView.mj_insetT -= self.mj_h;
// 自动调整透明度
if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
} completion:^(BOOL finished) {
self.pullingPercent = 0.0;
}];
重新将UIScrollView的contentInset属性设置位最初的值(对于一般状态的UITableView也就是64);并且保存了最后结束刷新的时间 和 self的透明度。
源代码如下:
屏幕快照 2015-09-24 下午4.16.37.png4、其它
a、对象调用方法永远都是从自己的方法列表中去寻找,当找不到的时候,才会去父类寻找方法。MJ很好的灵活运用了这个机制;
b、@property ( nonatomic ) UIEdgeInsets contentInset; 这个属性能够在UIScrollView的4周增加额外的滚动区域 ;
c、MJRefreshState oldState = self.state; if (state == oldState) return;之所以能拿到之前的state状态,通过state的getter方法来获取,这个时候属性_state并没有被更改;如果在这两句代码之前加上_state = state , 那么情况将会完全不一样;
d、如果自己去打断点执行一遍,思路会更清晰;