MJRefresh現(xiàn)在已經(jīng)12000多顆星.是目前為止使用最廣泛的刷新控件.基本滿足所有刷新需求
該框架的結(jié)構(gòu)設(shè)計(jì)得很清晰,使用一個基類MJRefreshComponent來做一些基本的設(shè)定,然后通過繼承的方式,讓MJRefreshHeader和MJRefreshFooter分別具備下拉刷新和上拉加載的功能。具體可以從下面的圖里看出來:
其中.簡單的區(qū)分一下MJRefreshBackFooter與MJRefreshAutoFooter
MJRefreshBackFooter :會回彈到底部的上拉刷新控件
MJRefreshAutoFooter :會自動刷新的上拉刷新控件
MJRefrshComponent類
基本主要思想與邏輯脈絡(luò)均在基類里面.讓我們詳細(xì)看一下MJRefrshComponent類
在.h中聲明了
1.所有的刷新狀態(tài).
2.刷新方法相關(guān)回調(diào)
3.刷新狀態(tài)的控制
4.添加子類們需要實(shí)現(xiàn)的方法
1.所有的刷新狀態(tài):記錄每一個當(dāng)前刷新狀態(tài).并且單獨(dú)對每一種狀態(tài)做單獨(dú)的處理
/**刷新控件的狀態(tài)*/
typedef NS_ENUM(NSInteger,MJRefreshState){
/**普通閑置狀態(tài)*/
MJRefreshStateIdle = 1,
/**松開就可以進(jìn)行刷新的狀態(tài) */
MJRefreshStatePulling,
/**正在刷新中的狀態(tài)*/
MJRefreshStateRefreshing,
/**即將刷新的狀態(tài)*/
MJRefreshStateWillRefresh,
/**所有數(shù)據(jù)加載完畢,沒有更多的數(shù)據(jù)了 */
MJRefreshStateNoMoreData
};
2.刷新方法相關(guān)回調(diào):回調(diào)給外界處理相關(guān)狀態(tài)完成以后的操作
/** 進(jìn)入刷新狀態(tài)的回調(diào) */
typedef void (^MJRefreshComponentRefreshingBlock)();
/** 開始刷新后的回調(diào)(進(jìn)入刷新狀態(tài)后的回調(diào)) */
typedef void (^MJRefreshComponentbeginRefreshingCompletionBlock)();
/** 結(jié)束刷新后的回調(diào) */
typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)();
3.刷新狀態(tài)的控制:
#pragma mark - 刷新狀態(tài)控制
/** 進(jìn)入刷新狀態(tài) */
- (void)beginRefreshing;
- (void)beginRefreshingWithCompletionBlock:(void (^)())completionBlock;
/** 開始刷新后的回調(diào)(進(jìn)入刷新狀態(tài)后的回調(diào)) */
@property (copy, nonatomic) MJRefreshComponentbeginRefreshingCompletionBlock beginRefreshingCompletionBlock;
/** 結(jié)束刷新的回調(diào) */
@property (copy, nonatomic) MJRefreshComponentEndRefreshingCompletionBlock endRefreshingCompletionBlock;
/** 結(jié)束刷新狀態(tài) */
- (void)endRefreshing;
- (void)endRefreshingWithCompletionBlock:(void (^)())completionBlock;
/** 是否正在刷新 */
@property (assign, nonatomic, readonly, getter=isRefreshing) BOOL refreshing;
//- (BOOL)isRefreshing;
/** 刷新狀態(tài) 一般交給子類內(nèi)部實(shí)現(xiàn) */
@property (assign, nonatomic) MJRefreshState state;
4.添加子類們需要實(shí)現(xiàn)的方法
/** 初始化 */
- (void)prepare NS_REQUIRES_SUPER;
/** 擺放子控件frame */
- (void)placeSubviews NS_REQUIRES_SUPER;
/** 當(dāng)scrollView的contentOffset發(fā)生改變的時(shí)候調(diào)用 */
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change NS_REQUIRES_SUPER;
/** 當(dāng)scrollView的contentSize發(fā)生改變的時(shí)候調(diào)用 */
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change NS_REQUIRES_SUPER;
/** 當(dāng)scrollView的拖拽狀態(tài)發(fā)生改變的時(shí)候調(diào)用 */
- (void)scrollViewPanStateDidChange:(NSDictionary *)change NS_REQUIRES_SUPER;
/** 拉拽的百分比(交給子類重寫) */
@property (assign, nonatomic) CGFloat pullingPercent;
/** 根據(jù)拖拽比例自動切換透明度 */
@property (assign, nonatomic, getter=isAutoChangeAlpha) BOOL autoChangeAlpha MJRefreshDeprecated("請使用automaticallyChangeAlpha屬性");
/** 根據(jù)拖拽比例自動切換透明度 */
@property (assign, nonatomic, getter=isAutomaticallyChangeAlpha) BOOL automaticallyChangeAlpha;
在.m中還添加了對scrollView的offSize以及contentSize等狀態(tài)進(jìn)行監(jiān)聽.并獲取當(dāng)當(dāng)前的刷新狀態(tài).做對應(yīng)的操作.相關(guān)代碼如下
- (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];
}
2.狀態(tài)變化的響應(yīng)
- (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];
}
}
3.隨著相關(guān)偏移狀態(tài)變化.留給子類做相關(guān)操作的函數(shù)
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{}
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change{}
- (void)scrollViewPanStateDidChange:(NSDictionary *)change{}
MJRefreshHeader類
主要看一個覆蓋父類函數(shù)的處理:
-(void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
[super scrollViewContentOffsetDidChange:change];
//在刷新的refreshing狀態(tài)
if(self.state == MJRefreshStateRefreshing){
if(self.window == nil)return;
// sectionheader停留解決-(-124)> 64 ? -(-124): 64.
//也就是說.獲取當(dāng)前的偏移值.當(dāng)偏移值大于頂部的inset時(shí)(下拉).那么就取偏移值.當(dāng)偏移值小于inset時(shí)(向上滾動).以頂部偏移值為偏移值. - 至少保證偏移值為正常的insetTop值.即還原!
CGFloat insetT = - self.scrollView.mj_offsetY > _scrollViewOriginalInset.top ? - self.scrollView.mj_offsetY : _scrollViewOriginalInset.top;
//一旦insetT大于了響應(yīng)刷新的最大值.(self.mj_h + _scrollViewOriginalInset.top).那么就以該最大值作為其偏移值.否則.是多少偏移值就是多少偏移值
insetT = insetT > self.mj_h + _scrollViewOriginalInset.top ? self.mj_h + _scrollViewOriginalInset.top : insetT;
//這樣能保證在刷新狀態(tài)時(shí).刷新的偏移值為top偏移值+最大自身高度
self.scrollView.mj_insetT = insetT;
self.insetTDelta = _scrollViewOriginalInset.top - insetT;
return;
}
//跳轉(zhuǎn)到下一個控制器時(shí),contentInset可能會變
_scrollViewOriginalInset = self.scrollView.mj_inset;
//當(dāng)前的contentOffset -當(dāng)前的偏移值
CGFloat offsetY = self.scrollView.mj_offsetY;
//頭部控件剛好出現(xiàn)的offsetY - 即剛出現(xiàn)的偏移值.一般是-64.或者-88.即狀態(tài)欄與導(dǎo)航欄的高度值
CGFloat happenOffsetY = - self.scrollViewOriginalInset.top;
//如果是向上滾動到看不見頭部控件,直接返回 - 不需要處理
// >= -> >
if(offsetY > happenOffsetY)return;
//普通 和 即將刷新 的臨界點(diǎn) . 即一般為導(dǎo)航欄+狀態(tài)欄+自身刷新控件的高度. -124
CGFloat normal2pullingOffsetY = happenOffsetY - self.mj_h;
//下拉進(jìn)度值.0 -無窮大.
CGFloat pullingPercent =(happenOffsetY - offsetY)/ self.mj_h;
if(self.scrollView.isDragging){ //如果正在拖拽
self.pullingPercent = pullingPercent;
if(self.state == MJRefreshStateIdle && offsetY < normal2pullingOffsetY){
//如果當(dāng)前是默認(rèn)狀態(tài).并且下拉的絕對值超過臨界點(diǎn)絕對值.即小于臨界值.就轉(zhuǎn)為即將刷新狀態(tài)
self.state = MJRefreshStatePulling;
} else if(self.state == MJRefreshStatePulling && offsetY >= normal2pullingOffsetY){
//如果當(dāng)前是松手就刷新的狀態(tài).并且上滾動的絕對值值小于臨界點(diǎn)的絕對值.即轉(zhuǎn)為普通狀態(tài)
self.state = MJRefreshStateIdle;
}
} else if(self.state == MJRefreshStatePulling){//即將刷新&&手松開
//開始刷新
[self beginRefreshing];
} else if(pullingPercent < 1){
self.pullingPercent = pullingPercent;
}
}
JTFQ_ActivityHeaderView
繼承至MJRefreshHeader.需要考慮的東西會更少 - 只需要重寫一些父類方法即可
pragma mark 在這里做一些初始化配置(比如添加子控件)
-(void)prepare
{
[super prepare];
#添加子控件.設(shè)定刷新控件的高度等.
}
pragma mark監(jiān)聽控件的刷新狀態(tài)
-(void)setState:(MJRefreshState)state
{
MJRefreshCheckState;
switch(state){
case MJRefreshStateIdle:
/*
普通閑置狀態(tài)
*/
[self refreshStateIdle];
break;
case MJRefreshStatePulling:
/*
松開就可以進(jìn)行刷新的狀態(tài)
*/
break;
case MJRefreshStateWillRefresh:
/*
無用-不是每次調(diào)用
*/
break;
case MJRefreshStateRefreshing:
/*
正在刷新狀態(tài)
*/
[self startAnimation];
break;
default:
break;
}
}
pragma mark 在這里設(shè)置子控件的位置和尺寸
-(void)placeSubviews
{
[super placeSubviews];
#在這里設(shè)置子控件的位置和尺寸
}
pragma mark 監(jiān)聽拖拽比例(控件被拖出來的比例)
-(void)setPullingPercent:(CGFloat)pullingPercent
{
#監(jiān)聽拖拽比例(根據(jù)控件被拖出來的比例.進(jìn)行一些動畫渲染).
}
現(xiàn)在只需要重寫以上幾個方法即可:
了解了整個MJRefresh的大致刷新思路以及相關(guān)類與函數(shù).現(xiàn)在就”L”形刷新進(jìn)行一個簡單的分析.
0.布局總刷新控件
1.閑置狀態(tài):首先是一個背景L.
2.拖拽進(jìn)度狀態(tài):隨后用藍(lán)色的layer進(jìn)行覆蓋.最后拉至全部覆蓋.并且達(dá)到可以刷新的條件時(shí).
3.開始刷新:覆蓋藍(lán)色逐漸減少至形成圓形的動畫.并一直旋轉(zhuǎn).直至刷新完成
4.刷新結(jié)束.將圓圈逐漸變細(xì)進(jìn)而消失-而后再恢復(fù)原狀態(tài).
代碼解析:
0.布局總刷新控件
-(void)placeSubviews
{
[super placeSubviews];
self.ltfAnimationView.center = CGPointMake(self.mj_w/2.0,self.mj_h /2.0);;
self.ltfAnimationView.bounds = kZHeaderLogoViewBounds;
}
1.閑置狀態(tài):首先是一個背景L.使用Bezier曲線畫出L形狀.背景接近灰色
-(CAShapeLayer *)bgLlayer
{
if(!_bgLlayer){
_bgLlayer =[[CAShapeLayer alloc]init];
UIBezierPath * bgPath =[UIBezierPath bezierPath];
[bgPath moveToPoint:CGPointMake(15,30)];
[bgPath addLineToPoint:CGPointMake(15,7.5)];
[bgPath moveToPoint:CGPointMake(25,27.5)];
[bgPath addLineToPoint:CGPointMake(15,27.5)];
_bgLlayer.fillColor = RGB(224.0,224.0,224.0).CGColor;
_bgLlayer.strokeColor = RGB(224.0,224.0,224.0).CGColor;
_bgLlayer.lineWidth = 5;
_bgLlayer.frame = CGRectMake(0,0,40,40);
_bgLlayer.path = bgPath.CGPath;
}
return _bgLlayer;
}
2.拖拽進(jìn)度狀態(tài):隨后用藍(lán)色的layer進(jìn)行覆蓋.最后拉至全部覆蓋.并且達(dá)到可以刷新的條件時(shí).
a.首先使用Bezier曲線畫出兩個元素.一個”I”與”-”.組裝為”L”.好處在于處理動畫
-(CAShapeLayer *)bottomLlayer
{
if(!_bottomLlayer){
_bottomLlayer =[CAShapeLayer layer];
_bottomLlayer.fillColor =[UIColor clearColor].CGColor;
_bottomLlayer.strokeColor =[UIColor colorWithRed:0.00 green:0.45 blue:0.98 alpha:1.00].CGColor;
_bottomLlayer.lineWidth = 5;
_bottomLlayer.frame = CGRectMake(0,0,40,40);
[self.ltfAnimationView.layer addSublayer:_bottomLlayer];
}
return _bottomLlayer;
}
-(CAShapeLayer *)topLlayer
{
if(!_topLlayer){
_topLlayer =[CAShapeLayer layer];
_topLlayer.fillColor =[UIColor clearColor].CGColor;
_topLlayer.strokeColor =[UIColor colorWithRed:0.00 green:0.45 blue:0.98 alpha:1.00].CGColor;
_topLlayer.lineWidth = 5;
_topLlayer.frame = CGRectMake(0,0,40,40);
[self.ltfAnimationView.layer addSublayer:_topLlayer];
}
return _topLlayer;
}
b.根據(jù)拖拽的進(jìn)度.不斷地更新路徑.
-(void)setPullingPercent:(CGFloat)pullingPercent
{
if(pullingPercent <= 0.5){
self.topLlayer.path = nil;
UIBezierPath * bottomLpath =[UIBezierPath bezierPath];
[bottomLpath moveToPoint:CGPointMake(25,27.5)];
[bottomLpath addLineToPoint:CGPointMake(25 - 25 * pullingPercent,27.5)];
self.bottomLlayer.path = bottomLpath.CGPath;
}else{
if(pullingPercent > 1){
pullingPercent = 1.0;
}
UIBezierPath * topLpath =[UIBezierPath bezierPath];
[topLpath moveToPoint:CGPointMake(15,25)];
[topLpath addLineToPoint:CGPointMake(15,25 - 35 *(pullingPercent - 0.5))];
self.topLlayer.path = topLpath.CGPath;
UIBezierPath * bottomLpath =[UIBezierPath bezierPath];
[bottomLpath moveToPoint:CGPointMake(12.5,27.5)];
[bottomLpath addLineToPoint:CGPointMake(25,27.5)];
self.bottomLlayer.path = bottomLpath.CGPath;
}
}
3.開始刷新:覆蓋藍(lán)色逐漸減少至形成圓形的動畫.并一直旋轉(zhuǎn).直至刷新完成
/**
開始動畫
*/
-(void)startAnimation{
self.bgLlayer.path = nil;
UIBezierPath * topLpath =[UIBezierPath bezierPath];
[topLpath moveToPoint:CGPointMake(15,25)];
[topLpath addLineToPoint:CGPointMake(15,7.5)];
self.topLlayer.path = topLpath.CGPath;
UIBezierPath * bottomLpath =[UIBezierPath bezierPath];
[bottomLpath moveToPoint:CGPointMake(25,27.5)];
[bottomLpath addLineToPoint:CGPointMake(12.5,27.5)];
self.bottomLlayer.path = bottomLpath.CGPath;
if([self.topLlayer.animationKeys containsObject:@"topLLayerStrokeEnd”]){
return;
}
if([self.bottomLlayer.animationKeys containsObject:@"bottomLLayerStrokeEnd”]){
return;
}
if([self.arcLlayer.animationKeys containsObject:@"arcLLayerStrokeEnd”]){
return;
}
//執(zhí)行動畫
CABasicAnimation * topLanimation =[CABasicAnimation animationWithKeyPath:@"strokeEnd”];
topLanimation.fromValue =@(1);
topLanimation.toValue = @(0);
topLanimation.duration = 2;//0.1;
topLanimation.removedOnCompletion = NO;
topLanimation.fillMode = kCAFillModeForwards;
[self.topLlayer addAnimation:topLanimation forKey:@"topLLayerStrokeEnd”];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2 * NSEC_PER_SEC)),dispatch_get_main_queue(),^{
CABasicAnimation * bottomLanimation =[CABasicAnimation animationWithKeyPath:@"strokeEnd”];
bottomLanimation.fromValue =@(1);
bottomLanimation.toValue = @(0);
bottomLanimation.duration =2;// 0.1;
bottomLanimation.removedOnCompletion = NO;
bottomLanimation.fillMode = kCAFillModeForwards;
[self.bottomLlayer addAnimation:bottomLanimation forKey:@"bottomLLayerStrokeEnd”];
//再次延后3秒執(zhí)行圓形的動畫//2
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2 * NSEC_PER_SEC)),dispatch_get_main_queue(),^{
UIBezierPath * arcLpath =[UIBezierPath bezierPathWithArcCenter:CGPointMake(20,20)radius:10 startAngle:M_PI * 2.0 / 3.0 endAngle:M_PI * 2.0 / 3.0+0.1 clockwise:NO];
self.arcLlayer.path = arcLpath.CGPath;
CABasicAnimation * arcBottomLanimation =[CABasicAnimation animationWithKeyPath:@"strokeEnd”];
arcBottomLanimation.fromValue =@(0);
arcBottomLanimation.toValue = @(1);
arcBottomLanimation.duration = 2;
[self.arcLlayer addAnimation:arcBottomLanimation forKey:@"arcLLayerStrokeEnd”];
//隨后一直旋轉(zhuǎn)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2 * NSEC_PER_SEC)),dispatch_get_main_queue(),^{
CABasicAnimation * arcLanimation =[CABasicAnimation animationWithKeyPath:@"transform.rotation.z”];
arcLanimation.fromValue =@(2 * M_PI);
arcLanimation.toValue = @(0);
arcLanimation.duration = 0.5;
arcLanimation.repeatCount = MAXFLOAT;
[self.arcLlayer addAnimation:arcLanimation forKey:@"runaroundAnim”];
self.hasRefreshed = YES;
});
});
});
}
4.刷新結(jié)束.將圓圈逐漸變細(xì)進(jìn)而消失-而后再恢復(fù)原狀態(tài).
/**
沒有結(jié)束的回調(diào)
*/
-(void)stopAnimation{
CABasicAnimation * arcLanimation =[CABasicAnimation animationWithKeyPath:@"lineWidth”];
arcLanimation.toValue = @(0);
arcLanimation.duration = 0.5;
self.arcLlayer.lineWidth = 0;
[self.arcLlayer addAnimation:arcLanimation forKey:nil];
}
-(void)endRefreshing
{
[self stopAnimation];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(0.5 * NSEC_PER_SEC)),dispatch_get_main_queue(),^{
[super endRefreshing];
});
}
看看最終的效果