我对MJRefresh框架的了解 -> MJRefreshHeader

正如源码中注释的一样,这个类的作用是:负责监控用户下拉的状态;

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.png

2、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.png

4、其它
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、如果自己去打断点执行一遍,思路会更清晰;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 李明杰老师的代表作之一MJRefresh可以说是棒棒的,很多小伙伴都会在没有什么特殊要求的情况下使用这个框架,简单...
    sunxu_cocoa阅读 516评论 0 1
  • 本文转载自J_Knight 的MJRefresh源码解析 MJRefresh是李明杰的作品,到现在已经有9800多...
    Detective41阅读 652评论 0 1
  • MJRefresh是流行的下拉刷新控件,前段时间为了修复一个BUG,读了它的源码,本文总结一下实现的原理 下拉刷新...
    晚安的你我阅读 425评论 0 0
  • 可改进部分 在 MJRefreshComponent.h 的 34 行, typedef void (^MJRef...
    在梦里失眠阅读 510评论 0 0
  • 背景 写这篇文章主要有两个目的: 虽然refresh的源码已经有很多小伙伴分析过了。但是其应用不能说不够广阔。所以...
    MrOreo阅读 1,248评论 0 0