我对MJRefresh框架的了解 -> MJRefreshHeader

正如源码中注释的一样,这个类的作用是:负责监控用户下拉的状态;

 

一、在.h文件中,提供了两种类方法实例化对象,分别是带block回调和target响应的方法,用户可根据自身习惯去选择,达到的目的都是相同的。

+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
{
    MJRefreshHeader *cmp = [[self alloc] init];
    cmp.refreshingBlock = refreshingBlock;
    return cmp;
}
+ (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action
{
    MJRefreshHeader *cmp = [[self alloc] init];
    [cmp setRefreshingTarget:target refreshingAction:action];
    return cmp;
}


不管是refreshingBlock,还是target和action,在执行beginRefreshing方法之后都有机会回调MJRefreshComponent中的executeRefreshingCallback方法。在executeRefreshingCallback中,都会去判断以及执行回调方法。

二、MJRefreshHeader通过重写父类的prepare和placeSubviews方法,来做一些基本的设置,代码如下

- (void)prepare
{
    [super prepare];
    // 设置key
    self.lastUpdatedTimeKey = MJRefreshHeaderLastUpdatedTimeKey;
    // 设置高度
    self.mj_h = MJRefreshHeaderHeight;//这里默认设置为54
}
- (void)placeSubviews
{
    [super placeSubviews];
    // 设置y值(当自己的高度发生改变了,肯定要重新调整Y值,所以放到placeSubviews方法中设置y值)
    self.mj_y = - self.mj_h - self.ignoredScrollViewContentInsetTop;
}



三、MJRefreshHeader的主要作用

1、重写父类scrollViewContentOffsetDidChange方法,以达到监听scrollView的contentOffset发生变化的目的,并做一些实际的操作。

需要指出的地方有:
a、首先检测self.state的状态是否处于正在刷新的状态(也就是否等于枚举MJRefreshStateRefreshing),如果在刷新,那么直接结束,不做任何操作。

b、CGFloat happenOffsetY = - self.scrollViewOriginalInset.top;
 疑问:为什么在UITableViewController管理的UITableView (这里我暂时只试了这个,其它情况不清楚)状态下,这个值一直是64,不管怎么上下拖动tableView都不会发生变化。

c、CGFloat offsetY = self.scrollView.mj_offsetY;
疑问:在tableView启动默认状态下,这个值为-64,往上移动时,这个值会变大; 往下移动时,这个值会变小;但其它一般的scrollView,确不是这么变化的。(我没怎么认真研究过tableView的contentOffset的变化情况,涨姿势了)

d、当偏移量(也就是offsetY)值变大时,只要大于happenOffsetY(操作时,这个值至始至终是-64),该方法就直接结束。

e、CGFloat normal2pullingOffsetY = happenOffsetY - self.mj_h;  
设置了一个即将刷新的临界值,因为happenOffsetY至始至终为-64,self.mj_h在prepare方法中设置为54,所以这个临界值为-118 。

f、 CGFloat pullingPercent = (happenOffsetY - offsetY) / self.mj_h;
计算拉伸的比例,当offsetY为-64时,也就是tableView启动默认状态下的情况,pullingPercent值是为0的;
往上拖动时,由于offsetY变大,pullingPercent会为负数;
往下拖动时,offsetY变小,当offsetY在-64和-118中间时,pullingPercent是一个小数值。 当offsetY小于-118时,pullingPercent就会大于1 。

g、self.scrollView.isDragging判断该scrollView(就是MJRefreshComponent的父类)是否在拖动状态。

源码:

if (self.scrollView.isDragging) { // 如果正在拖拽
        NSLog(@"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;
    }



为真,在拖动状态:
如果偏移量offsetY值小于(注意往下拉是负数)临界点normal2pullingOffsetY(也就是-118)时,同时这个时候的self.state等于空闲状态时(也就是MJRefreshStateIdle),那么将state置位刷新状态(也就是MJRefreshStatePulling);
就这样,当该scrollView在拖动的时候,self.state来回在MJRefreshStatePulling 和 MJRefreshStateIdle之间切换,并且相应的执行self.state的setter方法(后面会对setter方法有进一步分析);

为假,放手了,不在拖动状态:
如果self.state等于MJRefreshStatePulling状态,放手就开始执行beginRefreshing方法。

源码:scrollViewContentOffsetDidChange方法代码如下:

- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
    [super scrollViewContentOffsetDidChange:change];
    // 在刷新的refreshing状态
    if (self.state == MJRefreshStateRefreshing) {
        // sectionheader停留解决
        return;
    }
    // 跳转到下一个控制器时,contentInset可能会变
    _scrollViewOriginalInset = self.scrollView.contentInset;
    
    // 当前的contentOffset
    CGFloat offsetY = self.scrollView.mj_offsetY;
    // 头部控件刚好出现的offsetY
    CGFloat happenOffsetY = - self.scrollViewOriginalInset.top;
//    NSLog(@"happenOffsetY --> %.2f,  offsetY --> %.2f", happenOffsetY, offsetY);
    // 如果是向上滚动到看不见头部控件,直接返回
    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;
    }
}





