iOS 瀑布流效果(模仿UITableView重用机制)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013672551/article/details/49404485
瀑布流:
由很多的格子组成,但是每个格子的宽度和高速都是不确定的,是在动态改变的,就像瀑布一样,是一条线一条线的。说明:使用tableView不能实现瀑布流式的布局,因为tableView是以行为单位的,它要求每行(cell)的高度在内部是一致的。本文章介绍了如何自定义一个瀑布流控件来展示信息,本文介绍模仿UITableView的做法自定义瀑布流包括可重用机制的实现。
                 

接口的实现:

接口的实现包括数据源代理和自身代理所需要用到的方法,我们模仿官方的UITableView可以写出自定义View的代理方法:

#import <UIKit/UIKit.h>
typedef enum {
    JYHWaterfallFlowViewMarginTypeTop,
    JYHWaterfallFlowViewMarginTypeBottom,
    JYHWaterfallFlowViewMarginTypeLeft,
    JYHWaterfallFlowViewMarginTypeRight,
    JYHWaterfallFlowViewMarginTypeRow,
    JYHWaterfallFlowViewMarginTypeColumn
}JYHWaterfallFlowViewMarginType;
@class JYHWaterfallFlowView, JYHWaterfallFlowCell;
/**
 *  数据源代理协议
 */
@protocol JYHWaterfallFlowDataSource<NSObject>
@required
/**
 *  该View共有多少个数据
 */
- (NSUInteger)numberOfCellsInWaterfallFlowView:(JYHWaterfallFlowView *)waterfallFlowView;

/**
 *  返回对应位置的cell
 */
- (JYHWaterfallFlowCell *)waterfallFlowView:(JYHWaterfallFlowView *)waterfallFlowView cellAtIndex:(NSUInteger)index;

@optional
/**
 * 该瀑布流要显示几列
 */
- (NSUInteger)numberOfColumnsInWaterfallFlowView:(JYHWaterfallFlowView *)waterflowFlowView;

@end

/**
 *  JYHWaterfallFlowView代理
 */
@protocol JYHWaterfallFlowDelegate <UIScrollViewDelegate>
@optional
/**
 *  第index位置cell对应的高度
 */
- (CGFloat)waterfallFlowView:(JYHWaterfallFlowView *)waterfallFlowView heightAtIndex:(NSUInteger)index;
/**
 *  选中第index位置的cell
 */
- (void)waterfallFlowView:(JYHWaterfallFlowView *)waterfallFlowView didSelectAtIndex:(NSUInteger)index;
/**
 *  返回间距
 */
- (CGFloat)waterfallFlowView:(JYHWaterfallFlowView *)waterflowFlowView marginForType:(JYHWaterfallFlowViewMarginType)type;
@end

@interface JYHWaterfallFlowView : UIScrollView
/**
 *  数据源代理
 */
@property (nonatomic, weak)id<JYHWaterfallFlowDataSource>dataSource;

/**
 *  JYHWaterfallFlowView代理
 */
@property (nonatomic, weak)id<JYHWaterfallFlowDelegate>waterfallFlowViewdalegate;

/**
 *  刷新数据方法
 */
- (void)reloadData;

/**
 *  根据标示符从缓存池中查找可重用的Cell
 *
 *  @param identifier 标示符
 *
 *  @return cell
 */
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
@end
这里需要注意dalegate的命名由于它的父类UIScrollView已经有个叫dalegate的属性了,这里我不小心调了好长时间。。

接下来实现reloadData方法,该方法主要是求出每个cell的Frame里面有一些小算法在里面:

