一直好奇下拉刷新類的框架是怎么實(shí)現(xiàn),這周看了star最多的MJRefresh。源碼繼承結(jié)構(gòu)清晰,實(shí)現(xiàn)的較為巧妙。

1. header初始化
MJRefreshHeader類提供了初始化方法,并傳入刷新時(shí)要執(zhí)行的block
+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
{
MJRefreshHeader *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
}
其中,MJRefreshHeader *cmp = [[self alloc] init];會(huì)調(diào)用父類MJRefreshComponent初始化方法。
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
// 準(zhǔn)備工作
[self prepare];
// 默認(rèn)是普通狀態(tài)
self.state = MJRefreshStateIdle;
}
return self;
}
初始化方法中,會(huì)先自上而下的調(diào)用各個(gè)類的prepare方法,再自上而下的調(diào)用各個(gè)類的setState方法。
在prepare方法中,自上而下確定了header的高度、文字提示內(nèi)容、菊花的樣式。
在setState方法中,根據(jù)不同的state狀態(tài),自上而下調(diào)整scrollView的contentInset、contentOffset、header顯示的內(nèi)容和刷新狀態(tài)。
之后自上而下執(zhí)行willMoveToSuperview方法,只有MJRefreshComponent類重寫了這個(gè)方法
- (void)willMoveToSuperview:(UIView *)newSuperview
{
[super willMoveToSuperview:newSuperview];
// 如果不是UIScrollView,不做任何事情
if (newSuperview && ![newSuperview isKindOfClass:[UIScrollView class]]) return;
// 舊的父控件移除監(jiān)聽
[self removeObservers];
if (newSuperview) { // 新的父控件
// 設(shè)置寬度
self.mj_w = newSuperview.mj_w;
// 設(shè)置位置
self.mj_x = -_scrollView.mj_insetL;
// 記錄UIScrollView
_scrollView = (UIScrollView *)newSuperview;
// 設(shè)置永遠(yuǎn)支持垂直彈簧效果
_scrollView.alwaysBounceVertical = YES;
// 記錄UIScrollView最開始的contentInset
_scrollViewOriginalInset = _scrollView.mj_inset;
// 添加監(jiān)聽
[self addObservers];
}
}
這個(gè)方法里確定了header的寬度和x值,并添加了對srocllView的監(jiān)聽,監(jiān)聽方法后面說。
之后自上而下調(diào)用layoutSubviews方法,導(dǎo)致自上而下的調(diào)用placeSubviews方法,確定了header的y值,以及header的subView的布局。
之后調(diào)用 drawRect繪制顯示。
2. 對scrollView的監(jiān)聽
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
[super scrollViewContentOffsetDidChange:change];
// 在刷新的refreshing狀態(tài)
if (self.state == MJRefreshStateRefreshing) {
// 暫時(shí)保留
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)到下一個(gè)控制器時(shí),contentInset可能會(huì)變
_scrollViewOriginalInset = self.scrollView.mj_inset;
// 當(dāng)前的contentOffset
CGFloat offsetY = self.scrollView.mj_offsetY;
// 頭部控件剛好出現(xiàn)的offsetY
CGFloat happenOffsetY = - self.scrollViewOriginalInset.top;
// 如果是向上滾動(dòng)到看不見頭部控件,直接返回
// >= -> >
if (offsetY > happenOffsetY) return;
// 普通 和 即將刷新 的臨界點(diǎn)
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;
}
}
這個(gè)方法會(huì)根據(jù)不同的下拉距離,給self.state、self.pullingPercent賦值。
之后會(huì)自上而下調(diào)用setState方法,根據(jù)不同的state狀態(tài),調(diào)整scrollView的contentInset、contentOffset、header顯示的內(nèi)容和刷新狀態(tài)。