可改進部分
-
在 MJRefreshComponent.h 的 34 行,
typedef void (^MJRefreshComponentbeginRefreshingCompletionBlock)( );
這里的 begin
應(yīng)當(dāng)遵循命名規(guī)則改為 Begin
,與下面的
typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)( );
統(tǒng)一風(fēng)格,可能是作者一時手誤吧.
-
在 MJRefreshComponent.m 的 154 行,
// 預(yù)發(fā)當(dāng)前正在刷新中時調(diào)用本方法使得header insert回置失敗
這里應(yīng)改為 預(yù)防
源碼部分
-
MJRefreshConst.h
該文件中自定義了輸出方法
#ifdef DEBUG #define MJRefreshLog(...) NSLog(__VA_ARGS__) #else #define MJRefreshLog(...) #endif
我在他的基礎(chǔ)上寫了個自定義的輸出方法,多了輸出文件名,方法,行數(shù)的功能
#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
在這兩個文件里,動態(tài)的給 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);
函數(shù)的時候,對于這個參數(shù)
key
,其唯一性體現(xiàn)在它的內(nèi)存地址唯一而不是這個地址內(nèi)存放的值.- 在 UITableView 和 UICollectionView 的
+ (void)load
方法中,運用method_exchangeImplementations(Method m1, Method m2)
函數(shù)調(diào)換了系統(tǒng)原有的- (void)reloadData
方法和自行定義的- (void)mj_reloadData
方法的實現(xiàn).于是有了下面的這段代碼
- (void)mj_reloadData { [self mj_reloadData]; [self executeReloadDataBlock]; }
這里在
- (void)mj_reloadData
方法內(nèi)部執(zhí)行[self mj_reloadData];
,之所以不會造成死循環(huán)是因為此 時的- (void)mj_reloadData
實現(xiàn)已經(jīng)變?yōu)橄到y(tǒng)原有方法- (void)reloadData
的實現(xiàn). -
MJRefreshComponent.h && MJRefreshComponent.m
這個文件主要是定義了刷新控件的狀態(tài)以及刷新回調(diào), 其中 MJRefreshComponent 是刷新控件的基類,里面定義了諸多供子類實現(xiàn)的接口.至于代碼方面沒有什么值得記錄的.
-
MJRefreshHeader.m MJRefreshHeader.m
這個文件 override 了一些父類的方法,我摘抄出一段我理了很久才理清楚的代碼片段(其實主要是因為我對
UIScrollView
的contentInset
不了解導(dǎo)致的).
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
[super scrollViewContentOffsetDidChange:change];
// 在刷新的refreshing狀態(tài)
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;
}
// 跳轉(zhuǎn)到下一個控制器時接癌,contentInset可能會變
_scrollViewOriginalInset = self.scrollView.contentInset;
// 當(dāng)前的contentOffset
CGFloat offsetY = self.scrollView.mj_offsetY;
// 頭部控件剛好出現(xiàn)的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) {
// 轉(zhuǎn)為即將刷新狀態(tài)
self.state = MJRefreshStatePulling;
} else if (self.state == MJRefreshStatePulling && offsetY >= normal2pullingOffsetY) {
// 轉(zhuǎn)為普通狀態(tài)
self.state = MJRefreshStateIdle;
}
} else if (self.state == MJRefreshStatePulling) {// 即將刷新 && 手松開
// 開始刷新
[self beginRefreshing];
} else if (pullingPercent < 1) {
self.pullingPercent = pullingPercent;
}
}
代碼的邏輯是當(dāng)這個方法被調(diào)用的時候,
- 如果此時處于刷新狀態(tài),那么根據(jù)
scrollView
的contentOffset
來調(diào)整scrollView
的contentInset
,即做到了上下滑動時,能讓mj_header
的這個控件懸涂鬯希或者隱藏 - 如果此時不處于刷新狀態(tài),那么根據(jù)
scrollView
的contentOffset
和isDragging
實現(xiàn)mj_header
的state
狀態(tài)切換,通過
> \- (void)setState:(MJRefreshState)state;
方法,實現(xiàn)了改變 `scrollView` 的 `contentInset ` 等屬性.
-
MJRefreshStateHeader.h && MJRefreshStateHeader.m
代碼層面來說,這個類沒什么難的,主要就是給顯示刷新狀態(tài)和時間的 UILabel
做了下布局和根據(jù)不同情況顯示不同文字.
不過有趣的一點是在頭文件中,對 lastUpdatedTimeLabel
和 stateLabel
采取的是 weak
的內(nèi)存管理策略(在他的子類中,控件也都是用 weak
的管理策略),所以才會用下面這樣比較 "奇葩" 的方式創(chuàng)建 _stateLabel
(lastUpdatedTimeLabel
同理).其實我這里有點不懂,這里完全可以用 strong
的內(nèi)存管理策略,為什么非要用 weak
,希望有自己看法的童靴可以來指點下.
- (UILabel *)stateLabel
{
if (!_stateLabel) {
[self addSubview:_stateLabel = [UILabel mj_label]];
}
return _stateLabel;
}
通過 self
對 label
的強引用保證其不會被釋放,然后才能實現(xiàn) stateLabel
不會為 nil
.
-
MJRefreshNormalHeader.h && MJRefreshNormalHeader.m
這個類相對于他的父類 MJRefreshStateHeader
給 mj_header
加上了一個箭頭圖標(biāo)和一個 UIActivityIndicatorView
(即那個旋轉(zhuǎn)的小菊花),代碼層面上也沒什么需要記錄的點.
-
MJRefreshGifHeader.h && MJRefreshGifHeader.m
這個類相對于他的父類 MJRefreshStateHeader
給 mj_header
添加了一個顯示動圖的 UIImageView
,可以通過公開的接口來自行配置顯示的圖片組和動畫時長.代碼方面沒有需要記錄的點.
-
MJRefreshFooter.h && MJRefreshFooter.m
暴露出了 endRefreshingWithNoMoreData
的接口以及重置沒有更多數(shù)據(jù)狀態(tài)的接口 resetNoMoreData
.代碼方面沒什么值得記錄的.
-
MJRefreshBackFooter.h && MJRefreshBackFooter.m
-
MJRefreshBackStateFooter.h && MJRefreshBackStateFooter.m
-
MJRefreshBackNormalFooter.h && MJRefreshBackNormalFooter.m
-
MJRefreshBackGifFooter.h && MJRefreshBackGifFooter.m
和 header
相對應(yīng)的,代碼方面和 header
類似.
-
MJRefreshAutoFooter.h && MJRefreshAutoFooter.m
-
MJRefreshAutoStateFooter.h && MJRefreshAutoStateFooter.m
-
MJRefreshAutoNormalFooter.h && MJRefreshAutoNormalFooter.m
-
MJRefreshAutoGifFooter.h && MJRefreshAutoGifFooter.m
這部分的代碼和 MJRefreshBackxxx
對應(yīng)的代碼類似,只不過這里實現(xiàn)了根據(jù)設(shè)置的下拉百分比來自動刷新的功能.
總結(jié)
MJRefresh
這個框架主要通過對 UIScrollView
的 contentOffset
和 contentSize
進行觀察監(jiān)測,實時改變 UIScrollView
的 contentInset
和 UIScrollView
的 origin.y
,并且根據(jù)當(dāng)時的 offset
的高度對應(yīng)關(guān)系和 dragging
屬性來判斷是否要更改控件的 state
狀態(tài),進而判斷是否要刷新還是要結(jié)束刷新.