zoukankan      html  css  js  c++  java
  • MJRefresh 源码解读 + 使用

    MJRefresh这个刷新控件是一款非常好用的框架,我们在使用一个框架的同时,最好能了解下它的实现原理,不管是根据业务要求在原有的基础上修改代码,还是其他的目的,弄明白作者的思路和代码风格,会受益匪浅。

    前言

    随着开发经验的不断积累,个人的能力也会不断提高。每个人的进步都会有一个过程,这个过程就好比登山。一个框架对我们的意义从开始的简单实用,慢慢的就会过渡到了解,最终让它成为你大脑的一部分。这篇文章不会对代码进行逐行解读,最重要的目的是让朋友们明白 MJRefresh 是怎么做到刷新的,我们还能如何扩展它的功能。

    原理部分

    我们在 MJRefreshMJRefresh Github 上拿到了下边这张图:

    正如这张图片所说的,所有的功能的实现都基于 MJRefreshComponent 这个刷新组件。MJRefreshComponent 有两个分支 MJRefreshHeader(给了下拉刷新的能力) MJRefreshFooter(给了上拉刷新的能力)。

    在进行下边的代码分析之前,我们来看看刷新的整个过程(我们以头部刷新为例):

    1. 看这张图片上的代码,能够tableView.mj_header这样使用,说明tableview有mj_header这个属性。然后我们看一下UIScrollView+MJRefresh这个文件。原理是给这个scrollview添加了一个控件,代码如下
    2. 我看了下MJRefreshHeader的这个文件的头文件,对我们理解原理并没有帮助,由于它是继承MJRefreshComponent的,所以我们打开MJRefreshComponent的头文件来看。
    3. 这几个状态非常关键,是使用整个框架的核心思路。为什么这么说?如果我们在滑动的过程中,状态也会改变,那么必然会调用状态的setter方法,我们就利用这个方法,显示自己想要的效果。
    4. 到这里基本上就很明白了。由于MJRefreshComponent是一个UIView,因此我们可以随意往上添加控件,我们在prepare中添加控件,在placeSubviews中布局,通过scrollViewContentOffsetDidChange:方法指导contentOffset改变了。然后就能自定义事件了。
    5. 最终我们在我们最需要的状态上绑定事件就ok了。

    MJRefreshComponent

    MJRefreshComponent 这个类是最最基础能力的搭建了。除了暴露出业务接口外。值得注意的有两点:

    1. 上边UIScrollView+MJRefresh中提到把mj_header这个view加到了scrollview上,那么MJRefreshComponent是如何获取scrollview的呢?
    2. 如何监听我们需要的变化?比如说contentOffSet。。。

    示例代码:

    - (void)willMoveToSuperview:(UIView *)newSuperview
    {
        [super willMoveToSuperview:newSuperview];
        
        // 如果不是UIScrollView,不做任何事情
        if (newSuperview && ![newSuperview isKindOfClass:[UIScrollView class]]) return;
        
        // 旧的父控件移除监听
        [self removeObservers];
        
        if (newSuperview) { // 新的父控件
            // 设置宽度
            self.mj_w = newSuperview.mj_w;
            // 设置位置
            self.mj_x = 0;
            
            // 记录UIScrollView
            _scrollView = (UIScrollView *)newSuperview;
            // 设置永远支持垂直弹簧效果
            _scrollView.alwaysBounceVertical = YES;
            // 记录UIScrollView最开始的contentInset
            _scrollViewOriginalInset = _scrollView.contentInset;
            
            // 添加监听
            [self addObservers];
        }
    }
    

    当一个控价被添加到另一个控件上的时候,就会调用这个方法,这这个方法中我们就获取到了scrollview,并且设置了监听事件,监听事件这里就不写了。

    MJRefreshHeader

    MJRefreshHeader 这个类提供了刷新的核心功能,这个类并没有像UIImageview,UILabel这样的控件。所以说他同样是一个基础类,我们使用中只需继承这个类,添加需要的UI控件就行了。

    示例代码:

    - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
    {
        [super scrollViewContentOffsetDidChange:change];
        
        // 在刷新的refreshing状态
        if (self.state == MJRefreshStateRefreshing) {
            if (self.window == nil) return;
            
            // sectionheader停留解决
            NSLog(@"%f , %f",self.scrollView.mj_offsetY,_scrollViewOriginalInset.top);
            CGFloat insetT = - self.scrollView.mj_offsetY > _scrollViewOriginalInset.top ? - self.scrollView.mj_offsetY : _scrollViewOriginalInset.top;
            insetT = insetT > self.mj_h + _scrollViewOriginalInset.top ? self.mj_h + _scrollViewOriginalInset.top : insetT;
            self.scrollView.mj_insetT = insetT;
            
            self.insetTDelta = _scrollViewOriginalInset.top - insetT;
            return;
        }
        
        // 跳转到下一个控制器时,contentInset可能会变
         _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;
            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;
        }
    }
    

    这个是通过contentOffSet计算状态的核心方法。由于代码注释很详细,就不做解释了,只要一行一行理解就能行了。

    MJRefreshAutoFooter

    MJRefreshAutoFooter 提供了当滑到底部时,自动加载的功能,我们来看看代码:

    - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
    {
        [super scrollViewContentOffsetDidChange:change];
        
        if (self.state != MJRefreshStateIdle || !self.automaticallyRefresh || self.mj_y == 0) return;
        
        if (_scrollView.mj_insetT + _scrollView.mj_contentH > _scrollView.mj_h) { // 内容超过一个屏幕
            // 这里的_scrollView.mj_contentH替换掉self.mj_y更为合理
            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;
                
                // 当底部刷新控件完全出现时,才刷新
                [self beginRefreshing];
            }
        }
    }
    

    演示案例

    我们就简单演示 转转 的下拉加载效果。由于代码改动比较小,在这里下载:转转下拉效果

    总结

    有时间写一个类似知乎刷新那样的效果。

  • 相关阅读:
    怎样克服效率低
    开通博客
    一位父亲和一位母亲讲述孩子的成长故事--《粗养的智慧:李聃的普林斯顿之路》和《我的儿子马友友》阅读摘录
    读吴军博士新浪微博(2012.09-2014.12)信息整理
    读_浪潮之巅_新浪微博信息整理
    《现代软件工程-构建之法》读后感11-12章
    《现代软件工程-构建之法》读后感8-10章
    5.2 5.1的完善2.0
    四则运算程序的测试与封装
    《现代软件工程-构建之法》读后感6-7章
  • 原文地址:https://www.cnblogs.com/machao/p/5826817.html
Copyright © 2011-2022 走看看