iOS GZWaterfall任何形式的瀑布流
技术:Object-C
概述
使用UICollectionView可以布局各种各样的瀑布流,下面我写了几种不同布局的瀑布流样式
详细
首先,将所有的类型展示给大家;
上面图片中展示的样式在Demo中都有实现。
一、项目结构
对于我们要实现的各种各样的 collectionView,根据不同的需求设置不同的列数 ,列边距,行边距,collectionView边距
二、程序实现
1、随机瀑布流
#pragma mark - 创建collectionView - (void)setupCollectionView { GZFallsLayout *fallsLayout = [[GZFallsLayout alloc] init]; fallsLayout.delegate = self; UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:fallsLayout]; [self.view addSubview:collectionView]; _collectionView = collectionView; collectionView.dataSource = self; [collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([GZShopCell class]) bundle:nil] forCellWithReuseIdentifier:ID]; } #pragma mark - 创建上下拉刷新 - (void)setupRefresh { self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewShops)]; self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreShops)]; self.collectionView.backgroundColor = [UIColor whiteColor]; [self.collectionView.mj_header beginRefreshing]; } #pragma mark - 加载下拉数据 - (void)loadNewShops { __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSArray *shops = [GZShop mj_objectArrayWithFilename:@"1.plist"]; [weakSelf.shops removeAllObjects]; dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf.collectionView reloadData]; [weakSelf.shops addObjectsFromArray:shops]; [weakSelf.collectionView.mj_header endRefreshing]; [weakSelf.collectionView reloadData]; }); }); } #pragma mark - 加载上拉数据 - (void)loadMoreShops { __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSArray *shops = [GZShop mj_objectArrayWithFilename:@"1.plist"]; [weakSelf.shops addObjectsFromArray:shops]; dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf.collectionView.mj_footer endRefreshing]; [weakSelf.collectionView reloadData]; }); }); } - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 1; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { self.collectionView.mj_footer.hidden = self.shops.count == 0; return self.shops.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { GZShopCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath]; if (self.shops && self.shops.count >= indexPath.item+1) cell.shop = self.shops[indexPath.item]; return cell; } - (CGFloat)columnMarginInFallsLayout:(GZFallsLayout *)fallsLayout { return 5; } - (CGFloat)rowMarginInFallsLayout:(GZFallsLayout *)fallsLayout { return 5; } - (CGFloat)columnCountInFallsLayout:(GZFallsLayout *)fallsLayout { return 4; } - (UIEdgeInsets)edgeInsetsInFallsLayout:(GZFallsLayout *)fallsLayout { return UIEdgeInsetsMake(0, 10, 20, 10); } - (NSMutableArray *)shops { if (!_shops) { _shops = [NSMutableArray array]; } return _shops; } // 计算布局属性 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; // 每个collectionView的宽度 CGFloat collectionViewW = self.collectionView.frame.size.width; // 每个cell的宽度 CGFloat w = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right - self.columnMargin * (self.columnCount - 1)) / self.columnCount; // cell的高度 NSUInteger randomOfHeight = arc4random() % 100; CGFloat h = w * (randomOfHeight >= 50 ? 250 : 320) / 200; // cell应该拼接的列数 NSInteger destColumn = 0; // 高度最小的列数高度 CGFloat minColumnHeight = [self.columnHeights[0] doubleValue]; // 获取高度最小的列数 for (NSInteger i = 1; i < self.columnCount; i++) { CGFloat columnHeight = [self.columnHeights[i] doubleValue]; if (minColumnHeight > columnHeight) { minColumnHeight = columnHeight; destColumn = i; } } // 计算cell的x CGFloat x = self.edgeInsets.left + destColumn * (w + self.columnMargin); // 计算cell的y CGFloat y = minColumnHeight; if (y != self.edgeInsets.top) { y += self.rowMargin; } // 随机数,用来随机生成大尺寸cell NSUInteger randomOfWhetherDouble = arc4random() % 100; // 判断是否放大 if (destColumn < self.columnCount - 1 // 放大的列数不能是最后一列(最后一列方法超出屏幕) && _noneDoubleTime >= 1 // 如果前个cell有放大就不放大,防止连续出现两个放大 && (randomOfWhetherDouble >= 45 || _noneDoubleTime >= 8) // 45%几率可能放大,如果累计8次没有放大,那么满足放大条件就放大 && [self.columnHeights[destColumn] doubleValue] == [self.columnHeights[destColumn + 1] doubleValue] // 当前列的顶部和下一列的顶部要对齐 && _lastDoubleIndex != destColumn) { // 最后一次放大的列不等当前列,防止出现连续两列出现放大不美观 _noneDoubleTime = 0; _lastDoubleIndex = destColumn; // 重定义当前cell的布局:宽度*2,高度*2 attrs.frame = CGRectMake(x, y, w * 2 + self.columnMargin, h * 2 + self.rowMargin); // 当前cell列的高度就是当前cell的最大Y值 self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame)); // 当前cell列下一列的高度也是当前cell的最大Y值,因为cell宽度*2,占两列 self.columnHeights[destColumn + 1] = @(CGRectGetMaxY(attrs.frame)); } else { // 正常cell的布局 if (_noneDoubleTime <= 3 || _lastFixIndex == destColumn) { // 如果没有放大次数小于3且当前列等于上次矫正的列,就不矫正 attrs.frame = CGRectMake(x, y, w, h); } else if (self.columnHeights.count > destColumn + 1 // 越界判断 && y + h - [self.columnHeights[destColumn + 1] doubleValue] < w * 0.1) { // 当前cell填充后和上一列的高度偏差不超过cell最大高度的10%,就和下一列对齐 attrs.frame = CGRectMake(x, y, w, [self.columnHeights[destColumn + 1] doubleValue] - y); _lastFixIndex = destColumn; } else if (destColumn >= 1 // 越界判断 && y + h - [self.columnHeights[destColumn - 1] doubleValue] < w * 0.1) { // 当前cell填充后和上上列的高度偏差不超过cell最大高度的10%,就和下一列对齐 attrs.frame = CGRectMake(x, y, w, [self.columnHeights[destColumn - 1] doubleValue] - y); _lastFixIndex = destColumn; } else { attrs.frame = CGRectMake(x, y, w, h); } // 当前cell列的高度就是当前cell的最大Y值 self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame)); _noneDoubleTime += 1; } // 返回计算获取的布局 return attrs; }
2、规则瀑布流
// 计算布局属性 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; if (indexPath.item == 0) { attrs.frame = CGRectMake(0, 0, ([UIScreen mainScreen].bounds.size.width - 4)/3*2 +2, ([UIScreen mainScreen].bounds.size.width - 4)/3*2 +2); }else if (indexPath.item == 1){ attrs.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width - 4)/3*2 +4, 0, ([UIScreen mainScreen].bounds.size.width - 4)/3, ([UIScreen mainScreen].bounds.size.width - 4)/3); }else if (indexPath.item == 2){ attrs.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width - 4)/3*2 +4, ([UIScreen mainScreen].bounds.size.width - 4)/3 +2, ([UIScreen mainScreen].bounds.size.width - 4)/3, ([UIScreen mainScreen].bounds.size.width - 4)/3); }else if (indexPath.item == 3){ attrs.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width - 4)/3*2 +4, ([UIScreen mainScreen].bounds.size.width - 4)/3 *2 + 4, ([UIScreen mainScreen].bounds.size.width - 4)/3, ([UIScreen mainScreen].bounds.size.width - 4)/3); }else if (indexPath.item == 4){ attrs.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width - 4)/3 +2, ([UIScreen mainScreen].bounds.size.width - 4)/3 *2 +4, ([UIScreen mainScreen].bounds.size.width - 4)/3, ([UIScreen mainScreen].bounds.size.width - 4)/3); }else{ attrs.frame = CGRectMake(0, ([UIScreen mainScreen].bounds.size.width - 4)/3 *2 +4, ([UIScreen mainScreen].bounds.size.width - 4)/3, ([UIScreen mainScreen].bounds.size.width - 4)/3); } // 返回计算获取的布局 return attrs; }
3、简单两排瀑布流
-(CGSize)collectionViewContentSize { //计算整个contentsize的大小 __block CGFloat height=0; [arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if([obj floatValue]>height) { height=[obj floatValue]; } }]; return CGSizeMake(self.collectionView.bounds.size.width, height); } -(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { //计算每一个item的相关属性 UICollectionViewLayoutAttributes *attr=[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; int index=0; if([arr[0] floatValue]<[arr[1] floatValue]) { index=0; } else { index=1; } CGFloat width=self.collectionView.bounds.size.width/2; CGFloat height=arc4random()%200+100; CGFloat left=index*width; CGFloat top=[arr[index] floatValue]; CGRect frame=CGRectMake(left, top, width, height); attr.frame=frame; arr[index]=@([arr[index] floatValue]+height); return attr; }
4、两排瀑布流
-(void)prepareLayout{ _attributeArray = [NSMutableArray array]; [super prepareLayout]; float WIDTH = ([UIScreen mainScreen].bounds.size.width-self.sectionInset.left-self.sectionInset.right-self.minimumInteritemSpacing)/2; CGFloat colHight[2] = {self.sectionInset.top,self.sectionInset.bottom}; for (int i=0; i<_itemCount; i++) { NSIndexPath * index = [NSIndexPath indexPathForItem:i inSection:0]; UICollectionViewLayoutAttributes * attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:index]; CGFloat height = arc4random()%150+40; int width = 0; if (colHight[0]<colHight[1]) { colHight[0] = colHight[0]+height+self.minimumLineSpacing; width = 0; }else{ colHight[1] = colHight[1]+height+self.minimumLineSpacing; width = 1; } attribute.frame = CGRectMake(self.sectionInset.left+(self.minimumInteritemSpacing+WIDTH)*width, colHight[width]-height-self.minimumLineSpacing, WIDTH, height); [_attributeArray addObject:attribute]; } if (colHight[0]>colHight[1]) { self.itemSize = CGSizeMake(WIDTH, (colHight[0]-self.sectionInset.top)*2/_itemCount-self.minimumLineSpacing); }else{ self.itemSize = CGSizeMake(WIDTH, (colHight[1]-self.sectionInset.top)*2/_itemCount-self.minimumLineSpacing); } }
5、环形瀑布流
_itemCount = (int)[self.collectionView numberOfItemsInSection:0]; _attributeArray = [NSMutableArray array]; CGFloat radius =MIN(self.collectionView.frame.size.width, self.collectionView.frame.size.height)/2; CGPoint center = CGPointMake(self.collectionView.frame.size.width/2, self.collectionView.frame.size.height/2); for (int i=0; i<_itemCount; i++) { UICollectionViewLayoutAttributes * attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; attris.size = CGSizeMake(50, 50); float x = center.x+cosf(2*M_PI/_itemCount*i)*(radius-25); float y = center.y+sinf(2*M_PI/_itemCount*i)*(radius-25); attris.center = CGPointMake(x, y); [_attributeArray addObject:attris]; }
6、立方瀑布流
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ //创建一个item布局属性类 UICollectionViewLayoutAttributes * atti = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; //获取item的个数 int itemCounts = (int)[self.collectionView numberOfItemsInSection:0]; //设置每个item的大小为260*100 atti.size = CGSizeMake(260, 100); atti.center = CGPointMake(self.collectionView.frame.size.width/2, self.collectionView.frame.size.height/2+self.collectionView.contentOffset.y); CATransform3D tran3d = CATransform3DIdentity; tran3d.m34 = -1/2000.0; CGFloat radius = 50/tanf(M_PI*2/itemCounts/2); // CGFloat angle = (float)(indexPath.row)/itemCounts*M_PI*2; //获取当前的偏移量 float offset = self.collectionView.contentOffset.y; //在角度设置上,添加一个偏移角度 float angleOffset = offset/self.collectionView.frame.size.height; CGFloat angle = (float)(indexPath.row+angleOffset-1)/itemCounts*M_PI*2; tran3d = CATransform3DRotate(tran3d, angle, 1.0, 0, 0); tran3d = CATransform3DTranslate(tran3d, 0, 0, radius); //进行设置 atti.transform3D = tran3d; return atti; }
7、球形瀑布流
UICollectionViewLayoutAttributes * atti = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; //获取item的个数 int itemCounts = (int)[self.collectionView numberOfItemsInSection:0]; atti.center = CGPointMake(self.collectionView.frame.size.width/2+self.collectionView.contentOffset.x, self.collectionView.frame.size.height/2+self.collectionView.contentOffset.y); atti.size = CGSizeMake(30, 30); CATransform3D trans3D = CATransform3DIdentity; trans3D.m34 = -1/900.0; CGFloat radius = 15/tanf(M_PI*2/itemCounts/2); //根据偏移量 改变角度 //添加了一个x的偏移量 float offsety = self.collectionView.contentOffset.y; float offsetx = self.collectionView.contentOffset.x; //分别计算偏移的角度 float angleOffsety = offsety/self.collectionView.frame.size.height; float angleOffsetx = offsetx/self.collectionView.frame.size.width; CGFloat angle1 = (float)(indexPath.row+angleOffsety-1)/itemCounts*M_PI*2; //x,y的默认方向相反 CGFloat angle2 = (float)(indexPath.row+angleOffsetx-1)/itemCounts*M_PI*2; //这里我们进行四个方向的排列 if (indexPath.row%4==1) { trans3D = CATransform3DRotate(trans3D, angle1, 1.0,0, 0); }else if(indexPath.row%4==2){ trans3D = CATransform3DRotate(trans3D, angle2, 0, 1, 0); }else if(indexPath.row%4==3){ trans3D = CATransform3DRotate(trans3D, angle1, 0.5,0.5, 0); }else{ trans3D = CATransform3DRotate(trans3D, angle1, 0.5,-0.5,0); } trans3D = CATransform3DTranslate(trans3D, 0, 0, radius); atti.transform3D = trans3D;
三、运行效果
本实例支付的费用只是购买源码的费用,如有疑问欢迎在文末留言交流,如需作者在线代码指导、定制等,在作者开启付费服务后,可以点击“购买服务”进行实时联系,请知悉,谢谢
手机上随时阅读、收藏该文章 ?请扫下方二维码