(本文主要講一下現(xiàn)在比較流行的一種布局方式----瀑布流布局 如有寫的不好的地方 還請多多指正 感謝)
1晦墙、功能分析
如圖所示: 我們可以看到該布局中的每個元素有一個共同的特點就是等寬不等高. 而且當(dāng)一行排列完成之后在下一行進(jìn)行排列時都是從最短的那一列開始排,否則的話就會讓每一列的差距越來越大從而顯得非常不美觀.
2、實現(xiàn)思路
- 根據(jù)需求 該頁面需要有滾動效果 而且可以展示很多數(shù)據(jù) 所以決定用UICollectionView來完成 那么UICollectionView中具體的每一個cell如何排列就是我們需要解決的問題了.也就是說我們需要計算出每一個cell的frame.
- 接下來就對cell的x值,y值,寬度,高度進(jìn)行逐一計算
- 寬度w
我們可以很直觀的從圖中看出寬度w=(collectionView的寬度 - cell左邊的邊距 - cell右邊的邊距 - (總共的列數(shù) - 1) * 每一列之間的間距) / 總共的列數(shù) - 高度h
高度h是根據(jù)具體項目中的模型本身的高度來決定 - x,y值
根據(jù)上圖可以發(fā)現(xiàn)每一列中所有cell的x值是一樣的 所以要算x值只需要求出列號就行了. y值就是最短的那一列的cell最大y值再加上間距
綜上所述,現(xiàn)在需要做的首要任務(wù)就是找出最短的那一列.所以我們需要通過遍歷每一列的高度來找出最短的那一列.
3、code
以上進(jìn)行簡單分析之后就要開始動手了 既然是自定義布局 我們就需要創(chuàng)建一個繼承自UICollectionViewLayout
的類來實現(xiàn)布局 在這個類中我們需要實現(xiàn)以下幾個方法
-
- (void)prepareLayout
這個方法是用來進(jìn)行初始化的 實現(xiàn)代碼如下
-(void)prepareLayout
{
[super prepareLayout];
//清除之前計算的所有高度
[self.colunmHeights removeAllObjects];
for (NSInteger i = 0; i < ZDDefaultColumnCount; i++) {
[self.colunmHeights addObject:@(ZDDefaultEdgeInsets.top)];
}
//清除之前所有的布局
[self.attrsArray removeAllObjects];
//創(chuàng)建每一個cell對應(yīng)的布局屬性
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (NSInteger i = 0; i < count; i++) {
//創(chuàng)建位置
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
//獲取indexPath位置cell對應(yīng)的布局屬性
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attrsArray addObject:attrs];
}
}
其中self.colunmHeights
和self.attrsArray
是自己定義的兩個屬性 分別用來保存所有列的當(dāng)前高度以及所有cell的布局屬性(兩個都是可變數(shù)組類型)
-
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
這個方法是用來決定cell的排布 實現(xiàn)代碼如下
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
-
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
這個方法是用來返回indexPath的位置所對應(yīng)的cell的布局屬性的 實現(xiàn)代碼如下
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
//創(chuàng)建布局屬性
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
//collectionView的寬度
CGFloat collectionViewW = self.collectionView.frame.size.width;
//設(shè)置布局屬性的frame
CGFloat w = (collectionViewW - ZDDefaultEdgeInsets.left - ZDDefaultEdgeInsets.right - (ZDDefaultColumnCount - 1) * ZDDefaultColumnMargin) / ZDDefaultColumnCount;
CGFloat h = 50 + arc4random_uniform(100);
//找出高度最短的那一列
NSInteger destColumn = 0;
CGFloat minColumnHeight = [self.colunmHeights[0] doubleValue];
for (NSInteger i = 1; i < ZDDefaultColumnCount; i++) {
CGFloat columnHeight = [self.colunmHeights[i] doubleValue];
if (minColumnHeight > columnHeight) {
minColumnHeight = columnHeight;
destColumn = i;
}
}
CGFloat x = ZDDefaultEdgeInsets.left + destColumn * (w + ZDDefaultColumnMargin);
CGFloat y = minColumnHeight;
if (y != ZDDefaultEdgeInsets.top) {
y += ZDDefaultRowMargin;
}
attrs.frame = CGRectMake(x, y, w, h);
//更新最短那列的高度
self.colunmHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
// 記錄內(nèi)容的高度
CGFloat columnHeight = [self.columnHeights[destColumn] doubleValue];
if (self.contentHeight < columnHeight) {
self.contentHeight = columnHeight;
}
return attrs;
}
其中self.contentHeight
是自定義的一個屬性 用來保存內(nèi)容的高度
-
- (CGSize)collectionViewContentSize
這個方法是為了讓collectionView可以滾動起來 實現(xiàn)代碼如下
-(CGSize)collectionViewContentSize
{
return CGSizeMake(0, self.contentHeight + ZDDefaultEdgeInsets.bottom);
}
4疮跑、優(yōu)化
- 考慮到代碼的復(fù)用性 方便直接將代碼拖到下個項目中去
(比如具體要展示多少列,每個cell之間的間距等等都需要根據(jù)具體的項目需求來決定) 所以對代碼進(jìn)行優(yōu)化 - 優(yōu)化思路是根據(jù)
UITableViewDelegate
tableView具體展示什么數(shù)據(jù),展示多少數(shù)據(jù)都是由其數(shù)據(jù)源和代理方法來具體實現(xiàn)的,所以我也設(shè)計了一個代理屬性 具體顯示多少列 每個cell之間的間距都是有代理方法來實現(xiàn)的 具體實現(xiàn)代碼如下: - 在
ZDWaterfallLayout.h
文件中:
@class ZDWaterfallLayout;
@protocol ZDWaterfallLayoutDelegate <NSObject>
@required
-(CGFloat)waterfallLayout:(ZDWaterfallLayout *)waterfallLayout heightForItemAtIndex:(NSInteger)index itemWidth:(CGFloat)itemWidth;
@optional
//列數(shù)
-(CGFloat)columnCountInWaterfallLayout:(ZDWaterfallLayout *)waterfallLayout;
//每一列之間的間距
-(CGFloat)columnMarginInWaterfallLayout:(ZDWaterfallLayout *)waterfallLayout;
//每一行之間的間距
-(CGFloat)rowMarginInWaterfallLayout:(ZDWaterfallLayout *)waterfallLayout;
//cell的邊距
-(UIEdgeInsets)edgeInsetsInWaterfallLayout:(ZDWaterfallLayout *)waterfallLayout;
@end
@interface ZDWaterfallLayout : UICollectionViewLayout
/**布局代理屬性*/
@property (nonatomic,weak) id<ZDWaterfallLayoutDelegate>delegate ;
@end
- 在
ZDWaterfallLayout.m
文件中
-(CGFloat)rowMargin
{
if ([self.delegate respondsToSelector:@selector(rowMarginInWaterfallLayout:)]) {
return [self.delegate rowMarginInWaterfallLayout:self];
}else{
return ZDDefaultRowMargin;
}
}
-(CGFloat)columnMargin
{
if ([self.delegate respondsToSelector:@selector(columnMarginInWaterfallLayout:)]) {
return [self.delegate columnMarginInWaterfallLayout:self];
}else{
return ZDDefaultColumnMargin;
}
}
-(NSInteger)columnCount
{
if ([self.delegate respondsToSelector:@selector(columnCountInWaterfallLayout:)]) {
return [self.delegate columnCountInWaterfallLayout:self];
}else{
return ZDDefaultColumnCount;
}
}
-(UIEdgeInsets)edgeInsets
{
if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterfallLayout:)]) {
return [self.delegate edgeInsetsInWaterfallLayout:self];
}else{
return ZDDefaultEdgeInsets;
}
}
ZDDefaultRowMargin ZDDefaultColumnMargin ZDDefaultColumnCount ZDDefaultEdgeInsets
這是我自定義的默認(rèn)值
接下來想要改變布局效果就很簡單了 只需要通過具體實現(xiàn)幾個代理方法就可以搞定 比如我想排5列 只需要實現(xiàn)如下方法即可:
-(CGFloat)columnCountInWaterfallLayout:(ZDWaterfallLayout *)waterfallLayout
{
return 5;
}
顯示效果如下:
就是這么輕松愉快~
具體demo請看我的github