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];
}
}
}

演示案例

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

总结

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

MJRefresh 源码解读 + 使用的更多相关文章

  1. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  2. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  3. SDWebImage源码解读 之 UIImage+GIF

    第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...

  4. SDWebImage源码解读 之 SDWebImageCompat

    第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...

  5. SDWebImage源码解读_之SDWebImageDecoder

    第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...

  6. SDWebImage源码解读之SDWebImageCache(上)

    第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...

  7. SDWebImage源码解读之SDWebImageCache(下)

    第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...

  8. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  9. AFNetworking 3.0 源码解读 总结(干货)(上)

    养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...

随机推荐

  1. 试试SQLSERVER2014的内存优化表

    试试SQLSERVER2014的内存优化表 SQL Server 2014中的内存引擎(代号为Hekaton)将OLTP提升到了新的高度. 现在,存储引擎已整合进当前的数据库管理系统,而使用先进内存技 ...

  2. IE6、7下html标签间存在空白符,导致渲染后占用多余空白位置的原因及解决方法

    直接上图:原因:该div包含的内容是靠后台进行print操作,输出的.如果没有输出任何内容,浏览器会默认给该空白区域添加空白符.在IE6.7下,浏览器解析渲染时,会认为空白符也是占位置的,默认其具有字 ...

  3. nohup程序后台执行

    Linux常用命令,用于不挂断的执行程序. nohup命令:如果你正在运行一个进程,而且你觉得在退出帐户时该进程还不会结束,那么可以使用nohup命令.该命令可以在你退出帐户/关闭终端之后继续运行相应 ...

  4. Taurus.MVC 2.0 开源发布:WebAPI开发教程

    背景: 有用户反映,Tausus.MVC 能写WebAPI么? 能! 教程呢? 嗯,木有! 好吧,刚好2.0出来,就带上WEBAPI教程了! 开源地址: https://github.com/cyq1 ...

  5. Base64编码

    Base64编码 写在前面 今天在做一个Android app时遇到了一个问题:Android端采用ASE对称加密的数据在JavaWeb(jre1.8.0_7)后台解密时,居然解密失败了!经过测试后发 ...

  6. Python的单元测试(一)

    title: Python的单元测试(一) author: 青南 date: 2015-02-27 22:50:47 categories: Python tags: [Python,单元测试] -- ...

  7. Android-armebi-v7a、arm64-v8a、armebi的坑

    先来一波扫盲: armeabi:针对普通的或旧的arm v5 cpu armeabi-v7a:针对有浮点运算或高级扩展功能的arm v7 cpu(32位ARM设备) arm64-v8a:64位ARM设 ...

  8. ASP.NET Core应用针对静态文件请求的处理[5]: DefaultFilesMiddleware中间件如何显示默认页面

    DefaultFilesMiddleware中间件的目的在于将目标目录下的默认文件作为响应内容.我们知道,如果直接请求的就是这个默认文件,那么前面介绍的StaticFileMiddleware中间件会 ...

  9. 自己来实现一个简易的OCR

    来做个简易的字符识别 ,既然是简易的 那么我们就不能用任何的第三方库 .啥谷歌的 tesseract-ocr, opencv 之类的 那些玩意是叼 至少图像处理 机器视觉这类课题对我这种高中没毕业的人 ...

  10. asp.net mvc 验证码

    效果图 验证码类 namespace QJW.VerifyCode { //用法: //public FileContentResult CreateValidate() //{ // Validat ...