可改进部分
-
在 MJRefreshComponent.h 的 34 行,
typedef void (^MJRefreshComponentbeginRefreshingCompletionBlock)( );
这里的 begin
应当遵循命名规则改为 Begin
,与下面的
typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)( );
统一风格,可能是作者一时手误吧.
-
在 MJRefreshComponent.m 的 154 行,
// 预发当前正在刷新中时调用本方法使得header insert回置失败
这里应改为 预防
源码部分
-
MJRefreshConst.h
该文件中自定义了输出方法
#ifdef DEBUG #define MJRefreshLog(...) NSLog(__VA_ARGS__) #else #define MJRefreshLog(...) #endif
我在他的基础上写了个自定义的输出方法,多了输出文件名,方法,行数的功能
#define XZDEBUG #ifdef XZDEBUG #define TestLog(fmt, ...) \ NSLog((@"\nFile : %@\nmethod : %s\nLine : %zd\n" fmt), \ [[NSString stringWithFormat:@"%s",__FILE__] lastPathComponent], \ __FUNCTION__, __LINE__, ##__VA_ARGS__); #else #define TestLog(fmt, ...) #endif
-
UIScrollView+MJRefresh.h && UIScrollView+MJRefresh.m
在这两个文件里,动态的给 UIScrollView 加上了 mj_header , mj_footer ,和 mj_reloadDataBlock 属性.
- 使用
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); objc_getAssociatedObject(id object, const void *key);
函数的时候,对于这个参数
key
,其唯一性体现在它的内存地址唯一而不是这个地址内存放的值.- 在 UITableView 和 UICollectionView 的
+ (void)load
方法中,运用method_exchangeImplementations(Method m1, Method m2)
函数调换了系统原有的- (void)reloadData
方法和自行定义的- (void)mj_reloadData
方法的实现.于是有了下面的这段代码
- (void)mj_reloadData { [self mj_reloadData]; [self executeReloadDataBlock]; }
这里在
- (void)mj_reloadData
方法内部执行[self mj_reloadData];
,之所以不会造成死循环是因为此 时的- (void)mj_reloadData
实现已经变为系统原有方法- (void)reloadData
的实现. -
MJRefreshComponent.h && MJRefreshComponent.m
这个文件主要是定义了刷新控件的状态以及刷新回调, 其中 MJRefreshComponent 是刷新控件的基类,里面定义了诸多供子类实现的接口.至于代码方面没有什么值得记录的.
-
MJRefreshHeader.m MJRefreshHeader.m
这个文件 override 了一些父类的方法,我摘抄出一段我理了很久才理清楚的代码片段(其实主要是因为我对
UIScrollView
的contentInset
不了解导致的).
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
[super scrollViewContentOffsetDidChange:change];
// 在刷新的refreshing状态
if (self.state == MJRefreshStateRefreshing) {
if (self.window == nil) return;
// sectionheader停留解决
CGFloat insetT = - self.scrollView.mj_offsetY > _scrollViewOriginalInset.top ? - self.scrollView.mj_offsetY : _scrollViewOriginalInset.top;
insetT = insetT > self.mj_h + _scrollViewOriginalInset.top ? self.mj_h + _scrollViewOriginalInset.top : insetT;
self.scrollView.mj_insetT = insetT;
self.insetTDelta = _scrollViewOriginalInset.top - insetT;
return;
}
// 跳转到下一个控制器时,contentInset可能会变
_scrollViewOriginalInset = self.scrollView.contentInset;
// 当前的contentOffset
CGFloat offsetY = self.scrollView.mj_offsetY;
// 头部控件刚好出现的offsetY
CGFloat happenOffsetY = - self.scrollViewOriginalInset.top;
// 如果是向上滚动到看不见头部控件,直接返回
// >= -> >
if (offsetY > happenOffsetY) return;
// 普通 和 即将刷新 的临界点
CGFloat normal2pullingOffsetY = happenOffsetY - self.mj_h;
CGFloat pullingPercent = (happenOffsetY - offsetY) / self.mj_h;
if (self.scrollView.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;
}
}
代码的逻辑是当这个方法被调用的时候,
- 如果此时处于刷新状态,那么根据
scrollView
的contentOffset
来调整scrollView
的contentInset
,即做到了上下滑动时,能让mj_header
的这个控件悬停或者隐藏 - 如果此时不处于刷新状态,那么根据
scrollView
的contentOffset
和isDragging
实现mj_header
的state
状态切换,通过
> \- (void)setState:(MJRefreshState)state;
方法,实现了改变 `scrollView` 的 `contentInset ` 等属性.
-
MJRefreshStateHeader.h && MJRefreshStateHeader.m
代码层面来说,这个类没什么难的,主要就是给显示刷新状态和时间的 UILabel
做了下布局和根据不同情况显示不同文字.
不过有趣的一点是在头文件中,对 lastUpdatedTimeLabel
和 stateLabel
采取的是 weak
的内存管理策略(在他的子类中,控件也都是用 weak
的管理策略),所以才会用下面这样比较 "奇葩" 的方式创建 _stateLabel
(lastUpdatedTimeLabel
同理).其实我这里有点不懂,这里完全可以用 strong
的内存管理策略,为什么非要用 weak
,希望有自己看法的童靴可以来指点下.
- (UILabel *)stateLabel
{
if (!_stateLabel) {
[self addSubview:_stateLabel = [UILabel mj_label]];
}
return _stateLabel;
}
通过 self
对 label
的强引用保证其不会被释放,然后才能实现 stateLabel
不会为 nil
.
-
MJRefreshNormalHeader.h && MJRefreshNormalHeader.m
这个类相对于他的父类 MJRefreshStateHeader
给 mj_header
加上了一个箭头图标和一个 UIActivityIndicatorView
(即那个旋转的小菊花),代码层面上也没什么需要记录的点.
-
MJRefreshGifHeader.h && MJRefreshGifHeader.m
这个类相对于他的父类 MJRefreshStateHeader
给 mj_header
添加了一个显示动图的 UIImageView
,可以通过公开的接口来自行配置显示的图片组和动画时长.代码方面没有需要记录的点.
-
MJRefreshFooter.h && MJRefreshFooter.m
暴露出了 endRefreshingWithNoMoreData
的接口以及重置没有更多数据状态的接口 resetNoMoreData
.代码方面没什么值得记录的.
-
MJRefreshBackFooter.h && MJRefreshBackFooter.m
-
MJRefreshBackStateFooter.h && MJRefreshBackStateFooter.m
-
MJRefreshBackNormalFooter.h && MJRefreshBackNormalFooter.m
-
MJRefreshBackGifFooter.h && MJRefreshBackGifFooter.m
和 header
相对应的,代码方面和 header
类似.
-
MJRefreshAutoFooter.h && MJRefreshAutoFooter.m
-
MJRefreshAutoStateFooter.h && MJRefreshAutoStateFooter.m
-
MJRefreshAutoNormalFooter.h && MJRefreshAutoNormalFooter.m
-
MJRefreshAutoGifFooter.h && MJRefreshAutoGifFooter.m
这部分的代码和 MJRefreshBackxxx
对应的代码类似,只不过这里实现了根据设置的下拉百分比来自动刷新的功能.
总结
MJRefresh
这个框架主要通过对 UIScrollView
的 contentOffset
和 contentSize
进行观察监测,实时改变 UIScrollView
的 contentInset
和 UIScrollView
的 origin.y
,并且根据当时的 offset
的高度对应关系和 dragging
属性来判断是否要更改控件的 state
状态,进而判断是否要刷新还是要结束刷新.
网友评论