MJRefresh分析-自定义刷新控件

一、UITableViewCell详解

二、自定义刷新控件步骤

①偏移量判断

②界面编写

③增加控件

④切换状态:初始-下拉刷新->header完全出现时开始刷新->数据获取完成时结束刷新(开始刷新-正在刷新-结束刷新)

⑤封装代码

⑥自动刷新和重复刷新

 代码实现:

//
//  ViewController.m
//  HKUtilities
//
//  Created by HuJinTao on 2018/10/24.
//  Copyright © 2018 HuJinTao. All rights reserved.
//

#import "ViewController.h"
#import "HKMacro.h"
@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>

/** 数据量 */
@property (nonatomic, assign) NSInteger dataCount;
/** tableView列表 */
@property (nonatomic, strong) UITableView * tableView ;

/** 上拉加载更多控件 */
@property (nonatomic, strong) UIView *footer;
/** 上拉加载更多控件里面的文字 */
@property (nonatomic, strong) UILabel *footerLabel;
/** 上拉加载更多控件时是否正在刷新 */
@property (nonatomic, assign, getter=isFooterRefreshing) BOOL footerRefreshing ;

/** 下拉刷新控件 */
@property (nonatomic, strong) UIView *header;
/** 下拉刷新控件里面的文字 */
@property (nonatomic, strong) UILabel *headerLabel;
/** 下拉加载更多控件时正在刷新 */
@property (nonatomic, assign, getter=isHeaderRefreshing) BOOL headerRefreshing ;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.dataCount = 3;
    self.navigationItem.title = @"自定义刷新控件";
    self.view.backgroundColor = kLightGrayColor;
    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, HK_NAVBAR_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT-HK_NAVBAR_HEIGHT) style:UITableViewStylePlain];
    [self.view addSubview:self.tableView];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
    [self setupRefresh];
    self.tableView.scrollIndicatorInsets = self.tableView.contentInset;//设置滚动条的contentInset
    self.tableView.contentInset = UIEdgeInsetsMake(0, 0, HK_TABBAR_HEIGHT, 0);
    
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    //根据数据量设置footer是否显示(有数据显示,无数据隐藏)
    self.footer.hidden = (self.dataCount == 0);
    return self.dataCount;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    cell.textLabel.text = [NSString stringWithFormat:@"自定义刷新控件-%ld",(long)indexPath.row];
    return cell;
}
- (void)setupRefresh {
    //FIXME:②界面编写
    //广告条
    UILabel * label = [UILabel new];
    label.backgroundColor = kBlackColor;
    label.textColor = kWhiteColor;
    label.text = @"广告";
    label.textAlignment = NSTextAlignmentCenter;
    label.frame = CGRectMake( 0, 0, 0, 30);
    self.tableView.tableHeaderView = label;

    //header
    UIView *header = [[UIView alloc] init];
    header.frame = CGRectMake(0, -50, self.tableView.width, 50);
    self.header = header;
    //FIXME:③增加控件
    [self.tableView addSubview:header];

    UILabel *headerLabel = [[UILabel alloc] init];
    headerLabel.frame = CGRectMake(0, 0, header.width, header.height);
    headerLabel.backgroundColor = kRedColor;
    headerLabel.text = @"下拉可以刷新";
    headerLabel.backgroundColor = kRedColor;
    headerLabel.textColor = kWhiteColor;
    headerLabel.textAlignment = NSTextAlignmentCenter;
    [header addSubview:headerLabel];
    self.headerLabel = headerLabel;
    //FIXME:不建议使用此属性
    //self.tableView.tableHeaderView = header;
    
    //footer
    self.footer = [[UIView alloc] init];
    self.footer.frame = CGRectMake(0, 0, self.tableView.width, 35);
    self.footerLabel = [[UILabel alloc] init];
    self.footerLabel.frame = CGRectMake(0, 0, self.footer.width, 35);
    self.footerLabel.backgroundColor = kRedColor;
    self.footerLabel.text = @"上拉可以加载更多";
    self.footerLabel.textColor = kWhiteColor;
    self.footerLabel.textAlignment = NSTextAlignmentCenter;
    [self.footer addSubview:self.footerLabel];
    self.tableView.tableFooterView = self.footer;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    return 35;
}

#pragma mark---------代理方法------
/** 用户松开scrollView时调用 */
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    
    //如果正在下拉刷新,直接返回
    if (self.isHeaderRefreshing) return;
    
    //当scrollView的偏移量y值 >= offsetY时,代表header已经完全出现
    CGFloat offsetY = -(self.tableView.contentInset.top + self.header.height);
    if (self.tableView.contentOffset.y <= offsetY) {
        //header开始刷新
        //FIXME:⑤封装代码
        [self headerBeginRefreshing];
    }
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
    //处理Header
    [self dealHeader];
    
    //处理Footer
    [self dealFooter];
}

