MJRefresh源码阅读

很多项目的上拉加载下拉刷新功能都是基于MJRefresh来实现的,下面通过源码来分析几个问题。

类图

一、下拉刷新时,MJRefreshNormalHeader为什么不会回弹?

对于UIScrollView及其子类控件,下拉达到临界点时会进入正在刷新的状态,有没有疑问?为什么它能停在那里不弹回去?
MJRefresh源码阅读_第1张图片
refreshHeader.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的工作原理
MJRefresh源码阅读_第2张图片
在最底部弹出加载更多的MJRefreshBackNormalFooter.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的工作原理
MJRefresh源码阅读_第3张图片
加载更多追加在内容的尾部MJRefreshAutoNormalFooter.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;
    }
}

你可能感兴趣的:(MJRefresh源码阅读)