- (void)reloadData {
    //cell的总数
    long numberOfcells = [self.dataSource numberOfCellsInWaterfallFlowView:self];
    //View的总列数
    long numberOfColumn = [self numberOfColumn];
    
    //间距
    CGFloat topM = [self marginForType:JYHWaterfallFlowViewMarginTypeTop];
    CGFloat rightM = [self marginForType:JYHWaterfallFlowViewMarginTypeRight];
    CGFloat bottomM = [self marginForType:JYHWaterfallFlowViewMarginTypeBottom];
    CGFloat leftM = [self marginForType:JYHWaterfallFlowViewMarginTypeLeft];
    CGFloat columnM = [self marginForType:JYHWaterfallFlowViewMarginTypeColumn];
    CGFloat rowM = [self marginForType:JYHWaterfallFlowViewMarginTypeRow];
    
    //cell的宽度
    CGFloat cellW = (self.bounds.size.width - (rightM + leftM) - (numberOfColumn - 1) * columnM) / numberOfColumn;
    
    // 用一个C语言数组存放所有列的最大Y值
    CGFloat maxYOfColumns[numberOfColumn];
    for (int i = 0; i<numberOfColumn; i++) {
        maxYOfColumns[i] = 0.0;
    }
    
    for (int i = 0; i < numberOfcells; i++) {
        //cell该放到那一列(最短的那一列),默认为第一列
        NSUInteger cellColumn = 0;
        //最短一列的最大Y值
        CGFloat maxOfcellColumn = maxYOfColumns[cellColumn];
        //求最短的一列和最短列的最大Y值
        for (int j = 1; j < numberOfColumn; j++) {
            if(maxYOfColumns[j] < maxOfcellColumn) {
                cellColumn = j;
                maxOfcellColumn = maxYOfColumns[j];
            }
        }
        CGFloat cellH = [self hightOfIndex:i];
        CGFloat cellX = leftM + cellColumn * (cellW + columnM);
        CGFloat cellY = 0.0;
        if (maxOfcellColumn == 0.0) {//首行
            cellY = topM;
        } else  {
            cellY = rowM + maxOfcellColumn;
        }
        
        CGRect cellFrame = CGRectMake(cellX, cellY, cellW, cellH);
        [self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]];
        //更新最短列的最大Y值
        maxYOfColumns[cellColumn] = CGRectGetMaxY(cellFrame);
    }
    //设置scrollView的ContentSize
    CGFloat contentH = maxYOfColumns[0];
    for (int j = 1; j<numberOfColumn; j++) {
        if (maxYOfColumns[j] > contentH) {
            contentH = maxYOfColumns[j];
        }
    }
    contentH += bottomM;
    self.contentSize = CGSizeMake(0, contentH);
}

接下来是重点了,重用cell,将不在屏幕上的cell放入到缓存池中,由于scrollView在滚动过程中会调用layoutSubviews这个方法,所以我们可以在这个方法里面进行一些重用操作:

/**
 *  当ScrollView滚动时会调用这个方法
 */
- (void)layoutSubviews {
    [super layoutSubviews];
    
    NSUInteger numberOfCells = [self.cellFrames count];
    for (int i = 0; i < numberOfCells; i++) {
        //取出cellFrame
        CGRect cellFrame = [self.cellFrames[i] CGRectValue];
        //优先取出字典中的cell
        JYHWaterfallFlowCell *cell = self.displayingCells[@(i)];
        if([self isInScreen:cellFrame]) {//在屏幕上出现
            if(cell == nil) {
                cell = [self.dataSource waterfallFlowView:self cellAtIndex:i];
                cell.frame = cellFrame;
                [self addSubview:cell];
                //存到字典中
                self.displayingCells[@(i)] = cell;
            }
        } else {
            if (cell) {
                [cell removeFromSuperview];
                [self.displayingCells removeObjectForKey:@(i)];
                
                //将cell放入到缓存池中
                [self.reusableSet addObject:cell];
            }
        }
    }
}

用访问标识从缓存池中取出cell:

-(id)dequeueReusableCellWithIdentifier:(NSString *)identifier {
    __block JYHWaterfallFlowCell *resuableCell;
    [self.reusableSet enumerateObjectsUsingBlock:^(JYHWaterfallFlowCell *cell, BOOL *stop) {
        if([cell.identifier isEqualToString:identifier]) {
            resuableCell = cell;
            *stop = YES;
        }
    }];
    if(resuableCell) {//用完后将cell从缓存池中移除,防止越积越多
        [self.reusableSet removeObject:resuableCell];
    }
    return resuableCell;
}

记得在这个waterfallFlowView加入到父控件时像UITableView一样刷新数据:

- (void)willMoveToSuperview:(UIView *)newSuperview {
    [self reloadData];
}

当然还有一些懒加载及一些小方法,在这里就不一一列出了,我把demo上传了,想完全了解的可以下载下来看看,本demo只有第一幅图的代码,第二幅图也就是自定义cell往cell里加东西而已。

Demo连接:http://download.csdn.net/detail/u013672551/9212271


阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页