一直好奇下拉刷新类的框架是怎么实现,这周看了star最多的MJRefresh
。源码继承结构清晰,实现的较为巧妙。
1. header初始化
MJRefreshHeader
类提供了初始化方法,并传入刷新时要执行的block
+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
{
MJRefreshHeader *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
}
其中,MJRefreshHeader *cmp = [[self alloc] init];
会调用父类MJRefreshComponent
初始化方法。
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
// 准备工作
[self prepare];
// 默认是普通状态
self.state = MJRefreshStateIdle;
}
return self;
}
初始化方法中,会先自上而下的调用各个类的prepare
方法,再自上而下的调用各个类的setState
方法。
在prepare
方法中,自上而下确定了header的高度、文字提示内容、菊花的样式。
在setState
方法中,根据不同的state状态,自上而下调整scrollView的contentInset、contentOffset
、header显示的内容和刷新状态。
之后自上而下执行willMoveToSuperview
方法,只有MJRefreshComponent
类重写了这个方法
- (void)willMoveToSuperview:(UIView *)newSuperview
{
[super willMoveToSuperview:newSuperview];
// 如果不是UIScrollView,不做任何事情
if (newSuperview && ![newSuperview isKindOfClass:[UIScrollView class]]) return;
// 旧的父控件移除监听
[self removeObservers];
if (newSuperview) { // 新的父控件
// 设置宽度
self.mj_w = newSuperview.mj_w;
// 设置位置
self.mj_x = -_scrollView.mj_insetL;
// 记录UIScrollView
_scrollView = (UIScrollView *)newSuperview;
// 设置永远支持垂直弹簧效果
_scrollView.alwaysBounceVertical = YES;
// 记录UIScrollView最开始的contentInset
_scrollViewOriginalInset = _scrollView.mj_inset;
// 添加监听
[self addObservers];
}
}
这个方法里确定了header
的宽度和x
值,并添加了对srocllView
的监听,监听方法后面说。
之后自上而下调用layoutSubviews
方法,导致自上而下的调用placeSubviews
方法,确定了header
的y
值,以及header
的subView
的布局。
之后调用 drawRect
绘制显示。
2. 对scrollView的监听
- (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.mj_inset;
// 当前的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;
}
}
这个方法会根据不同的下拉距离,给self.state、self.pullingPercent
赋值。
之后会自上而下调用setState
方法,根据不同的state状态,调整scrollView的contentInset、contentOffset、header显示的内容和刷新状态。