/** 处理Hooter */
- (void)dealHeader {
    
    
    //如果正在下拉刷新,直接返回
    if (self.isHeaderRefreshing) return;
    //FIXME:④切换状态
    //当scrollView的偏移量y值 >= offsetY时,代表footer已经完全出现
    CGFloat offsetY = -(self.tableView.contentInset.top + self.header.height);
    if (self.tableView.contentOffset.y <= offsetY) {
        //header已经完全出现
        self.headerLabel.text = @"松开立即刷新";
        self.headerLabel.backgroundColor = kGrayColor;
    }else {
        self.headerLabel.text = @"下拉可以刷新";
        self.headerLabel.backgroundColor = kRedColor;
    }
}
/** 处理footer */
- (void)dealFooter{
    //如果没有内容,则不需要执行这个操作
    if (self.tableView.contentSize.height == 0) return;
    //如果正在刷新,直接返回
    if (self.isFooterRefreshing) return;
    
    //FIXME:①偏移量判断
    //当scrollView的偏移量y值 >= offsetY时,代表footer已经完全出现
    //A.footer 完全出现时的偏移量
    //scrollView.contentOffset.y = 内容高度+底部内边距-frame高度
    //CGFloat ofsetY = self.tableView.contentSize.height + self.tableView.contentInset.bottom;
    //B.假设出现一半的时候修改状态,变为正在加载中...
    // CGFloat ofsetY = self.tableView.contentSize.height + self.tableView.contentInset.bottom - self.tableView.height-self.tableView.tableFooterView.height/2;
    //C.刚出现就修改状态
    CGFloat ofsetY = self.tableView.contentSize.height + self.tableView.contentInset.bottom - self.tableView.height-self.tableView.tableFooterView.height;
    if (self.tableView.contentOffset.y >= ofsetY && self.tableView.contentOffset.y > -(self.tableView.contentInset.top)) {
        //footer已经完全出现,并且是往下拖拽
        //进入刷新状态
        //FIXME:⑤封装代码
        [self footerBeginRefreshing];
    }
}

#pragma mark - header
- (void)headerBeginRefreshing  {
    
    //如果正在刷新,直接返回
    if (self.isHeaderRefreshing) return;
    
    //header已经完全出现
    self.headerLabel.text = @"正在刷新数据...";
    self.headerLabel.backgroundColor = kBlueColor;
    self.headerRefreshing = YES;
    //增加内边距
    [UIView animateWithDuration:0.25 animations:^{
        UIEdgeInsets inset = self.tableView.contentInset;
        inset.top += self.header.height;
        self.tableView.contentInset = inset;
        //FIXME:⑥自动刷新和重复刷新
        //修改偏移量(稳住刷新状态)
        self.tableView.contentOffset = CGPointMake(self.tableView.contentOffset.x, - inset.top);
    }];
    
    HKLog(@"发送请求给服务器。下拉刷新数据");
    //发送请求给服务器。下拉刷新数据
    [self loadNewData];
}
- (void)headerEndRefreshing {
    self.headerRefreshing = NO;
    //减小内边距
    [UIView animateWithDuration:0.25 animations:^{
        UIEdgeInsets inset = self.tableView.contentInset;
        inset.top -= self.header.height;
        self.tableView.contentInset = inset;
    }];
    //self.headerLabel.text = @"下拉可以刷新";
    //self.headerLabel.backgroundColor = kRedColor;
}

#pragma mark - footer

- (void)footerBeginRefreshing {
    
    //如果正在刷新,直接返回
    if (self.isFooterRefreshing) return;//保证同一时间只执行一次
    
    //进入刷新状态
    self.footerRefreshing = YES;
    self.footerLabel.text = @"正在加载更多数据...";
    self.footerLabel.backgroundColor = kBlueColor;
    
    //发送请求给服务器-加载更多数据
    HKLog(@"发送请求给服务器-加载更多数据");
    [self loadMoreData];
}

- (void)footerEndRefreshing
{
    self.footerRefreshing = NO;
    self.footerLabel.text = @"上拉可以加载更多";
    self.footerLabel.backgroundColor = kRedColor;
}

#pragma mark - 数据处理
/** 发送请求给服务器。下拉刷新数据 */
- (void)loadNewData {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //服务器的数据回来了
        self.dataCount = 3;
        [self.tableView reloadData];
        //结束刷新
        [self headerEndRefreshing];
    });
}
/** 发送请求给服务器-加载更多数据 */
- (void)loadMoreData {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //服务器请求回来
        self.dataCount +=5;
        [self.tableView reloadData];
        //结束刷新
        [self footerEndRefreshing];
    });
}

@end


MethodList

效果图

 

Demo地址

三、第三方MJRefresh类结构图

MJRefresh 类结构图:

MIRefreshComponent:刷新控件的基类

MJRefreshHeader:基础的下拉刷新控件(Header)

MJRefreshStateHeader:带有状态文字的下拉刷新控件

MJRefreshNormalHeader:默认的下拉刷新控件

MJRefreshGitHeader:带动图的下拉刷新控件

 

MJRefreshFooter:基础的下拉刷新控件(Footer)

MJRefreshBackFooter:会回弹到底部的下拉刷新控件

MJRefreshAutoFooter:会自动刷新的上拉刷新控件

MJRefreshBackStateFooter:带有状态文字的上拉加载控件MJRefreshAutoStateFooter:带有状态文字的上拉加载控件

