MJRefresh 源码解读 + 使用
MJRefresh这个刷新控件是一款非常好用的框架,我们在使用一个框架的同时,最好能了解下它的实现原理,不管是根据业务要求在原有的基础上修改代码,还是其他的目的,弄明白作者的思路和代码风格,会受益匪浅。
前言
随着开发经验的不断积累,个人的能力也会不断提高。每个人的进步都会有一个过程,这个过程就好比登山。一个框架对我们的意义从开始的简单实用,慢慢的就会过渡到了解,最终让它成为你大脑的一部分。这篇文章不会对代码进行逐行解读,最重要的目的是让朋友们明白 MJRefresh 是怎么做到刷新的,我们还能如何扩展它的功能。
原理部分
我们在 MJRefresh 的 MJRefresh Github 上拿到了下边这张图:

正如这张图片所说的,所有的功能的实现都基于 MJRefreshComponent 这个刷新组件。MJRefreshComponent 有两个分支 MJRefreshHeader(给了下拉刷新的能力) MJRefreshFooter(给了上拉刷新的能力)。
在进行下边的代码分析之前,我们来看看刷新的整个过程(我们以头部刷新为例):
看这张图片上的代码,能够tableView.mj_header这样使用,说明tableview有mj_header这个属性。然后我们看一下UIScrollView+MJRefresh这个文件。原理是给这个scrollview添加了一个控件,代码如下
- 我看了下
MJRefreshHeader的这个文件的头文件,对我们理解原理并没有帮助,由于它是继承MJRefreshComponent的,所以我们打开MJRefreshComponent的头文件来看。
这几个状态非常关键,是使用整个框架的核心思路。为什么这么说?如果我们在滑动的过程中,状态也会改变,那么必然会调用状态的setter方法,我们就利用这个方法,显示自己想要的效果。
到这里基本上就很明白了。由于MJRefreshComponent是一个UIView,因此我们可以随意往上添加控件,我们在prepare中添加控件,在placeSubviews中布局,通过scrollViewContentOffsetDidChange:方法指导contentOffset改变了。然后就能自定义事件了。- 最终我们在我们最需要的状态上绑定事件就ok了。
MJRefreshComponent
MJRefreshComponent 这个类是最最基础能力的搭建了。除了暴露出业务接口外。值得注意的有两点:
- 上边
UIScrollView+MJRefresh中提到把mj_header这个view加到了scrollview上,那么MJRefreshComponent是如何获取scrollview的呢? - 如何监听我们需要的变化?比如说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 源码解读 + 使用的更多相关文章
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
- SDWebImage源码解读 之 NSData+ImageContentType
第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...
- SDWebImage源码解读 之 UIImage+GIF
第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...
- SDWebImage源码解读 之 SDWebImageCompat
第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...
- SDWebImage源码解读_之SDWebImageDecoder
第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...
- SDWebImage源码解读之SDWebImageCache(上)
第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...
- SDWebImage源码解读之SDWebImageCache(下)
第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...
- AFNetworking 3.0 源码解读 总结(干货)(下)
承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...
- AFNetworking 3.0 源码解读 总结(干货)(上)
养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...
随机推荐
- 在docker中运行ASP.NET Core Web API应用程序(附AWS Windows Server 2016 widt Container实战案例)
环境准备 1.亚马逊EC2 Windows Server 2016 with Container 2.Visual Studio 2015 Enterprise(Profresianal要装Updat ...
- Partition:增加分区
在关系型 DB中,分区表经常使用DateKey(int 数据类型)作为Partition Column,每个月的数据填充到同一个Partition中,由于在Fore-End呈现的报表大多数是基于Mon ...
- Partition:Partiton Scheme是否指定Next Used?
在SQL Server中,为Partition Scheme多次指定Next Used,不会出错,最后一次指定的FileGroup是Partition Scheme的Next Used,建议,在执行P ...
- Android 6.0 权限知识学习笔记
最近在项目上因为6.0运行时权限吃了亏,发现之前对运行时权限的理解不足,决定回炉重造,重新学习一下Android Permission. 进入正题: Android权限 在Android系统中,权限分 ...
- kafka学习笔记:知识点整理
一.为什么需要消息系统 1.解耦: 允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束. 2.冗余: 消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险. ...
- C#中如何给Excel添加水印
我们知道Microsoft Excel并没有内置的功能直接给Excel表添加水印,但是其实我们可以用其他变通的方式来解决此问题,如通过添加页眉图片或艺术字的方法来模仿水印的外观.所以在这篇文章中,我将 ...
- UVA, 10336 Rank the Languages
难点在于:递归函数和输出: #include <iostream> #include <vector> #include <algorithm> #include ...
- web服务器集群
概述 集群和分布式都是从集中式进化而来的.分布式和集群会相互合作的,同时的集群和分布式.在这里重点说说集群 集群是什么? 集群能提高单位时间内处理的任务数量,提升服务器性能 有多台服务器去处理任务,但 ...
- CSS margin详解
以下的分享是本人最近几天学习了margin知识后,大有启发,感觉以前对margin的了解简直太浅薄.所以写成以下文章,一是供自己整理思路:二是把知识分享出来,避免各位对margin属性的误解.内容可能 ...
- Spring代理模式及AOP基本术语
一.代理模式: 静态代理.动态代理 动态代理和静态代理区别?? 解析:静态代理需要手工编写代理类,代理类引用被代理对象. 动态代理是在内存中构建的,不需要手动编写代理类 代理的目的:是为了在原有的方法 ...