iOSUICollectionView自定义流水布局

    xiaoxiao2022-09-22  5

    UICollectionViewFlowLayout

    1:直接设置FlowLayout对象

    创建UICollectionViewLayout对象,通过设置UICollectionViewLayout对象属性的值可以设置item的基本布局,包括大小,间距,内边距等。

    UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical; // 滑动方向 flowLayout.minimumLineSpacing = 8; // 行间距 flowLayout.minimumInteritemSpacing = 6; // 列间距 flowLayout.itemSize = CGSizeMake(kScreenWidth/2, 90); // item 大小 flowLayout.sectionInset = UIEdgeInsetsMake(20, 15, 20, 15); // 内边距

    2:通过UICollectionViewDelegateFlowLayout代理方法返回

    // item 大小 - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath; // 每个分区的内边距 - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section; // 行间距 - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section; // 列间距 - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section; // SectionHeader 大小 - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section; // SectionFooter 大小 - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;

    注意:第一种和第二种方法可以混合使用,但不要同时使用。例如内边距行列间距通过FlowLayout对象设置,itemSize 通过代理动态返回。但是不要通过FlowLayout对象设置了行列间距之后, 还实现代理方法返回行列间距。同时使用不会报错,但没意义消耗系统资源。

    3:自定义布局

    当系统的布局无论你怎么设置都满足不了需求的时候,就需要用到自定义布局了。我们需要继承UICollectionViewFlowLayout,重写其计算布局的方法,来打到预期的效果。

    上图中,上半部分是系统默认的布局,下半部分是我自定义布局实现的。我们注意到系统布局是将 item 两端对齐,间距根据剩余的宽度自己缩放。UICollectionViewFlowLayout对象的minimumInteritemSpacing说的很清楚,我们设置的是最小间距。但是 UI 小姐姐希望间距相等,这个时候系统的布局就实现不了了。

    自定义布局的具体实现

    首先继承UICollectionViewFlowLayout不必多说,然后就是重写其布局方法。 1:重写prepareLayout,每次更新布局的时候collectionview都会首先调用这个方法,为将要开始的更新做准备,我们需要在此处计算好必要的布局信息并存储起来

    - (void)prepareLayout{ [super prepareLayout]; NSMutableArray *layoutInfoArr = [NSMutableArray array]; // 获取布局信息 NSInteger numberOfSections = [self.collectionView numberOfSections]; for (NSInteger section = 0; section < numberOfSections; section++){ NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section]; NSMutableArray *subArr = [NSMutableArray arrayWithCapacity:numberOfItems]; for (NSInteger item = 0; item < numberOfItems; item++){ NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section]; UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; [subArr addObject:attributes]; } // 添加到二维数组 [layoutInfoArr addObject:[subArr copy]]; } // 存储布局信息 self.attributesArray = [layoutInfoArr copy]; }

    2:重写并调用layoutAttributesForItemAtIndexPath方法计算布局

    // 根据自己的需求,计算每一个item的布局,如果 itemSize是动态的,可以配合代理方法拿到当前 indexPath 的大小 // 这里只自定义了item的布局 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ // 拿到系统为我们计算的布局 UICollectionViewLayoutAttributes *oldAttributes = [super layoutAttributesForItemAtIndexPath:indexPath]; // 创建一个我们期望的布局 UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; CGFloat itemX = self.sectionInset.left; // 默认X值 CGFloat itemY = oldAttributes.frame.origin.y; // Y值直接用系统算的 CGSize itemSize = oldAttributes.size; // 大小直接代理返回的 // 无需换行 && indexPath.row !=0 调整X值 (indexPath.row=0时 self.lastFrame 还未赋值) if (oldAttributes.frame.origin.x != itemX && indexPath.row != 0) { itemX = self.lastFrame.origin.x + self.lastFrame.size.width + self.minimumInteritemSpacing; } // 赋值 attributes.frame = CGRectMake(itemX, itemY, itemSize.width, itemSize.height); // 更新上一个item的位置 self.lastFrame = CGRectMake(itemX, itemY, itemSize.width, itemSize.height); return attributes; }

    3:重写layoutAttributesForElementsInRect:方法返回指定区域cell、Supplementary View和Decoration View的布局属性。

    // 找出了与指定区域有交接的UICollectionViewLayoutAttributes对象放到一个数组中,然后返回 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{ NSMutableArray *layoutAttributesArr = [NSMutableArray array]; [self.attributesArray enumerateObjectsUsingBlock:^(NSArray *array, NSUInteger i, BOOL * _Nonnull stop) { [array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *obj, NSUInteger idx, BOOL * _Nonnull stop) { if(CGRectIntersectsRect(obj.frame, rect)) { // 如果 item 在rect内 [layoutAttributesArr addObject:obj]; } }]; }]; return layoutAttributesArr; }

    4:如果需要自定义分区头部和尾部可以重写下面两个方法,并在prepareLayout里面做相应的处理

    - (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;

    - (nullable UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;

    5:重写- (CGSize)collectionViewContentSize方法,返回 contentSize。注意这个方法返回的尺寸是给UICollectionView的父类UIScrollView作为contentSize ,不是UICollectionView的视图尺寸。正是因为这一点,我们自定义layout如果想让它只能横向滑动,只需要将这个 size.height 设置成 collectionView.height 就行了。 这个方法会多次调用,所以最好是在prepareLayout里就计算好,设置为全局变量,直接返回

    - (CGSize)collectionViewContentSize{ return self.contentSize; }

    做完这些就可以实现上图的效果了

    全部代码

    #import "LGCollectionViewFlowLayout.h" @interface LGCollectionViewFlowLayout () @property (nonatomic, assign) CGRect lastFrame; // 上一个item的 布局 @property (nonatomic, strong) NSMutableArray *attributesArray; @end @implementation LGCollectionViewFlowLayout - (void)prepareLayout{ [super prepareLayout]; NSMutableArray *layoutInfoArr = [NSMutableArray array]; // 获取布局信息 NSInteger numberOfSections = [self.collectionView numberOfSections]; for (NSInteger section = 0; section < numberOfSections; section++){ NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section]; NSMutableArray *subArr = [NSMutableArray arrayWithCapacity:numberOfItems]; for (NSInteger item = 0; item < numberOfItems; item++){ NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section]; UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; [subArr addObject:attributes]; } // 添加到二维数组 [layoutInfoArr addObject:[subArr copy]]; } // 存储布局信息 self.attributesArray = [layoutInfoArr copy]; } // 直接使用系统计算的大小 不做更新了 //- (CGSize)collectionViewContentSize{ // return self.contentSize; //} // 找出了与指定区域有交接的UICollectionViewLayoutAttributes对象放到一个数组中,然后返回 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{ NSMutableArray *layoutAttributesArr = [NSMutableArray array]; [self.attributesArray enumerateObjectsUsingBlock:^(NSArray *array, NSUInteger i, BOOL * _Nonnull stop) { [array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *obj, NSUInteger idx, BOOL * _Nonnull stop) { if(CGRectIntersectsRect(obj.frame, rect)) { // 如果 item 在rect内 [layoutAttributesArr addObject:obj]; } }]; }]; return layoutAttributesArr; } // 根据自己的需求,计算每一个item的布局,如果 itemSize是动态的,可以配合代理方法拿到当前 indexPath 的大小 // 这里只自定义了item的布局 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ // 拿到系统为我们计算的布局 UICollectionViewLayoutAttributes *oldAttributes = [super layoutAttributesForItemAtIndexPath:indexPath]; // 创建一个我们期望的布局 UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; CGFloat itemX = self.sectionInset.left; // 默认X值 CGFloat itemY = oldAttributes.frame.origin.y; // Y值直接用系统算的 CGSize itemSize = oldAttributes.size; // 大小直接代理返回的 // 同一行 BOOL line = oldAttributes.frame.origin.y == self.lastFrame.origin.y; // 不换行 && (indexPath.row=0时 self.lastFrame 还未赋值) 调整X值 if (oldAttributes.frame.origin.x != itemX && indexPath.row != 0 && line) { itemX = self.lastFrame.origin.x + self.lastFrame.size.width + self.minimumLineSpacing; } // 赋值 attributes.frame = CGRectMake(itemX, itemY, itemSize.width, itemSize.height); // 更新上一个item的位置 self.lastFrame = CGRectMake(itemX, itemY, itemSize.width, itemSize.height); return attributes; } - (NSMutableArray *)attributesArray{ if (!_attributesArray) { _attributesArray = [NSMutableArray array]; } return _attributesArray; } @end

    使用的时候直接换一下类名就好了

    // itemSize 是通过代理动态计算的 LGCollectionViewFlowLayout *flowLayout = [[LGCollectionViewFlowLayout alloc] init]; flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical; flowLayout.minimumLineSpacing = 8; flowLayout.minimumInteritemSpacing = 6; flowLayout.sectionInset = UIEdgeInsetsMake(20, 15, 20, 15);

    参考:https://www.cnblogs.com/daxueshan/p/6222000.html

    最新回复(0)