MIRefreshBackNormalFooter:默认的上拉加载控件

MIRefreshBackGifFooter:带动图的上拉加载控件

MIRefreshAutoNormalFooter:默认的上拉加载控件

MIRefreshAutoGifFooter:带动图的上拉加载控件

使用:

+ (MJRefreshGifHeader *)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock{

    

    MJRefreshGifHeader *header = [MJRefreshGifHeader headerWithRefreshingBlock:refreshingBlock];

    header.lastUpdatedTimeLabel.hidden = YES;

    header.stateLabel.hidden = YES;

    

    NSMutableArray *a = @[].mutableCopy;

    for (int i =0 ; i<4; i++) {

        [a addObject:[UIImage imageNamed:[NSString stringWithFormat:@"下啦刷新-%d",i+1]]];

    }

    [header setImages:a duration:0.5f forState:MJRefreshStateRefreshing];

    [header setImages:@[[UIImage imageNamed:@"松手加载-1"],[UIImage imageNamed:@"松手加载-2"]] duration:0.3f forState:MJRefreshStatePulling];

    [header setImages:@[[UIImage imageNamed:@"用力一点-1"],[UIImage imageNamed:@"用力一点-2"]] duration:0.5f forState:MJRefreshStateIdle];

    header.backgroundColor = [UIColor clearColor];

    return header;

}

 

+ (MJRefreshAutoNormalFooter *)footerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock{

    MJRefreshAutoNormalFooter *footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:refreshingBlock];

    footer.backgroundColor = [UIColor clearColor];

    footer.automaticallyRefresh = YES;

    footer.triggerAutomaticallyRefreshPercent = -3.0;

    return footer;

}

_collectionView = [[XBHomeCollectionView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)];

    _collectionView.homeDelegate = self;

    _collectionView.backgroundColor = kBlackColor;

    XBWeakSelf

    _collectionView.mj_header = [XBTool headerWithRefreshingBlock:^{

        XBStrongSelf

        [self reloadData:NO];

    }];

    _collectionView.mj_footer = [XBTool footerWithRefreshingBlock:^{

        XBStrongSelf

        [self reloadData:YES];

    }];

    [self.view addSubview:_collectionView];

    self.collectionView.mj_footer.hidden = YES;

 

-(void)reloadData:(BOOL)append {

    if (!append) {

        self.currentPage = 1;

        [_dataArray removeAllObjects];

    }else {

        self.currentPage += 1;

    }

    [self getData:append];

}

- (void)getData:(BOOL)append{

    XBWeakSelf

    //获取精选列表

    NSDictionary * dic = @{@"currentPage":[NSString stringWithFormat:@"%ld",(long)self.currentPage],

                           @"pageSize":[NSString stringWithFormat:@"%ld",(long)DefaultPageSize]

                           };

    [[HKNetService jsonManager] sendPOSTWithUrl:SERVER_HOMEHANDPICKLIST params:dic success:^(HKURLResponse *response, HKResultMessage *resultMessage) {

        XBStrongSelf

        NSDictionary * dic = response.result;

        NSArray * array = [dic valueForKey:@"data"];

 

        for (NSDictionary *dic in array) {

            XBHomeModel * model = [[XBHomeModel alloc] init];

            [model setValuesForKeysWithDictionary:dic];

            [self.dataArray addObject:model];

        }

        if ([response.responseData isKindOfClass:[NSDictionary class]] || [response.responseData isKindOfClass:[NSArray class]]) {

            NSString *b = [response.responseData JF_jsonString];

            [XBUserDefaults setObject:b forKey:HandpickListData];

            [XBUserDefaults synchronize];

        }

        [self endRefreshing];

        if (self.dataArray.count) {

            dispatch_async(dispatch_get_main_queue(), ^{

                [self.collectionView setDataArray:self.dataArray];

            });

        }

        if (array.count<DefaultPageSize) {

            self.collectionView.mj_footer.hidden = YES;

        }

        if (array.count == 0) {

            [self.collectionView.mj_footer endRefreshingWithNoMoreData];

        }

 

    } failure:^(HKURLResponse *response, HKResultMessage *resultMessage) {

        XBStrongSelf

        [self endRefreshing];

        [HKToast showErrorMessage:resultMessage];

    }];

}

- (void)endRefreshing {

    [self.collectionView.mj_header endRefreshing];

    [self.collectionView.mj_footer endRefreshing];

}

下拉刷新-动画图片

// 设置回调(一旦进入刷新状态,就调用target的action,也就是调用self的loadNewData方法)
MJRefreshGifHeader *header = [MJRefreshGifHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewData)];
// 设置普通状态的动画图片
[header setImages:idleImages forState:MJRefreshStateIdle];
// 设置即将刷新状态的动画图片(一松开就会刷新的状态)
[header setImages:pullingImages forState:MJRefreshStatePulling];
// 设置正在刷新状态的动画图片
[header setImages:refreshingImages forState:MJRefreshStateRefreshing];
// 设置header
self.tableView.mj_header = header;

原文地址:https://www.cnblogs.com/StevenHuSir/p/MJRefreshCustom.html