MJRefresh自定義"L"形刷新

MJRefresh現(xiàn)在已經(jīng)12000多顆星.是目前為止使用最廣泛的刷新控件.基本滿足所有刷新需求

該框架的結(jié)構(gòu)設(shè)計(jì)得很清晰,使用一個基類MJRefreshComponent來做一些基本的設(shè)定,然后通過繼承的方式,讓MJRefreshHeaderMJRefreshFooter分別具備下拉刷新和上拉加載的功能。具體可以從下面的圖里看出來:

image

其中.簡單的區(qū)分一下MJRefreshBackFooterMJRefreshAutoFooter

MJRefreshBackFooter :會回彈到底部的上拉刷新控件

image

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形狀.背景接近灰色

image
-(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”.好處在于處理動畫

image
-(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)度.不斷地更新路徑.

image
-(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).直至刷新完成

image
  /**

   開始動畫

  */

 -(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];

});

 }

看看最終的效果

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容