MJRefresh是一款非常优秀的刷新控件。代码简洁,优雅。今天有时间对源代码阅读了一下。对MJRefresh的宏观设计非常赞叹。所谓大道至简就是这样吧。  
MJRefresh所采用的主要设计模式非常简单,是类继承 + 模版方法设计模式。 所以子类也主要围绕着这几个模版方法和继承方法进行定制行为的。  
模版方法设计模式:
由父类MJRefreshComponent定义方法接口并添加到执行步骤中,对象执行中,在特定时间一定会调用的方法。由子类在需要的时候进行自定义实现。
在MJRefreshComponent类中的重要模版方法如下: [self prepare];//在父类initWithFrame方法调用 [self
placeSubviews];//在父类layoutSubviews方法调用
 

类继承:父类定义了方法的基本实现,子类在此基础上进行持续增加,达到复杂功能。与模版方法的区别是没有固定的执行步骤。
在MJRefreshComponent类中的重要继承方法如下: //状态设置 - (void)setState:(MJRefreshState)state
//事件监听 - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{} - (void
)scrollViewContentSizeDidChange:(NSDictionary *)change{} - (void
)scrollViewPanStateDidChange:(NSDictionary *)change{}
 
MJRefresh作为刷新组件,核心逻辑根据ScrollView的Offset不同更新相应的状态和数据,
根据方法名字应该是MJRefreshComponent类中的重要继承方法: - (void
)scrollViewContentOffsetDidChange:(NSDictionary *)change{}  
下面看一下其子类MJRefreshHeader对这个方法的实现:
MJRefreshHeader是父类MJRefreshComponent的子类,其方法声明结构如下:

红框内是主要实现代码应该就是这四个“覆盖父类方法”了   子类MJRefreshHeader的两个模版方法实现如下:   - (void)prepare {
[super prepare];// 设置key self.lastUpdatedTimeKey =
MJRefreshHeaderLastUpdatedTimeKey;// 设置高度 self.mj_h = MJRefreshHeaderHeight; }
- (void)placeSubviews { [super placeSubviews]; //
设置y值(当自己的高度发生改变了,肯定要重新调整Y值,所以放到placeSubviews方法中设置y值) self.mj_y = - self.mj_h -
self.ignoredScrollViewContentInsetTop; } 方法prepare:设置属性值 方法placeSubviews:更新UI布局
子类填充后,父类按照约定的步骤时机执行。over!   子类MJRefreshHeader的覆盖方法实现如下:   - (void
)scrollViewContentOffsetDidChange:(NSDictionary *)change { [super
scrollViewContentOffsetDidChange:change];// 在刷新的refreshing状态 if (self.state ==
MJRefreshStateRefreshing) {// 暂时保留 //
My:当NavigationBar从一个页面滑出时,可能被移除页面,其window为nil if (self.window == nil) return; //
sectionheader停留解决//My:当scrollView向下偏移的距离超过它的contentInset的上间隔时,取距离大的 CGFloat
insetT = - self.scrollView.mj_offsetY > _scrollViewOriginalInset.top ? -
self.scrollView.mj_offsetY : _scrollViewOriginalInset.top;//My:当这个距离超过了(刷新控件的高度
+ 它的contentInset的上间隔)时,取它们的和值 insetT = insetT > self.mj_h +
_scrollViewOriginalInset.top ? self.mj_h + _scrollViewOriginalInset.top :
insetT;//My:将这个合理的最大值,设置到它的contentInset的上间隔上。 self.scrollView.mj_insetT =
insetT;//My:实际露出的刷新空间高 self.insetTDelta = _scrollViewOriginalInset.top - insetT;
return; } // 跳转到下一个控制器时,contentInset可能会变 _scrollViewOriginalInset =
self.scrollView.mj_inset;// 当前的contentOffset CGFloat offsetY =
self.scrollView.mj_offsetY;// 头部控件刚好出现的offsetY CGFloat happenOffsetY = -
self.scrollViewOriginalInset.top;// 如果是向上滚动到看不见头部控件,直接返回 // >= -> > if (offsetY
> happenOffsetY)return; // 普通 和 即将刷新 的临界点 //My:下拉距离正好是(刷新控件高度+contentInset的上间隔)
CGFloat normal2pullingOffsetY = happenOffsetY - self.mj_h; //My:露出的高度/总高度
CGFloat pullingPercent = (happenOffsetY - offsetY) / self.mj_h; if
(self.scrollView.isDragging) {// 如果正在拖拽 self.pullingPercent = pullingPercent; if
(self.state == MJRefreshStateIdle && offsetY < normal2pullingOffsetY) { //
My:下拉度超过临界值// 转为即将刷新状态 self.state = MJRefreshStatePulling; } else if
(self.state == MJRefreshStatePulling && offsetY >= normal2pullingOffsetY) { //
My:下拉度小于临界值// 转为普通状态 self.state = MJRefreshStateIdle; } } else if (self.state ==
MJRefreshStatePulling) {// 即将刷新 && 手松开 // 开始刷新 [self beginRefreshing]; } else
if (pullingPercent < 1) { self.pullingPercent = pullingPercent; } }
该方法会随着ScrollView的滚动,其Offset会不断更新,此方法不不断被触发。 操作步骤大概思路是:
1.如果当前处于刷新状态,offset的改变时,设置scrollView的offset为(刷新控件的高度 + 它的contentInset的上间隔)。
2.否则的话,如果处于拖拽时,根据拖拽距离和当前控件状态,更新下一步控件的状态。 详细描述见上面的注释。  
带有NavigationBar的UIScrollView,默认它的offset = {0, -64}; 默认它的contentInset =
{64,0,0,0} 内容展示部分刚好在NavigationBar的下面   子类MJRefreshHeader的状态设置后,会调用如下方法,刷新控件的UI:
  - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 根据状态做事情 if
(state == MJRefreshStateIdle) { if (oldState != MJRefreshStateRefreshing) return
;// 保存刷新时间 [[NSUserDefaults standardUserDefaults] setObject:[NSDate date]
forKey:self.lastUpdatedTimeKey]; [[NSUserDefaults standardUserDefaults]
synchronize];// 恢复inset和offset [UIView
animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
self.scrollView.mj_insetT+= self.insetTDelta; // 自动调整透明度 if
(self.isAutomaticallyChangeAlpha) self.alpha =0.0; } completion:^(BOOL
finished) { self.pullingPercent= 0.0; if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock(); } }]; }else if (state ==
MJRefreshStateRefreshing) { MJRefreshDispatchAsyncOnMainQueue({ [UIView
animateWithDuration:MJRefreshFastAnimationDuration animations:^{ if
(self.scrollView.panGestureRecognizer.state !=
UIGestureRecognizerStateCancelled) { CGFloat top=
self.scrollViewOriginalInset.top + self.mj_h; // 增加滚动区域top
self.scrollView.mj_insetT = top; // 设置滚动位置 CGPoint offset =
self.scrollView.contentOffset; offset.y= -top; [self.scrollView
setContentOffset:offset animated:NO]; } } completion:^(BOOL finished) { [self
executeRefreshingCallback]; }]; }) } }
宏MJRefreshCheckState:检查旧状态与新状态是否一致,一致的话就返回。 从刷新转普通状态时: 保存刷新时间,调整菊花透明度,移动offset
转换成刷新状态时: 设置contentInset.top,设置offset   逻辑主干是上面的四个方法,其他的逻辑枝叶,想自己研究的话可以翻看源代码。    

友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信