上一篇文章我分析了Header的实现,本文主要分析Footer的实现。其实Footer的实现和Header的实现思路差不多,都是通过Scrollview的ContentOffset确定刷新状态。
1.为ScrollView添加MJRefreshFooter
和添加Header的方式是一样的。
self.tableView.mj_footer = [DDRefreshFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreListData)];
2.完整的继承关系
3.MJRefreshFooter
MJRefreshComponent作为Header和Footer的共同的基类,上一篇文章已经介绍过了。本篇文章直接从MJRefreshFooter开始讲起。
和MJRefreshHeader一样,MJRefreshFooter也定义了两种实例化方法。
+ (instancetype)footerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock;
+ (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action;
4.MJRefreshBackFooter
MJRefreshBackFooter这个类中实现了父类的4个方法。同样地在- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change;
中,确定了刷新状态(MJRefreshState)。
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
[super scrollViewContentOffsetDidChange:change];
// 如果正在刷新,直接返回
if (self.state == MJRefreshStateRefreshing) return;
_scrollViewOriginalInset = self.scrollView.mj_inset;
// 当前的contentOffset
CGFloat currentOffsetY = self.scrollView.mj_offsetY;
// 尾部控件刚好出现的offsetY
CGFloat happenOffsetY = [self happenOffsetY];
// 如果是向下滚动到看不见尾部控件,直接返回
if (currentOffsetY <= happenOffsetY) return;
CGFloat pullingPercent = (currentOffsetY - happenOffsetY) / self.mj_h;
// 如果已全部加载,仅设置pullingPercent,然后返回
if (self.state == MJRefreshStateNoMoreData) {
self.pullingPercent = pullingPercent;
return;
}
if (self.scrollView.isDragging) {
self.pullingPercent = pullingPercent;
// 普通 和 即将刷新 的临界点
CGFloat normal2pullingOffsetY = happenOffsetY + self.mj_h;
if (self.state == MJRefreshStateIdle && currentOffsetY > normal2pullingOffsetY) {
// 转为即将刷新状态
self.state = MJRefreshStatePulling;
} else if (self.state == MJRefreshStatePulling && currentOffsetY <= normal2pullingOffsetY) {
// 转为普通状态
self.state = MJRefreshStateIdle;
}
} else if (self.state == MJRefreshStatePulling) {// 即将刷新 && 手松开
// 开始刷新
[self beginRefreshing];
} else if (pullingPercent < 1) {
self.pullingPercent = pullingPercent;
}
}
#pragma mark 获得scrollView的内容 超出 view 的高度
- (CGFloat)heightForContentBreakView
{
CGFloat h = self.scrollView.frame.size.height - self.scrollViewOriginalInset.bottom - self.scrollViewOriginalInset.top;
return self.scrollView.contentSize.height - h;
}
#pragma mark 刚好看到上拉刷新控件时的contentOffset.y
- (CGFloat)happenOffsetY
{
CGFloat deltaH = [self heightForContentBreakView];
if (deltaH > 0) {
return deltaH - self.scrollViewOriginalInset.top;
} else {
return - self.scrollViewOriginalInset.top;
}
}
在- (void)setState:(MJRefreshState)state;
中根据刷新状态修改Scrollview的ContentInset;
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
// 根据状态来设置属性
if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) {
// 刷新完毕
if (MJRefreshStateRefreshing == oldState) {
[UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
self.scrollView.mj_insetB -= self.lastBottomDelta;
// 自动调整透明度
if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
} completion:^(BOOL finished) {
self.pullingPercent = 0.0;
if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock();
}
}];
}
CGFloat deltaH = [self heightForContentBreakView];
// 刚刷新完毕
if (MJRefreshStateRefreshing == oldState && deltaH > 0 && self.scrollView.mj_totalDataCount != self.lastRefreshCount) {
self.scrollView.mj_offsetY = self.scrollView.mj_offsetY;
}
} else if (state == MJRefreshStateRefreshing) {
// 记录刷新前的数量
self.lastRefreshCount = self.scrollView.mj_totalDataCount;
[UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
CGFloat bottom = self.mj_h + self.scrollViewOriginalInset.bottom;
CGFloat deltaH = [self heightForContentBreakView];
if (deltaH < 0) { // 如果内容高度小于view的高度
bottom -= deltaH;
}
self.lastBottomDelta = bottom - self.scrollView.mj_insetB;
self.scrollView.mj_insetB = bottom;
self.scrollView.mj_offsetY = [self happenOffsetY] + self.mj_h;
} completion:^(BOOL finished) {
[self executeRefreshingCallback];
}];
}
}
在- (void)scrollViewContentSizeDidChange:(NSDictionary *)change
中,实时更新Footer的位置。这是因为随着上拉加载,如果有新内容,Scrollview的ContentSize的是改变的,所以为了让Footer一直在Scrollview最下面,Scrollview的Y坐标随着ContentSize的改变而改变。这个是和Header是不同的。
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change
{
[super scrollViewContentSizeDidChange:change];
// 内容的高度
CGFloat contentHeight = self.scrollView.mj_contentH + self.ignoredScrollViewContentInsetBottom;
// 表格的高度
CGFloat scrollHeight = self.scrollView.mj_h - self.scrollViewOriginalInset.top - self.scrollViewOriginalInset.bottom + self.ignoredScrollViewContentInsetBottom;
// 设置位置和尺寸
self.mj_y = MAX(contentHeight, scrollHeight);
}
5.MJRefreshBackStateFooter
MJRefreshBackStateFooter在- (void)placeSubviews
中设置了stateLabel的位置,和是Footer的bounds,所以stateLabel和Footer的大小一样;在- (void)setState:(MJRefreshState)state;
中设置了stateLabel的内容,和MJRefreshStateHeader几乎没有差别,甚至更简单,因为Footer只有一个Label。
6.MJRefreshBackNormalFooter(MJRefreshBackGifFooter)
MJRefreshBackNormalFooter主要是在- (void)placeSubviews ;
设置箭头(arrowView)和菊花(loadingView,其实就是UIActivityIndicatorView)的位置,这两个控件的中心在一起;在- (void)setState:(MJRefreshState)state设置设置箭头(arrowView)和菊花(loadingView,其实就是UIActivityIndicatorView)的动画。MJRefreshBackGifFooter就是摆放箭头和菊花的位置换成了ImageView,更新刷新状态更换ImageView 的Image。
MJRefreshBackNormalFooter(MJRefreshBackGifFooter)和MJRefreshNormalHeader(MJRefreshGifHeader)的实现基本一致。
以上就是对MJRefresh的主要实现思路的分析,其中还有很多细小的知识点。如果有想知道的,可以给我留言,一起研究。