瀑布流布局提供了一种在有限的视图区域内尽可能多的展示更多的内容,它可以摒弃掉iOS默认布局(UICollectionViewFlowLayout)的不规整的缺点。百度图片就是使用的横向的流式布局方式:虽然图片的size各有不同,通过把他们约束在相同高度、不同长度的视图内(高度一定,等比例缩放长度),实现规整的排列。
iOS中实瀑布流有很多方式:多列UITableView、使用UIScrollView等等,但最常见的还是实现继承于UICollectionViewLayout的子类,自定义布局方式。在Code4App网站上一搜就能搜到很多,而且都是封装好的类直接可以拿来用。鉴于此这里只以一个简单例子介绍实现瀑布流的三个关键方法。
创建一个UICollectionViewLayout的子类:CustomViewLayout。
在.h 文件中添加两个属性:int型的m_numberOfColums 用于记录视图中有几列,float型的m_interItemSpacing用于记录图片视图之间的间距。在定义一个向使用改布局的UICollectionView对象的协议方法。这是.h文件应该是这样的:
#import <UIKit/UIKit.h> @class CustomViewLayout; @protocol CustomViewLayoutDelegate <NSObject> //--向代理询问每个item的高度的协议方法-- - (float)collectionView:(UICollectionView *)view layout:(CustomViewLayout *)layout heightForItemAtIndexPath:(NSIndexPath *)indexPath; @end @interface CustomViewLayout : UICollectionViewLayout @property (nonatomic,assign)int m_numberOfColums; @property (nonatomic,assign)float m_interItemSpacing; @end
在.m文件中添加两个属性:NSMutableArray类型的m_allItemAttributeArray 用于存储每个视图的布局信息(LayoutAttributes),NSMutableDictionary类型的m_everyColumsHeightDic 用于存储每列的最新高度,每次新加入一个视图时将向该字典询问最短的列,根据最短列的高度生成该视图的布局信息然后更新字典中该列的高度。
初始化方法:(给列数和视图间的间隔附上默认值)
- (instancetype)init{ if (self = [super init]) { _m_numberOfColums = 3; _m_interItemSpacing = 10; } return self; }
实现瀑布流的三个关键方法之一:- (void)prepareLayout;在这个方法中将生成每个视图的布局信息,其实就是为每个视图生成一个UICollectionViewLayoutAttributes类的对象,并给该对象的frame属性附上视图的frame信息,然后加入到m_allItemAttributeArray中去。具体实现如下:
- (void)prepareLayout{ [super prepareLayout]; //根据列数和间隔计算出每个视图的宽度 //视图的frame_width float itemWidth = (self.collectionView.bounds.size.width - (self.m_interItemSpacing * (self.m_numberOfColums + 1))) / self.m_numberOfColums; //初始化每列的原始高度 for (int i = 0; i < self.m_numberOfColums; i ++) { [self.m_everyColumsHeightDic setObject:@(0.0f) forKey:[@(i) description]]; } //每个分区的视图总数,为简化起见,将定只有一个分区 int itemCount = (int)[self.collectionView numberOfItemsInSection:0]; for (int i = 0; i < itemCount; i ++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0]; //得到高度最短的列 int minHeightColum = [self getMinHeightColum]; //视图的frame_x float x = self.m_interItemSpacing + (self.m_interItemSpacing + itemWidth) * minHeightColum; //视图的frame_y float y = [self.m_everyColumsHeightDic[[@(minHeightColum) description]] floatValue]; //视图的frame_height,是向代理对象(使用改布局方式的CollectonView的代理对象,这里只做一下强转)询问的视图的高度 float itemHeight = [(id<CustomViewLayoutDelegate>)self.collectionView.delegate collectionView:self.collectionView layout:self heightForItemAtIndexPath:indexPath]; //生成布局信息 UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; attribute.frame = CGRectMake(x, y, itemWidth, itemHeight); [self.m_allItemAttributeArray addObject:attribute]; y += self.m_interItemSpacing; y += itemHeight; //跟新每列的高度信息 [self.m_everyColumsHeightDic setObject:@(y) forKey:[@(minHeightColum) description]]; } }
实现瀑布流的三个关键方法之二:- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;在这个方法中当滑动UICollectionView时,UICollectionView对象会向布局方式实时询问将要展示在屏幕上的视图对象的布局信息。因此,在这里只需返回上一个方法计算都的布局信息即可具体实现如下:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{ //创建新的数组对象 NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.m_allItemAttributeArray.count]; [self.m_allItemAttributeArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { UICollectionViewLayoutAttributes *attribute = obj; //将指定区域(rect)内的布局信息放入该数组 if (CGRectIntersectsRect(rect, attribute.frame)) { [array addObject:attribute]; } }]; return array; }
实现瀑布流的三个关键方法之三:- (CGSize)collectionViewContentSize;这个方法就跟简单了,返回使用该布局方式的UICollectionView对象的size即可。具体实现如下:
- (CGSize)collectionViewContentSize{ NSString *str = [@([self getMaxHeightColum]) description]; return CGSizeMake(self.collectionView.bounds.size.width, [self.m_everyColumsHeightDic[str] floatValue]); //当某个UICollectionView使用该布局方式时([[UICollectionView alloc] initWithFrame:rect collectionViewLayout:layout]),self.collectionView属性会自动指向它, //这也是可以通过询问collectionView的代理对象获得自己学需要的视图高度信息的原因 //[(id<CustomViewLayoutDelegate>)self.collectionView.delegate collectionView:self.collectionView layout:self heightForItemAtIndexPath:indexPath] }
其他有关UICollectionView的更复杂、更任性的布局样式都是通过自定义布局方式(重写UICollectionViewLayout)实现的,其中最关键的方法应该属上面三个(当然还有其他可选的方法)。这里给出最简单的实现希望对理解复杂的实现有所帮助。
附一:
- (int)getMinHeightColum{ int count = (int)[self.m_everyColumsHeightDic count]; int minIndex = 0; float minHeight = [self.m_everyColumsHeightDic[@"0"] floatValue]; for (int i = 1; i < count; i ++) { if ([self.m_everyColumsHeightDic[[@(i) description]] floatValue] < minHeight) { minHeight = [self.m_everyColumsHeightDic[[@(i) description]] floatValue]; minIndex = i; } } return minIndex; } - (int)getMaxHeightColum{ int count = (int)[self.m_everyColumsHeightDic count]; int maxIndex = 0; float maxHeight = [self.m_everyColumsHeightDic[@"0"] floatValue]; for (int i = 1; i < count; i ++) { if ([self.m_everyColumsHeightDic[[@(i) description]] floatValue] < maxHeight) { maxHeight = [self.m_everyColumsHeightDic[[@(i) description]] floatValue]; maxIndex = i; } } return maxIndex; }
附二: