很多项目的上拉加载下拉刷新功能都是基于MJRefresh来实现的,下面通过源码来分析几个问题。
![](https://img.haomeiwen.com/i182346/6be6e78d9c648089.png)
一、下拉刷新时,MJRefreshNormalHeader为什么不会回弹?
对于UIScrollView及其子类控件,下拉达到临界点时会进入正在刷新的状态,有没有疑问?为什么它能停在那里不弹回去?![](https://img.haomeiwen.com/i2655527/4a8f324ba49b8da0.png)
下面是源码:
//mj_header添加了scrollViewContentOffset监听,下拉超过头部后松手,此时状态是MJRefreshStatePulling,
//它会执行[self beginRefreshing];开始刷新
- (void)beginRefreshing
{ // ...
self.state = MJRefreshStateRefreshing;
}
// MJRefreshHeader有多重继承关系,找到MJRefreshHeader类中的下面方法即可知道,
//在进入刷新状态时增加了scorllView. contentInsets.top,并将scrollView.contentOffset设置为新的contentInsets.top;
//刷新完成后再设置回原本的即可
- (void)setState:(MJRefreshState)state
{
if (state == MJRefreshStateRefreshing) {
[UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
if (self.scrollView.panGestureRecognizer.state != UIGestureRecognizerStateCancelled) {
CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
// 在进入刷新状态时增加滚动区域top,mj_insetT是contentInsets.top
self.scrollView.mj_insetT = top;
// 设置滚动位置
CGPoint offset = self.scrollView.contentOffset;
offset.y = -top;
[self.scrollView setContentOffset:offset animated:NO];
}
} completion:^(BOOL finished) {
[self executeRefreshingCallback];
}];
}
}
二、MJRefreshBackNormalFooter的工作原理
![](https://img.haomeiwen.com/i2655527/c29875e40cce43da.png)
- 通过对
scrollViewContentSize
监听来调整footer的y值
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change{
// 设置位置和尺寸
self.mj_y = MAX(内容的高度, 表格的高度);
}
- 通过对
scrollViewContentOffset
监听,进入刷新状态
// 当上拉的ContentOffset大于自身内容+footer高度后松手,此时状态是MJRefreshStatePulling,
//它会执行[self beginRefreshing];开始刷新
- (void)beginRefreshing
{ // ...
self.state = MJRefreshStateRefreshing;
}
// 原理与header类似,设置contentInset.bottom增加footer高度,
// 并设置scrollView.mj_offsetY加上footer高度
- (void)setState:(MJRefreshState)state
{
//....
//
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;
// mj_insetB就是设置contentInset.bottom
self.scrollView.mj_insetB = bottom;
self.scrollView.mj_offsetY = [self happenOffsetY] + self.mj_h;
} completion:^(BOOL finished) {
[self executeRefreshingCallback];
}];
}
}
三、MJRefreshAutoNormalFooter的工作原理
![](https://img.haomeiwen.com/i2655527/8277f421fde4b8c3.png)
- 在添加到scollView上时就设置contentInset.bottom,并将footer追加在尾部
- (void)willMoveToSuperview:(UIView *)newSuperview{
[super willMoveToSuperview:newSuperview];
if (newSuperview) { // 新的父控件
if (self.hidden == NO) {
self.scrollView.mj_insetB += self.mj_h;
}
// 设置位置
self.mj_y = _scrollView.mj_contentH;
} else { // 被移除了
if (self.hidden == NO) {
self.scrollView.mj_insetB -= self.mj_h;
}
}
}
- 通过对
scrollViewContentSize
监听来调整footer的y值
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change{
[super scrollViewContentSizeDidChange:change];
// 设置位置
self.mj_y = self.scrollView.mj_contentH + self.ignoredScrollViewContentInsetBottom;
}
- 通过对
scrollViewContentOffset
监听,如果内容超过一个scrollView高度时滑到底部就自动加载更多。
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
[super scrollViewContentOffsetDidChange:change];
if (self.state != MJRefreshStateIdle || !self.automaticallyRefresh || self.mj_y == 0) return;
// 1、内容超过一个屏幕, 则进行下一步判断
if (_scrollView.mj_insetT + _scrollView.mj_contentH > _scrollView.mj_h) {
// 2. 当滑动offsetY后达到了底部,并且在拖动状态isDragging,就进入刷新状态
if (_scrollView.mj_offsetY >= _scrollView.mj_contentH - _scrollView.mj_h + self.mj_h * self.triggerAutomaticallyRefreshPercent + _scrollView.mj_insetB - self.mj_h) {
// 防止手松开时回弹来到这里连续调用
CGPoint old = [change[@"old"] CGPointValue];
CGPoint new = [change[@"new"] CGPointValue];
if (new.y <= old.y) return;
if (_scrollView.isDragging) {
self.triggerByDrag = YES;
}
// 当底部刷新控件完全出现时,才刷新
[self beginRefreshing];
}
}
}
- (void)beginRefreshing
{// [self.mj_footer endRefreshingWithNoMoreData];之后状态是MJRefreshStateNoMoreData
if (state == MJRefreshStateNoMoreData) {
return;
}
[super beginRefreshing];
}
- 对
scrollView.panGestureRecognizer.state
的监听。
1. 在内容不够一个scrollView高度时,向上拽了就会加载更多。
2. 在内容超出一个scrollView高度时,如果此时是在最底部,向上拽了就会加载更多。
- (void)scrollViewPanStateDidChange:(NSDictionary *)change
{
[super scrollViewPanStateDidChange:change];
if (self.state != MJRefreshStateIdle) return;
UIGestureRecognizerState panState = _scrollView.panGestureRecognizer.state;
switch (panState) {
// 手松开
case UIGestureRecognizerStateEnded: {
if (_scrollView.mj_insetT + _scrollView.mj_contentH <= _scrollView.mj_h) { // 不够一个屏幕
if (_scrollView.mj_offsetY >= - _scrollView.mj_insetT) { // 向上拽
self.triggerByDrag = YES;
[self beginRefreshing];
}
} else { // 超出一个屏幕
if (_scrollView.mj_offsetY >= _scrollView.mj_contentH + _scrollView.mj_insetB - _scrollView.mj_h) {
self.triggerByDrag = YES;
[self beginRefreshing];
}
}
}
break;
}
}
网友评论