MJRefresh研究

MJRefresh研究MJRefresh 框架的研究 MJRefresh 可能是大家用得最多的一个框架了吧 基本上就没几个 App 游戏除外 没有 UITableView 有 UITableView 的地方可能没有上拉加载 但是十有八九就有下拉刷新 本篇文章让我们来研究一下 MJRefresh 的实现原理 MJRefresh 框架内文件结构偷懒用 MindNode 画的 希望别介意 首先 我们得搞清楚 UITableVi

MJRefresh可能是大家用得最多的一个框架了吧.基本上就没几个App(游戏除外).没有UITableView.有UITableView的地方可能没有上拉加载,但是十有八九就有下拉刷新.

本篇文章让我们来研究一下MJRefresh的实现原理.

MJRefresh框架内文件结构

在这里插入图片描述

偷懒用MindNode画的,希望别介意.

首先.我们得搞清楚.UITableView的下拉刷新的那个肯定是一个UIView可以直接放在UITableView上.然后呢.平时我们并不能看到它.当我们把UITableView往下拉之后才可能看到,并且进入正在刷新的状态时会让上拉的header/上拉加载的footer”悬停”一会儿.直到刷新状态完毕.

假如你是MJRefresh的作者.想要做到这一步应该怎么做一般来说.大部分人的思维肯定是子类化一个UITableView.然后重写scrollViewDidScroll.然后算位置.然而这样的话我们集成的话就非常不方便了.所以正确的处理应该是通过某种手段获取到内部的滚动方法.不管是+load的方法交换也好.还是其他什么的也好.这样就避免了子类化集成不便的问题.

MJRefresh的解决方案:

@property(nonatomic, readonly) UIPanGestureRecognizer *panGestureRecognizer NS_AVAILABLE_IOS(5_0); // `pinchGestureRecognizer` will return nil when zooming is disabled. @property(nullable, nonatomic, readonly) UIPinchGestureRecognizer *pinchGestureRecognizer NS_AVAILABLE_IOS(5_0); // `directionalPressGestureRecognizer` is disabled by default, but can be enabled to perform scrolling in response to up / down / left / right arrow button presses directly, instead of scrolling indirectly in response to focus updates. @property(nonatomic, readonly) UIGestureRecognizer *directionalPressGestureRecognizer API_DEPRECATED("Configuring the panGestureRecognizer for indirect scrolling automatically supports directional presses now, so this property is no longer useful.", tvos(9.0, 11.0)); 

我们知道,UITableView的滚动是靠着手势来做的.就是上面截取的UIScrollView内部的panGestureRecognizer.第二个捏合是用来做缩放.第三个手势是应该是TV OS用来做按压力度什么的.这两个咱们目前先忽略掉.

因为scrollView的滚动是由手势来触发的.所以MJ使用了一个基类MJRefreshComponment来监听手势.

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

添加/移除监听以及监听内容的处理

#pragma mark - KVO监听 - (void)addObservers { NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentOffset options:options context:nil]; [self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentSize options:options context:nil]; self.pan = self.scrollView.panGestureRecognizer; [self.pan addObserver:self forKeyPath:MJRefreshKeyPathPanState options:options context:nil]; } - (void)removeObservers { [self.superview removeObserver:self forKeyPath:MJRefreshKeyPathContentOffset]; [self.superview removeObserver:self forKeyPath:MJRefreshKeyPathContentSize]; [self.pan removeObserver:self forKeyPath:MJRefreshKeyPathPanState]; self.pan = nil; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { // 遇到这些情况就直接返回 if (!self.userInteractionEnabled) return; // 这个就算看不见也需要处理 if ([keyPath isEqualToString:MJRefreshKeyPathContentSize]) { [self scrollViewContentSizeDidChange:change]; } // 看不见 if (self.hidden) return; if ([keyPath isEqualToString:MJRefreshKeyPathContentOffset]) { [self scrollViewContentOffsetDidChange:change]; } else if ([keyPath isEqualToString:MJRefreshKeyPathPanState]) { [self scrollViewPanStateDidChange:change]; } } 

根据监听,成功的把contentOffset的改变、contentSize的改变以及手势的改变传入到了这三个方法里面,我截取.m里面的是告诉大家这里只是空实现.具体处理是子类去处理.这里空实现可以防止子类忘记实现导致的崩溃23333

- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{} - (void)scrollViewContentSizeDidChange:(NSDictionary *)change{} - (void)scrollViewPanStateDidChange:(NSDictionary *)change{} 

言归正传.先说说- (void)willMoveToSuperview:(UIView *)newSuperview.
这个方法可以看做是View的生命周期中的将要添加到View上和将要从View上移除.

因为,比如目前有A和B两个View.[A addSubView:B].那么B就会willMoveToSuperView:了.然而[B removeFromSuperView]的时候也会调用willMoveToSuperView:.那么,区别在哪呢.

当添加到一个新视图上的时候,newSuperView的值不为nil.当从一个父视图上移除的时候.newSuperView为nil.这就是区别.

悬停的实现.

主要是根据上面的contentOffset监听改变的通知和当前的MJ状态来做的.

/ 刷新控件的状态 */ typedef NS_ENUM(NSInteger, MJRefreshState) { / 普通闲置状态 */ MJRefreshStateIdle = 1, / 松开就可以进行刷新的状态 */ MJRefreshStatePulling, / 正在刷新中的状态 */ MJRefreshStateRefreshing, / 即将刷新的状态 */ MJRefreshStateWillRefresh, / 所有数据加载完毕,没有更多的数据了 */ MJRefreshStateNoMoreData }; 

对于上面的状态来说.MJRefreshStateIdle状态其实没有什么必要讲(就是普通状态,没显示header/footer的状态).然后呢MJRefreshStatePulling状态和MJRefreshStateWillRefresh状态其实就是一种状态.同理,MJRefreshStateRefreshingMJRefreshStateNoMoreData也是一种状态.

为什么我进行上面的状态划分呢.其实仔细想想特别容易思考到.

首先,比如我们如果在设置mj_header之后再设置tableview.bounce = NO;.那么,这个下拉刷新是无法触发的.并且在scrollViewContentOffsetDidChange:里头的状态赋值都不会有比如当前是刷新状态,再赋值一个刷新状态进去的情况.正在刷新和没有更多数据的时候其实是用_scrollViewOriginalInset记录了一下原始的值.然后改变了contentInset的值来做到悬停效果.当刷新结束,状态就被置位MJRefreshStateIdle.然后重设contentInset.

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/214033.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午5:08
下一篇 2026年3月18日 下午5:08


相关推荐

  • 罗马字符转换数字_数字变成字符串怎么改过来

    罗马字符转换数字_数字变成字符串怎么改过来今年在力扣上做了一道这个题,还算简单,主要是理解规则。解法也有很多种,我这里用的是常规解法,先将输入进来的字符串转换为字符数组,然后进行一系列操作。题目:罗马数字包含以下七种字符:I,V,X,L,C,D和M。字符数值I1V5X10L50C100D500M1000例如,…

    2026年4月19日
    6
  • ubuntu20.04下pycharm无法输入中文快速解决办法

    ubuntu20.04下pycharm无法输入中文快速解决办法ubuntu20 04 下 pycharm 无法输入中文快速解决办法

    2026年2月10日
    2
  • source insight3.5注册码_sourceinsight激活成功教程版安装教程

    source insight3.5注册码_sourceinsight激活成功教程版安装教程sourceInsight的注册码是SI3US-361500-17409。

    2022年10月3日
    5
  • java对象池commons-pool-1.6详解(一)

    java对象池commons-pool-1.6详解(一)对象的创建和销毁在一定程度上会消耗系统的资源 虽然 jvm 的性能在近几年已经得到了很大的提高 对于多数对象来说 没有必要利用对象池技术来进行对象的创建和管理 但是对于有些对象来说 其创建的代价还是比较昂贵的 比如线程 tcp 连接 rpc 连接 数据库连接等对象 因此对象池技术还是有其存在的意义 Apache commons pool 1 6 提供的对象池主要有两种 一种是带 Key 的对象池 这种带

    2026年3月16日
    1
  • maven编译 Process terminated【已解决】

    maven编译 Process terminated【已解决】maven项目编译报错如下:点击【项目名】提示点击蓝色报错的链接,在idea中打开了settings文件,找到提示的报错位置最后发现是缩进或者空格不对导致该问题,建议在notepa++中复制粘贴过来就好了…

    2022年4月27日
    102
  • Linux 进程管理

    Linux 进程管理引言 nbsp nbsp nbsp nbsp 在 Linux 的内核的五大组成模块中 进程管理模块时非常重要的一部分 它虽然不像内存管理 虚拟文件系统等模块那样复杂 也不像进程间通信模块那样条理化 但作为五大内核模块之一 进程管理对我们理解内核的运作 对于我们以后的编程非常重要 同时 作为五大组成模块中的核心模块 它与其他四个模块都有联系 下面就对进程模块进行想写的介绍 首先要了解进程及其相关的概念 其次介

    2026年3月17日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号