iOS 中的瀑布流原理及实现

/ 0评 / 4

越来越多的app让人眼花缭乱,展示的方式也各有不同,不过现在这么快节奏的生活方式下人们都追求简单直接暴力的表现形式,所以就出现了瀑布流,直接砸出一堆图片让人们的感受瞬间不同,下面就介绍一下瀑布流在iOS中的原理及其实现。

一、原理

瀑布流其实就是依托iOS中的 collectionView这个控件实现的,利用自定义的cell,一般都是给予cell固定的宽度(因为手机的屏幕宽度有限),然后根据内容的高度来给予cell的高度,让内容类似瀑布下流的形式展示出来,这里也需要自定义一下 collectionViewLayout 来达到这个效果。

二、代码实现

首先我们看一下自定义的 layout 布局

//这是头文件的内容
@class DCDefaultWaterLayout;

@protocol DCDefaultWaterLayoutDelegate 
@required
//每个cell高度指定代理
- (CGFloat) collectionView:(UICollectionView *) collectionView
                    layout:(DCDefaultWaterLayout *) layout
  heightForItemAtIndexPath:(NSIndexPath *) indexPath;
@end

@interface DCDefaultWaterLayout : UICollectionViewLayout

@property (nonatomic, assign) NSUInteger columnCount; // 列数
@property (nonatomic, assign) CGFloat itemWidth; // item的宽度
@property (nonatomic, assign) UIEdgeInsets sectionInset; // 每个section的边框间距
@property (nonatomic, assign) CGFloat minLineSpacing;  //每行每列的间隔

/**代理**/
@property (weak, nonatomic) id delegate;
@end

//这里是实现
@interface DCDefaultWaterLayout()
@property (nonatomic, assign) NSInteger itemCount; //item的个数
@property (nonatomic, strong) NSMutableArray *columnHeights;  // 每一列的总高度
@property (nonatomic, assign) CGFloat interitemSpacing;  //每列的间隔

@property (nonatomic, strong) NSMutableArray *itemAttributes;  // 每个item的attributes
@end


@implementation DCDefaultWaterLayout

#pragma mark - Accessors
- (void)setColumnCount:(NSUInteger)columnCount{
    if (_columnCount != columnCount) {
        _columnCount = columnCount;
        [self invalidateLayout];
    }
}

- (void)setItemWidth:(CGFloat)itemWidth{
    if (_itemWidth != itemWidth) {
        _itemWidth = itemWidth;
        [self invalidateLayout];
    }
}

- (void)setSectionInset:(UIEdgeInsets)sectionInset{
    if (!UIEdgeInsetsEqualToEdgeInsets(_sectionInset, sectionInset)) {
        _sectionInset = sectionInset;
        [self invalidateLayout];
    }
    
}


#pragma mark - Init
- (void)commonInit{
    _columnCount = 2;
    _itemWidth = 145.0f;
    _sectionInset = UIEdgeInsetsZero;
}

- (id)init{
    self = [super init];
    if (self) {
        [self commonInit];
    }
    return self;
}

#pragma mark - Life cycle
- (void)dealloc{
    [_columnHeights removeAllObjects];
    _columnHeights = nil;
    
    [_itemAttributes removeAllObjects];
    _itemAttributes = nil;
}

#pragma mark - Methods to Override
- (void)prepareLayout{
    [super prepareLayout];
    
    
    _itemCount = [[self collectionView] numberOfItemsInSection:0];
    
    NSAssert(_columnCount > 1, @"columnCount for UICollectionViewWaterfallLayout should be greater than 1.");
    
    CGFloat width = self.collectionView.frame.size.width - _sectionInset.left - _sectionInset.right;
    _interitemSpacing = floorf((width - _columnCount * _itemWidth) / (_columnCount - 1));
    _itemAttributes = [NSMutableArray arrayWithCapacity:_itemCount];
    _columnHeights = [NSMutableArray arrayWithCapacity:_columnCount];
    
    for (NSInteger idx = 0; idx < _columnCount; idx++) {
        [_columnHeights addObject:@(_sectionInset.top)];
    }
    
    // Item will be put into shortest column.
    for (NSInteger idx = 0; idx < _itemCount; idx++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:idx inSection:0];
        CGFloat itemHeight = [self.delegate collectionView:self.collectionView
                                                    layout:self
                                  heightForItemAtIndexPath:indexPath];
        NSUInteger columnIndex = [self shortestColumnIndex];
        CGFloat xOffset = _sectionInset.left + (_itemWidth + _interitemSpacing) * columnIndex;
        CGFloat yOffset = [(_columnHeights[columnIndex]) floatValue];
        
        UICollectionViewLayoutAttributes *attributes =
        [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        attributes.frame = CGRectMake(xOffset, yOffset, self.itemWidth, itemHeight);
        [_itemAttributes addObject:attributes];
        _columnHeights[columnIndex] = @(yOffset + itemHeight + _minLineSpacing);
    }
}

