iOS之简单瀑布流的实现
zpxe3836
8年前
<p> </p> <h2>前言</h2> <p>超简单的瀑布流实现,这里说一下笔者的思路, <a href="/misc/goto?guid=4959670071458034649" rel="nofollow,noindex">详细代码在这里</a> 。</p> <p>效果演示</p> <p><img src="https://simg.open-open.com/show/d8059a96d5b7bd193af05ef57fa423b8.gif"></p> <h2>实现思路</h2> <p>collectionView能实现各中吊炸天的布局,其精髓就在于UICollectionViewLayout,因此我们要自定义一个layout来继承系统的UICollectionViewLayout,所有工作都在这个类中进行。</p> <h3>1.定义所需属性</h3> <p>瀑布流的思路就是,从上往下,那一列最短,就把下一个item放在哪一列,因此我们需要定义一个字典来记录每一列的最大y值</p> <p>每一个item都有一个attributes,因此定义一个数组来保存每一个item的attributes。</p> <p>我们还必须知道有多少列以及列间距、行间距、section到collectionView的边距。</p> <p><img src="https://simg.open-open.com/show/496398b8ced749bd32fc6b0b79386b3a.jpg"></p> <pre> <code class="language-java">//总列数 @property (nonatomic, assign) NSInteger columnCount; //列间距 @property (nonatomic, assign) NSInteger columnSpacing; //行间距 @property (nonatomic, assign) NSInteger rowSpacing; //section到collectionView的边距 @property (nonatomic, assign) UIEdgeInsets sectionInset; //保存每一列最大y值的数组 @property (nonatomic, strong) NSMutableDictionary *maxYDic; //保存每一个item的attributes的数组 @property (nonatomic, strong) NSMutableArray *attributesArray;</code></pre> <h3>2.重写系统方法</h3> <p>我们一共需要重写4个方法</p> <pre> <code class="language-java">1)- (void)prepareLayout 2)- (CGSize)collectionViewContentSize 3)- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 4)- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect</code></pre> <p>- (void)prepareLayout 方法</p> <p>布局前的一些准备工作都在这里进行, 初始化字典,有几列就有几个键值对,key为第几列,value为列的最大y值,初始值为上内边距:</p> <pre> <code class="language-java">for (int i = 0; i < self.columnCount; i++) { self.maxYDic[@(i)] = @(self.sectionInset.top); }</code></pre> <p>创建每个item的attributes,并存入数组:</p> <pre> <code class="language-java">//根据collectionView获取总共有多少个item NSInteger itemCount = [self.collectionView numberOfItemsInSection:0]; //为每一个item创建一个attributes并存入数组 for (int i = 0; i < itemCount; i++) { UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; [self.attributesArray addObject:attributes]; }</code></pre> <p>- (CGSize)collectionViewContentSize 方法</p> <p>用来计算collectionView的contentSize</p> <p>一般瀑布流只能垂直滚动,不能水平滚动,因此contentSize.width = 0,我们只需要计算contentSize.height即可</p> <p>从字典中找出最长列的最大y值,再加上下面的内边距,即为contentSize.height</p> <pre> <code class="language-java">- (CGSize)collectionViewContentSize { //假设第0列是最长的那列 __block NSNumber *maxIndex = @0; //遍历字典,找出最长的那一列 [self.maxYDic enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSNumber *obj, BOOL *stop) { //如果maxColumn列的最大y值小于obj,则让maxColumn等于obj所属的列 if ([self.maxYDic[maxIndex] floatValue] < obj.floatValue) { maxIndex = key; } }]; //collectionView的contentSize.height就等于最长列的最大y值+下内边距 return CGSizeMake(0, [self.maxYDic[maxIndex] floatValue] + self.sectionInset.bottom); }</code></pre> <p>- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 方法</p> <p>该方法则用来设置每个item的attributes,在这里,我们只需要简单的设置每个item的attributes.frame即可</p> <p>首先我们必须得知collectionView的尺寸,然后我们根据collectionView的宽度,以及列数、各个间距来计算每个item的宽度</p> <p>item的宽度 = (collectionView的宽度 - 内边距及列边距) / 列数</p> <p><img src="https://simg.open-open.com/show/480153378dae4e7148ec8ee02236b910.jpg"></p> <pre> <code class="language-java">CGFloat collectionViewWidth = self.collectionView.frame.size.width; //self.sectionInset.left:左边距 self.sectionInset.right:右边距 //(self.columnCount - 1) * columnSpacing:一行中所有的列边距 CGFloat itemWidth = (collectionViewWidth - self.sectionInset.left - self.sectionInset.right - (self.columnCount - 1) * self.columnSpacing) / self.columnCount;</code></pre> <p>接下来计算item的坐标,要想计算坐标,那就必须知道最短的那一列,先遍历字典,找出最短列是哪一列(minColumn)以及其最大y值。</p> <p>item的y值就等于最短列的最大y值再加上行间距,x值就等于左边距 + (item宽度 + 列间距) * minColumn</p> <p><img src="https://simg.open-open.com/show/c2a93d71fc67577004201498c58d5d9c.jpg"></p> <pre> <code class="language-java">//找出最短的那一列 __block NSNumber *minIndex = @0; [self.maxYDic enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSNumber *obj, BOOL *stop) { if ([self.maxYDic[minIndex] floatValue] > obj.floatValue) { minIndex = key; } }]; //根据最短列的列数计算item的x值 CGFloat itemX = self.sectionInset.left + (self.columnSpacing + itemWidth) * minIndex.integerValue; //item的y值 = 最短列的最大y值 + 行间距 CGFloat itemY = [self.maxYDic[minIndex] floatValue] + self.rowSpacing;</code></pre> <p>接下来便是item的高度,我们应该根据图片的原始尺寸以及计算出来的宽度,等比例缩放来计算高度,但是在layout类中,我们是拿不到图片的,因此我们可以定义一个block属性,或者代理,让外界来计算并返回给我们,我们需要将item的宽度以及indexPath传递给外界:</p> <pre> <code class="language-java">@property (nonatomic, strong) CGFloat(^itemHeightBlock)(CGFloat itemHeight,NSIndexPath *indexPath);</code></pre> <p>根据返回值来设置item的高度:</p> <pre> <code class="language-java">if (self.itemHeightBlock) itemHeight = self.itemHeightBlock(itemWidth, indexPath);</code></pre> <p>最后设置attributes的frame并更新字典:</p> <pre> <code class="language-java">//设置attributes的frame attributes.frame = CGRectMake(itemX, itemY, itemWidth, itemHeight); //更新字典中的最短列的最大y值 self.maxYDic[minIndex] = @(CGRectGetMaxY(attributes.frame));</code></pre> <p>- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 方法</p> <p>该方法用来返回rect范围内,item的attributes</p> <p>直接返回attributesArray即可</p> <pre> <code class="language-java">- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { return self.attributesArray; }</code></pre> <h2>使用</h2> <p>布局类写完了,接下来就可以直接使用了</p> <pre> <code class="language-java">//创建布局对象 XRWaterfallLayout *waterfall = [[XRWaterfallLayout alloc] init]; //设置相关属性 waterfall.columnCount = 3;//共多少列 waterfall.columnSpacing = 10;//列间距 waterfall.rowSpacing = 10;//行间距 waterfall.sectionInset = UIEdgeInsetsMake(10, 10 , 10, 10);//内边距 [waterfall setItemHeightBlock:^CGFloat(CGFloat itemWidth, NSIndexPath *indexPath) { //根据图片的原始尺寸,及显示宽度,等比例缩放来计算显示高度 XRImage *image = self.images[indexPath.item]; return image.imageH / image.imageW * itemWidth; }]; collectionView.collectionViewLayout = waterfall; ...... ......</code></pre> <p>具体代码请到这里下载: <a href="/misc/goto?guid=4959670071458034649" rel="nofollow,noindex">https://github.com/codingZero/XRWaterfallLayout</a> ,觉得不错的,请献上你的star</p> <p>来自: <a href="/misc/goto?guid=4959670071557462398" rel="nofollow">http://www.cocoachina.com/ios/20160407/15872.html</a></p>