MJRefresh 源码解读 + 使用
2016-08-31 17:21
155 查看
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]; } } }
演示案例
我们就简单演示 转转 的下拉加载效果。由于代码改动比较小,在这里下载:转转下拉效果总结
有时间写一个类似知乎刷新那样的效果。相关文章推荐
- 图像分割之(四)OpenCV的GrabCut函数使用和源码解读
- 图像分割之(四)OpenCV的GrabCut函数使用和源码解读
- Alamofire源码解读系列(一)之概述和使用
- opencv之GrabCut函数使用和源码解读
- [源码解读]Silverlight 4 中对不规则对象进行碰撞检测(在游戏中常使用的是否碰撞怪物边界等原理)
- 图像分割之(四)OpenCV的GrabCut函数使用和源码解读
- [1.0]完美解读使用IDEA开发spark应用程序及spark源码阅读环境搭建
- 【dubbo源码解读系列】之一 使用eclipse调试dubbo源代码
- Caffe源码解读(四):proto文件的编写与使用
- beego 0.9.0 中智能路由AutoRouter的使用方法及源码解读
- 图像分割之(四)OpenCV的GrabCut函数使用和源码解读
- 图像分割之(四)OpenCV的GrabCut函数使用和源码解读
- JSPatch 源码解读 及使用
- 图像分割之(四)OpenCV的GrabCut函数使用和源码解读
- OpenCV的GrabCut函数使用和源码解读
- 图像分割之(四)OpenCV的GrabCut函数使用和源码解读
- [OpenCV]图像分割之(四)OpenCV的GrabCut函数使用和源码解读
- QCustomplot使用分享(二) 源码解读
- Caffe源码解读(八):使用训练好的模型