- (CGSize)collectionViewContentSize{
    if (self.itemCount == 0) {
        return CGSizeZero;
    }
    
    CGSize contentSize = self.collectionView.frame.size;
    NSUInteger columnIndex = [self longestColumnIndex];
    CGFloat height = [self.columnHeights[columnIndex] floatValue];
    contentSize.height = height - self.interitemSpacing + self.sectionInset.bottom + 64;;
    return contentSize;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path{
    return (self.itemAttributes)[path.item];
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
    return [self.itemAttributes filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes *evaluatedObject, NSDictionary *bindings) {
        return CGRectIntersectsRect(rect, [evaluatedObject frame]);
    }]];
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    return NO;
}

#pragma mark - Private Methods
// Find out shortest column.
- (NSUInteger)shortestColumnIndex{
    __block NSUInteger index = 0;
    __block CGFloat shortestHeight = MAXFLOAT;
    
    [self.columnHeights enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        CGFloat height = [obj floatValue];
        if (height < shortestHeight) {
            shortestHeight = height;
            index = idx;
        }
    }];
    
    return index;
}

// Find out longest column.
- (NSUInteger)longestColumnIndex{
    __block NSUInteger index = 0;
    __block CGFloat longestHeight = 0;
    
    [self.columnHeights enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        CGFloat height = [obj floatValue];
        if (height > longestHeight) {
            longestHeight = height;
            index = idx;
        }
    }];
    
    return index;
}

以上是自定义布局,自我们使用的时候只需要将collectionView的布局设定为我们自定的布局,然后实现布局的代理就可以了

- (void)createCollection{
    /**
     布局瀑布流效果, 要自定义布局样式, 继承自UICollectionViewLayout
     DCDefaultWaterLayout 是第三方的一个瀑布流样式
     */
    //    UICollectionViewFlowLayout
    DCDefaultWaterLayout *waterFlow = [[DCDefaultWaterLayout alloc] init];
    //1.设置item的宽度
    waterFlow.itemWidth = kItemWidth;
    //2.设置每个分区的缩进量
    waterFlow.sectionInset = UIEdgeInsetsMake(5, 5, 10, 5);
    //3.设置代理, 用来动态返回每一个item的高度
    waterFlow.delegate = self;
    //4.设置最小行间距
    waterFlow.minLineSpacing = 10;
    
    
    self.collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:waterFlow];
    self.collectionView.dataSource = self;
    self.collectionView.delegate = self;
    self.collectionView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:self.collectionView];
    
    [self.collectionView registerClass:[AvatarCollectionViewCell class] forCellWithReuseIdentifier:kUICollectionViewCell];
}


//动态返回每个item的高度 DCSimpleWaterLayout || DCDefaultWaterLayout
- (CGFloat) collectionView:(UICollectionView *) collectionView layout:(DCDefaultWaterLayout *) layout heightForItemAtIndexPath:(NSIndexPath *) indexPath {
    
    //根据对应的Model对象, 动态计算出每个item的高度,
    //按比例进行缩放,得到最终缩放之后的高度,返回
    Model *model = self.dataArr[indexPath.row];
    CGFloat randomHeight = kItemWidth / model.width.floatValue * model.height.floatValue;
    
    NSLog(@"=====%f",randomHeight);
    
    return randomHeight;
}

这样就得到了瀑布流的展示效果~

waterflow

评论已关闭。