内容简介:MJRefresh是MJ大神写的一个实现上拉刷新和下拉刷新的第三方库,这个库目前在很多有名的应用上都有使用看,下面就来分析一下MJRefresh的源码。下面创建一个绿色的UIScrollview,然后在UIScrollview上加上一个红色的视图作为子视图:然后我们看一下效果:
MJRefresh是MJ大神写的一个实现上拉刷新和下拉刷新的第三方库,这个库目前在很多有名的应用上都有使用看,下面就来分析一下MJRefresh的源码。
1.简单应用
下面创建一个绿色的UIScrollview,然后在UIScrollview上加上一个红色的视图作为子视图:
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 20, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
self.scrollView.backgroundColor = [UIColor greenColor];
[self.view addSubview:_scrollView];
self.scrollView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(refresh)];
self.scrollView.contentInset = UIEdgeInsetsMake(54, 0, 0, 0);
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height)];
view.backgroundColor = [UIColor redColor];
[self.scrollView addSubview:view];
复制代码
然后我们看一下效果:
在这里我们设置的contentInset.top = 54,54正是这个下拉控件的高度,所以整个下拉控件是完全可见的。
2.源码分析
我们首先看一下MJRefresh的源码的类的结构,由于上拉刷新和下拉刷新控件的原理基本一致,因此这里我们仅使用下拉刷新控件来分析:
下面我们从下拉刷新控件的使用开始来探索源码:
self.scrollView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(refresh)]; 复制代码
首先我们的疑问是UIScrollview的mj_header这个属性是哪里来的?我们找到 UIScrollview+MJRefresh.h 这个文件,这是写的UIScrollview的一个分类,在这个分类中我们找到了mj_header属性,mj_footer属性,但是分类申明属性是没有set方法和get方法的,那么怎么去赋值和取值呢?这时候就要用到runtime的关联属性方法了,我们在 UIScrollview+MJRefresh.m 文件中找到 - (void)setMj_header:(MJRefreshHeader *)mj_header 方法看看是不是像我猜想的那样:
MJRefreshNormalHeader 最终是继承自
MJRefreshComponent ,那么我们就先从
MJRefreshComponent
来看: 首先在MJRefresh.h文件中有一个枚举类表示刷新控件的状态:
/** 刷新控件的状态 */
typedef NS_ENUM(NSInteger, MJRefreshState) {
/** 普通闲置状态 */
MJRefreshStateIdle = 1,
/** 松开就可以进行刷新的状态 */
MJRefreshStatePulling,
/** 正在刷新中的状态 */
MJRefreshStateRefreshing,
/** 即将刷新的状态 */
MJRefreshStateWillRefresh,
/** 所有数据加载完毕,没有更多的数据了 */
MJRefreshStateNoMoreData
};
复制代码
其中默认状态是 MJRefreshStateIdle , 当我们拖拽UIScrollview的时候在没有到达临界点之前都是这个状态,当我们拖拽到了临界点之后就变成了 MJRefreshStatePulling 状态,这个状态就是松手就可以刷新的状态,当我们在 MJRefreshStatePulling 状态下松手就变成了 MJRefreshStateRefreshing 状态,即正在刷新状态。 MJRefreshComponnet 中有下列属性:
@property (weak, nonatomic) id refreshingTarget; @property (assign, nonatomic) SEL refreshingAction; @property (assign, nonatomic) MJRefreshState state; @property (assign, nonatomic, readonly) UIEdgeInsets scrollViewOriginalInset; @property (assign, nonatomic) CGFloat pullingPercent;
在 MJRefreshComponent.m 中的核心方法是: - (void)willMoveToSuperview:(UIView *)newSuperview ,这个方法是在视图加入父视图或者改变父视图的时候调用:
- (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 = -_scrollView.mj_insetL;
// 记录UIScrollView
_scrollView = (UIScrollView *)newSuperview;
// 设置永远支持垂直弹簧效果
_scrollView.alwaysBounceVertical = YES;
// 记录UIScrollView最开始的contentInset
_scrollViewOriginalInset = _scrollView.mj_inset;
// 添加监听
[self addObservers];
}
}
复制代码
在这个方法里面设置了 self.mj_x,self.mj_w, _scrollViewOriginalInset 这三个属性并且添加了观察者:
- (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];
}
复制代码
监听了scrollview的contentOffset和contentsize的改变,需要根据contentoffset的改变来变更刷新控件的状态。
MJRefreshHeader
接下来再来分析一下 MJRefreshComponnet 的子类 MJRefreshHeader 这个类,这个类是基础的下拉刷新控件类: 这个类的头文件中多了两个方法,这两个方法都是用来创建下拉刷新控件的,不同的是一个的回调是block,另一个的回调是@selector:
+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
{
MJRefreshHeader *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
}
+ (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action
{
MJRefreshHeader *cmp = [[self alloc] init];
[cmp setRefreshingTarget:target refreshingAction:action];
return cmp;
}
复制代码
在这个类中确定了上拉刷新控件的mj_y属性:
- (void)placeSubviews
{
[super placeSubviews];
// 设置y值(当自己的高度发生改变了,肯定要重新调整Y值,所以放到placeSubviews方法中设置y值)
self.mj_y = - self.mj_h - self.ignoredScrollViewContentInsetTop;
}
复制代码
由于 self.ignoredScrollViewContentInsetTop 一般是0,所以一般情况下就有:
self.mj_y = -self.mj_h; 复制代码
然后我们再来看一下一个最核心的方法: - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change ,这个方法是contentoffset发生变化时KVO产生的调用,
- (void)setState:(MJRefreshState)state
,我们看看这个方法里面做了什么:
总结一下 MJRefreshHeader 这个类,这个类实现了两个非常重要的方法,一个是 - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change ,这个方法是在用手拖拽scrollview导致contentoffset变化的时候调用的,在这个方法中会根据contentoffset的值来改变下拉刷新控件的状态。这个类实现的另一个很重要的方法是 - (void)setState:(MJRefreshState)state ,在 - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change 中改变属性,在 - (void)setState:(MJRefreshState)state 中根据属性的改变来做具体的事。 #####MJRefreshStateHeader ** MJRefreshStateHeader 继承自 MJRefreshHeader ,这个类是带有状态文字的刷新控件,没有箭头和菊花。这个类比较简单,主要是对状态label和最近刷新时间label进行布局:
MJRefreshNoramlHeader 是 MJRefreshStateHeader 的子类,它是默认的下拉刷新控件类,我们实例中用的就是这种,这种刷新控件是在拖拽的时候显示箭头,当开始刷新的时候箭头小时,显示菊花。
MJRefreshNoramlHeader 这个类做了两件事,一件事是布局上面说到的箭头视图和菊花视图,另外一件事是处理在拖拽过程中箭头和菊花的变化。
- 布局菊花和箭头:
- 处理拖拽过程中箭头和菊花的变化
MJRefreshGifHeader
MJRefreshGifHeader 这个类也是继承自 MJRefreshStateHeader ,它是带GIF的下拉刷新控件,就是把 MJRefreshNormalHeader 的箭头和菊花变成GIF,下面我们先看一下 MJRefreshNormal 的简单使用:
MJRefreshGifHeader *header = [MJRefreshGifHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewData)]; // Set the ordinary state of animated images [header setImages:idleImages forState:MJRefreshStateIdle]; // Set the pulling state of animated images(Enter the status of refreshing as soon as loosen) [header setImages:pullingImages forState:MJRefreshStatePulling]; // Set the refreshing state of animated images [header setImages:refreshingImages forState:MJRefreshStateRefreshing]; // Set header self.tableView.mj_header = header; 复制代码
下面我们就从 - (void)setImages:(NSArray *)images forState:(MJRefreshState)state 这个方法下手来看看具体实现:
- (void)setImages:(NSArray *)images forState:(MJRefreshState)state
{
[self setImages:images duration:images.count * 0.1 forState:state];
}
复制代码
这个方法还是主要调用 - (void)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state 这个方法:
- (void)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state
{
if (images == nil) return;
self.stateImages[@(state)] = images;
self.stateDurations[@(state)] = @(duration);
/* 根据图片设置控件的高度 */
UIImage *image = [images firstObject];
if (image.size.height > self.mj_h) {
self.mj_h = image.size.height;
}
}
复制代码
在这个方法中,使用了两个字典,一个字典用来存放各种状态下的图片数组,因为GIF本质上也就是多张图片循环播放嘛,另一个字典用来存放各种状态下GIF动画的周期。并且如果GIF图片的高度大于刷新控件的高度,那么就调整刷新控件的高度为GIF图片的高度。 我们再来看一下GIF视图的布局:
根据状态变化显示不同的GIF视图:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Phoenix解读 | Phoenix源码解读之索引
- Phoenix解读 | Phoenix源码解读之SQL
- Redux 源码解读 —— 从源码开始学 Redux
- AQS源码详细解读
- SDWebImage源码解读《一》
- MJExtension源码解读
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
How to Build a Billion Dollar App
George Berkowski / Little, Brown Book Group / 2015-4-1 / USD 24.95
Apps have changed the way we communicate, shop, play, interact and travel and their phenomenal popularity has presented possibly the biggest business opportunity in history. In How to Build a Billi......一起来看看 《How to Build a Billion Dollar App》 这本书的介绍吧!