iOS MJRefresh源码研读

/*

 //当自己重写一个UIView的时候有可能用到这个方法,当本视图的父类视图改变的时候,系统会自动的执行这个方法.newSuperview是本视图的新父类视图.newSuperview有可能是nil.

 Tells the view that its superview is about to change to the specified superview.

 The default implementation of this method does nothing. Subclasses can override it to perform additional actions whenever the superview changes.

 - (void)willMoveToSuperview:(UIView *)newSuperview 父类的addObserver方法中添加下面这个KVO


 这个newSuperview就是下面的scrollview

 [self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentOffset options:options context:nil];

 

 NSString *const MJRefreshKeyPathContentOffset = @"contentOffset";

 

- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change

KVO的执行的方法


//initWithFrame方法中        self.state = MJRefreshStateIdle;


//drawRect方法中     

 if (self.state == MJRefreshStateWillRefresh) {

 // 预防view还没显示出来就调用了beginRefreshing

 self.state = MJRefreshStateRefreshing;

 }


//- (void)beginRefreshing

 {

 [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{

 self.alpha = 1.0;

 }];

 self.pullingPercent = 1.0;

 // 只要正在刷新,就完全显示

 if (self.window) {

 self.state = MJRefreshStateRefreshing;

 } else {

 // 预防正在刷新中时,调用本方法使得header inset回置失败

 if (self.state != MJRefreshStateRefreshing) {

 self.state = MJRefreshStateWillRefresh;

 // 刷新(预防从另一个控制器回到这个控制器的情况,回来要重新刷新一下)

 [self setNeedsDisplay];

 }

 }

 }


//- (void)endRefreshing

 {

 self.state = MJRefreshStateIdle;

 }

 

//- (BOOL)isRefreshing

 {

 return self.state == MJRefreshStateRefreshing || self.state == MJRefreshStateWillRefresh;

 }

 */

- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change

{

    [super scrollViewContentOffsetDidChange:change];

    

    // 在刷新的refreshing状态

    if (self.state == MJRefreshStateRefreshing) {

        if (self.window == nil) return;

        

        // sectionheader停留解决

        // _scrollViewOriginalInset = _scrollView.contentInset;(父类里做的)

        // - self.scrollView.mj_offsetY 这个变量加(负号)的目的是把滑动量转换为(正数)(因为下拉的时候是负数)

        // 下拉开始之前的top0

        CGFloat insetT = - self.scrollView.mj_offsetY > _scrollViewOriginalInset.top ? - self.scrollView.mj_offsetY : _scrollViewOriginalInset.top;

        

        // 判断下拉量大于头视图的高度(因为此时top0)(如果大就取高度,如果小就去当前值)

        insetT = insetT > self.mj_h + _scrollViewOriginalInset.top ? self.mj_h + _scrollViewOriginalInset.top : insetT;

        // 改变inset.top的值(这里就是完成header停留问题)

        self.scrollView.mj_insetT = insetT;

        

        // 注意_scrollViewOriginalInset.top还是上面的0,还没有改变,insetT是下拉量

        self.insetTDelta = _scrollViewOriginalInset.top - insetT;

        return;

    }

    

    // 跳转到下一个控制器时,contentInset可能会变

    // 注意上面的if中有return,所以这里的_scrollViewOriginalInset和上面没有关系

     _scrollViewOriginalInset = self.scrollView.contentInset;

    

    // 当前的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;

        // 注意offsetY是负数 normal2pullingOffsetY也是负数(其实是头视图高度的负数),前者小于后者说明  前者的绝对者大于后者绝对者

        // 即拖动量大于头视图高度

        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;

    }

}



注意:写自定义的header或者footer的时候,执行顺序是先prepare 后willMoveToSuperview

在创建头视图的时候,传进去一个block类型的参数,而这个block的调用时发生方法:executeRefreshingCallback中的(改方法的实现写在MJRefreshComponent中

prepare方法是在MJRefreshComponent的initWithFrame中调用的

对于gif的下拉刷新,是在setPullingPercent方法中做gifView(UIImageView)展示图片索引的改变

还有一点要注意的是:
mj_header是通过运行时分类添加属性的方式给UIScrollview分类添加的一个属性
代码如下:

@implementation UIScrollView (MJRefresh)


#pragma mark - header

static const char MJRefreshHeaderKey = '\0';

- (void)setMj_header:(MJRefreshHeader *)mj_header

{

    if (mj_header != self.mj_header) {

        // 删除旧的,添加新的

        [self.mj_header removeFromSuperview];

        [self insertSubview:mj_header atIndex:0];

        

        // 存储新的

        [self willChangeValueForKey:@"mj_header"]; // KVO

        objc_setAssociatedObject(self, &MJRefreshHeaderKey,

                                 mj_header, OBJC_ASSOCIATION_ASSIGN);

        [self didChangeValueForKey:@"mj_header"]; // KVO

    }

}


- (MJRefreshHeader *)mj_header

{

    return objc_getAssociatedObject(self, &MJRefreshHeaderKey);

}


#pragma mark - footer

static const char MJRefreshFooterKey = '\0';

- (void)setMj_footer:(MJRefreshFooter *)mj_footer

{

    if (mj_footer != self.mj_footer) {

        // 删除旧的,添加新的

        [self.mj_footer removeFromSuperview];

        [self insertSubview:mj_footer atIndex:0];

        

        // 存储新的

        [self willChangeValueForKey:@"mj_footer"]; // KVO

        objc_setAssociatedObject(self, &MJRefreshFooterKey,

                                 mj_footer, OBJC_ASSOCIATION_ASSIGN);

        [self didChangeValueForKey:@"mj_footer"]; // KVO

    }

}


- (MJRefreshFooter *)mj_footer

{

    return objc_getAssociatedObject(self, &MJRefreshFooterKey);

}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值