MJRefresh是一款非常优秀的刷新控件。代码简洁,优雅。今天有时间对源代码阅读了一下。对MJRefresh的宏观设计非常赞叹。所谓大道至简就是这样吧。
 
MJRefresh所采用的主要设计模式非常简单,是类继承 + 模版方法设计模式。
所以子类也主要围绕着这几个模版方法和继承方法进行定制行为的。
 
模版方法设计模式:
由父类MJRefreshComponent定义方法接口并添加到执行步骤中,对象执行中,在特定时间一定会调用的方法。由子类在需要的时候进行自定义实现。
在MJRefreshComponent类中的重要模版方法如下:
[self prepare];//在父类initWithFrame方法调用
[self placeSubviews];//在父类layoutSubviews方法调用

类继承:父类定义了方法的基本实现,子类在此基础上进行持续增加,达到复杂功能。与模版方法的区别是没有固定的执行步骤。

在MJRefreshComponent类中的重要继承方法如下:
//状态设置
- (void)setState:(MJRefreshState)state
//事件监听
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{}
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change{}
- (void)scrollViewPanStateDidChange:(NSDictionary *)change{}
MJRefresh作为刷新组件,核心逻辑根据ScrollView的Offset不同更新相应的状态和数据,
根据方法名字应该是MJRefreshComponent类中的重要继承方法:
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{}
 
下面看一下其子类MJRefreshHeader对这个方法的实现:
MJRefreshHeader是父类MJRefreshComponent的子类,其方法声明结构如下:

红框内是主要实现代码应该就是这四个“覆盖父类方法”了
 
子类MJRefreshHeader的两个模版方法实现如下:

- (void)prepare
{
[super prepare]; // 设置key
self.lastUpdatedTimeKey = MJRefreshHeaderLastUpdatedTimeKey; // 设置高度
self.mj_h = MJRefreshHeaderHeight;
} - (void)placeSubviews
{
[super placeSubviews]; // 设置y值(当自己的高度发生改变了,肯定要重新调整Y值,所以放到placeSubviews方法中设置y值)
self.mj_y = - self.mj_h - self.ignoredScrollViewContentInsetTop;
}
方法prepare:设置属性值
方法placeSubviews:更新UI布局
子类填充后,父类按照约定的步骤时机执行。over!
 
子类MJRefreshHeader的覆盖方法实现如下:

- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
[super scrollViewContentOffsetDidChange:change]; // 在刷新的refreshing状态
if (self.state == MJRefreshStateRefreshing) {
// 暂时保留
//My:当NavigationBar从一个页面滑出时,可能被移除页面,其window为nil
if (self.window == nil) return; // sectionheader停留解决
//My:当scrollView向下偏移的距离超过它的contentInset的上间隔时,取距离大的
CGFloat insetT = - self.scrollView.mj_offsetY > _scrollViewOriginalInset.top ? - self.scrollView.mj_offsetY : _scrollViewOriginalInset.top;
//My:当这个距离超过了(刷新控件的高度 + 它的contentInset的上间隔)时,取它们的和值
insetT = insetT > self.mj_h + _scrollViewOriginalInset.top ? self.mj_h + _scrollViewOriginalInset.top : insetT;
//My:将这个合理的最大值,设置到它的contentInset的上间隔上。
self.scrollView.mj_insetT = insetT;
//My:实际露出的刷新空间高
self.insetTDelta = _scrollViewOriginalInset.top - insetT;
return;
} // 跳转到下一个控制器时,contentInset可能会变
_scrollViewOriginalInset = self.scrollView.mj_inset; // 当前的contentOffset
CGFloat offsetY = self.scrollView.mj_offsetY;
// 头部控件刚好出现的offsetY
CGFloat happenOffsetY = - self.scrollViewOriginalInset.top; // 如果是向上滚动到看不见头部控件,直接返回
// >= -> >
if (offsetY > happenOffsetY) return; // 普通 和 即将刷新 的临界点
//My:下拉距离正好是(刷新控件高度+contentInset的上间隔)
CGFloat normal2pullingOffsetY = happenOffsetY - self.mj_h;
//My:露出的高度/总高度
CGFloat pullingPercent = (happenOffsetY - offsetY) / self.mj_h; if (self.scrollView.isDragging) { // 如果正在拖拽
self.pullingPercent = pullingPercent; if (self.state == MJRefreshStateIdle && offsetY < normal2pullingOffsetY) {
//My:下拉度超过临界值 // 转为即将刷新状态
self.state = MJRefreshStatePulling;
} else if (self.state == MJRefreshStatePulling && offsetY >= normal2pullingOffsetY) {
//My:下拉度小于临界值 // 转为普通状态
self.state = MJRefreshStateIdle;
}
} else if (self.state == MJRefreshStatePulling) {
// 即将刷新 && 手松开 // 开始刷新
[self beginRefreshing];
} else if (pullingPercent < ) {
self.pullingPercent = pullingPercent;
}
}
该方法会随着ScrollView的滚动,其Offset会不断更新,此方法不不断被触发。
操作步骤大概思路是:
1.如果当前处于刷新状态,offset的改变时,设置scrollView的offset为(刷新控件的高度 + 它的contentInset的上间隔)。
2.否则的话,如果处于拖拽时,根据拖拽距离和当前控件状态,更新下一步控件的状态。
详细描述见上面的注释。
 
带有NavigationBar的UIScrollView,默认它的offset = {0, -64}; 默认它的contentInset = {64,0,0,0}
内容展示部分刚好在NavigationBar的下面
 
子类MJRefreshHeader的状态设置后,会调用如下方法,刷新控件的UI:

- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState // 根据状态做事情
if (state == MJRefreshStateIdle) {
if (oldState != MJRefreshStateRefreshing) return; // 保存刷新时间
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:self.lastUpdatedTimeKey];
[[NSUserDefaults standardUserDefaults] synchronize]; // 恢复inset和offset
[UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
self.scrollView.mj_insetT += self.insetTDelta; // 自动调整透明度
if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
} completion:^(BOOL finished) {
self.pullingPercent = 0.0; if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock();
}
}];
} else if (state == MJRefreshStateRefreshing) {
MJRefreshDispatchAsyncOnMainQueue({
[UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
if (self.scrollView.panGestureRecognizer.state != UIGestureRecognizerStateCancelled) {
CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
// 增加滚动区域top
self.scrollView.mj_insetT = top;
// 设置滚动位置
CGPoint offset = self.scrollView.contentOffset;
offset.y = -top;
[self.scrollView setContentOffset:offset animated:NO];
}
} completion:^(BOOL finished) {
[self executeRefreshingCallback];
}];
})
}
}
宏MJRefreshCheckState:检查旧状态与新状态是否一致,一致的话就返回。
从刷新转普通状态时:
保存刷新时间,调整菊花透明度,移动offset
转换成刷新状态时:
设置contentInset.top,设置offset
 
逻辑主干是上面的四个方法,其他的逻辑枝叶,想自己研究的话可以翻看源代码。
 
 

MJRefresh源码框架分析的更多相关文章

  1. golang 移动应用例子 example/basic 源码框架分析

    条件编译 我们在源码中可以看到2个文件: main.go 和 main_x.go 这两个包名都是 package main , 都有 main 函数. 不会冲突么? 答案是不会的, main_x.go ...

  2. android adb 源码框架分析(2 角色)【转】

    本文转载自:http://blog.csdn.net/luansxx/article/details/25203323 角色 l  服务 服务是提供特定功能的实体,接收请求,返回应答是服务直接最表现. ...

  3. android adb 源码框架分析(1 系统)【转】

    本文转载自:http://blog.csdn.net/luansxx/article/details/25203269 ‘ Adb模块包括adb,adbd,源代码都在system/core/adb目录 ...

  4. JUC同步器框架AbstractQueuedSynchronizer源码图文分析

    JUC同步器框架AbstractQueuedSynchronizer源码图文分析 前提 Doug Lea大神在编写JUC(java.util.concurrent)包的时候引入了java.util.c ...

  5. LinkedHashMap 源码详细分析(JDK1.8)

    1. 概述 LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题.除此之外,Linke ...

  6. 【Orleans开胃菜系列2】连接Connect源码简易分析

    [Orleans开胃菜系列2]连接Connect源码简易分析 /** * prism.js Github theme based on GitHub's theme. * @author Sam Cl ...

  7. RxJava && Agera 从源码简要分析基本调用流程(2)

    版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...

  8. FFmpeg的HEVC解码器源码简单分析:概述

    ===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...

  9. 自定义View系列教程03--onLayout源码详尽分析

    深入探讨Android异步精髓Handler 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Andr ...

随机推荐

  1. delphi 6数据库连接之长短模式(sqlserver)

    delphi 6数据库连接之长短模式(sqlserver) 标签: delphi数据库 2015-08-12 20:59 351人阅读 评论(0) 收藏 举报  分类: delphi(3)  版权声明 ...

  2. 一个初学者的指南,使用D3做数据绑定

    一个初学者的指南,使用D3做数据绑定 D3.js 是个强大的数据可视化库,可以做出惊艳的图表.比如:气泡图,线图和条形图--只需要很少行的代码 随着初学者对JavaScript的理解,可以将数组或者对 ...

  3. DBCC--EXTENTINFO/IND/PAGE--显示数据页信息

    DBCC EXTENTINFO得到对象分配的区DBCC EXTENTINFO(<dbname|dbid>,<tabelname|tableid>[,{indexname|ind ...

  4. JAVA 从头开始<二>

    一.JAVA_HOME 1.环境变量如果经常变更,就要经常操作到Path,可能会一不小心把什么东西给删了 2.最好新建一个环境变量 3.如果使用新环境变量 ①原来的写法 ②现在的写法 新建环境变量JA ...

  5. SpingBoot的认识和基本使用

    认识SpingBoot Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程. -使用springboot以后,搭建一个spring应 ...

  6. 1002. Find Common Characters

    Given an array A of strings made only from lowercase letters, return a list of all characters that s ...

  7. 深度认识 Sharding-JDBC:做最轻量级的数据库中间层

    转自: https://juejin.im/entry/5905ac37a22b9d0065e1199c 基于关系型数据库的水平扩展方案有很多开源的解决方案,但成熟稳定的产品凤毛麟角.当当自研的数据库 ...

  8. AVFoundation - 拍照(Simple)

    1:基础 /* 1:获取可用输入设备 AVCaptureDevice 2:设置输入设备: [AVCaptureDeviceInput deviceInputWithDevice:self.captur ...

  9. dataTable 参数说明

    下面是一些常用的参数列表,比较常用或者有价值的标示为绿色. 功能参数(Features) 参数名 说明 参考值 默认值 autoWidth 定义是否由控件自动控制列宽 Boolean true def ...

  10. 解决后台json数据返回的字段需要替换的问题

    有时候后台json数据返回的字段含有“id”,也有可能是有时候为了减少代码的冗余,两页面之间只是数据模型个别属性的区别,所以这时候最好是用到模型属性的替换,用新的属性替换返回的json数据的字段.这里 ...