2、MJRefreshComponent的beginRefreshing方法,代码如下:

- (void)beginRefreshing
{
    [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
        self.alpha = 1.0;
    }];
    self.pullingPercent = 1.0;
    // 只要正在刷新,就完全显示
    NSLog(@"self.window->%@",self.window);
    if (self.window) {
        self.state = MJRefreshStateRefreshing;
    } else {
        NSLog(@"else /self.window->%@",self.window);
        self.state = MJRefreshStateWillRefresh;
        // 刷新(预防从另一个控制器回到这个控制器的情况,回来要重新刷新一下)
        [self setNeedsDisplay];
    }
}


在前文提到的放手就开始执行beginRefreshing方法,内部就再次对self.state进行MJRefreshStateRefreshing赋值,执行self.state的setter方法。

疑问:为什么在刷新的时候跳转到另外一个控制器self.window会成为空,而且还会第二次执行beginRefreshing方法,从而将self.state赋值为MJRefreshStateWillRefresh。

3、根据scrollViewContentOffsetDidChange内部执行的操作,来设置MJRefreshState的状态。MJRefreshHeader内部有重写父类中state的setter方法,


a、注意点这两句代码:MJRefreshState oldState = self.state; if (state == oldState) return;
一开始我没理解这个逻辑,后来一想,如果在前面加一段_state = state;那么这就是我最初理解的了。只能说MJ让我涨姿势了~~~

b、下拉刷新时,当执行了beginRefreshing方法,内部将self.state设置为MJRefreshStateRefreshing,并且调用self.state的setter方法,这个时候执行这串代码:

[UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
            // 增加滚动区域
            CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
            self.scrollView.mj_insetT = top;//top值将会为118
            // 设置滚动位置
            self.scrollView.mj_offsetY = - top;
        } completion:^(BOOL finished) {
            [self executeRefreshingCallback];
        }]

 


这里修改UIScrollView的contentInset属性,通过增加UIScrollView额外的滚动区域来达到显示的效果。
同时,将contentOffset的Y值设置为-118;

c、当下拉刷新完成时,假如执行了endRefreshing操作,内部会通过setter方法将state置位MJRefreshStateIdle,这个时候就会执行这串代码:
 // 保存刷新时间
[[NSUserDefaults standardUserDefaults] setObject: [NSDate date] forKey:self.lastUpdatedTimeKey];
[[NSUserDefaults standardUserDefaults] synchronize];
 // 恢复inset和offset
 [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
            self.scrollView.mj_insetT -= self.mj_h;
            // 自动调整透明度
            if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
        } completion:^(BOOL finished) {
            self.pullingPercent = 0.0;
    }];


重新将UIScrollView的contentInset属性设置位最初的值(对于一般状态的UITableView也就是64);并且保存了最后结束刷新的时间 和 self的透明度。

源代码如下:

- (void)setState:(MJRefreshState)state
{
//    MJRefreshCheckState //源码是这句宏定义代码,但是为了能清晰看到逻辑原理,我把宏换了
    MJRefreshState oldState = self.state;//搞清楚了,self.state(调用了getter方法)是获取了之前的状态,
    if (state == oldState) return;
    [super setState:state];
    // 根据状态做事情
    if (state == MJRefreshStateIdle) {
        if (oldState != MJRefreshStateRefreshing) return;
        // 保存刷新时间
        [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:self.lastUpdatedTimeKey];
        [[NSUserDefaults standardUserDefaults] synchronize];
        MJRefreshLog(@"1->contentInset.top -> %.2f", self.scrollView.mj_insetT);
        // 恢复inset和offset
        [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
            self.scrollView.mj_insetT -= self.mj_h;
            // 自动调整透明度
            if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
        } completion:^(BOOL finished) {
            self.pullingPercent = 0.0;
        }];
    } else if (state == MJRefreshStateRefreshing) {
        MJRefreshLog(@"2->contentInset.top -> %.2f", self.scrollView.mj_insetT);
        [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
            // 增加滚动区域
            CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
            self.scrollView.mj_insetT = top;
            // 设置滚动位置
            self.scrollView.mj_offsetY = - top;
        } completion:^(BOOL finished) {
            [self executeRefreshingCallback];
        }];
    }
}





4、其它

a、对象调用方法永远都是从自己的方法列表中去寻找,当找不到的时候,才会去父类寻找方法。MJ很好的灵活运用了这个机制;

b、@property ( nonatomic ) UIEdgeInsets contentInset; 这个属性能够在UIScrollView的4周增加额外的滚动区域 ;

c、MJRefreshState oldState = self.state; if (state == oldState) return;之所以能拿到之前的state状态,通过state的getter方法来获取,这个时候属性_state并没有被更改;如果在这两句代码之前加上_state = state  , 那么情况将会完全不一样;

d、如果自己去打断点执行一遍,思路会更清晰;



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值