UICollectionView Layout自定义 Layout布局
当系统自带的UICollectionViewFlowLayout无法满足我们的布局需求时
所以,了解并学习一下自定义Layout是很有必要。
其实可以分三个步骤:
- 实现prepareLayout方法时,在内部预先计算并存储必要的布局信息。
- 根据prepareLayout方法生成的布局信息,在使用UICollectionViewContentView 方法获取 navigator 的内容尺寸。
- 调用 layoutAttributesForElementsInRect: 方法以获取指定区域 cell、Supplementary View 以及Decoration View 所具有的布局属性.
熟悉了自定义布局的关键三步。为了更好地掌握自定义布局的应用方法,请问您想学习如何通过自定义布局实现一个类似grideView的功能吗?实际上,grideView的设计者已经为我们提供了现成的UICollectionViewFlowLayout组件。因此我们可以选择一个常用且易于理解的例子来演示。
我们建立了新项目BGCustomLayoutCollectionViewDemo。接着生成一个[BGGrideLayout]类的对象,并指出这一对象属于我们的自定义布局组件。
在BGGrideLayout里面,我们首先覆写prepareLayout方法。
prepareLayout是用来预先规划布局的,在 prepareLayout 方法中,在进行后续操作前先预判将需要用到的各项布局数据,并提前记录下来。从而避免后续任何方法因重复计算而导致效率低下。
举例来说,在 prepareLayout 方法中我们可以预先确定每个单元格 cell 的属性设置以及整个CollectionView的实际显示尺寸等关键参数。
该方法会在整个布局流程开始前执行一次初始化操作。
之后只有当需要重新校验布局(invalidateLayout)、判断布局边界是否发生变化(shouldInvalidateLayoutForBoundsChange:)返回 YES 或者需要刷新整个 CollectionView 时才再次被激活。
而在BGGrideLayout的prepareLayout方法中,我们有两个目的:
从给定的 indexPath 中提取出对应的 UICollectionViewLayoutAttributes 实例,并将其保存为二维数组 layoutInfoArr 的一个元素。
二是计算出内容尺寸并保存到全局变量 contentSize 当中。
代码如下:
- (void)prepareLayout{
[super prepareLayout];
NSMutableArray *layoutInfoArr = [NSMutableArray array];
NSInteger maxNumberOfItems = 0; //获取布局信息 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]; } if(maxNumberOfItems < numberOfItems){ maxNumberOfItems = numberOfItems; } //添加到二维数组 [layoutInfoArr addObject:[subArr copy]]; } //存储布局信息 self.layoutInfoArr = [layoutInfoArr copy]; //保存内容尺寸 self.contentSize = CGSizeMake(maxNumberOfItems*(self.itemSize.width+self.interitemSpacing)+self.interitemSpacing, numberOfSections*(self.itemSize.height+self.lineSpacing)+self.lineSpacing); }
代码解读
在上述代码中可以看到-webkit-CompoundKeyPattern这个属性,在 macOS 系统中被广泛用于实现快速布局功能。它通常包含如 frame 大小、布局类型等关键属性,并且能够根据不同的需求灵活配置布局模式。最终会将这些信息应用到对应的视图上以完成布局设置。因此,在上述代码中获取此属性的方法是通过调用-webkit-CompoundKeyPattern/layoutAttributesForItemAtIndexPath:方法实现的
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
//每一组cell为一行 attributes.frame = CGRectMake((self.itemSize.width+self.interitemSpacing)*indexPath.row+self.interitemSpacing, (self.itemSize.height+self.lineSpacing)*indexPath.section+self.lineSpacing, self.itemSize.width, self.itemSize.height); return attributes; }
代码解读
在本方法中, itemSize表示单元格的大小, interitemSpacing代表单元格之间的间隔, lineSpacing则是行间距。
随后,覆写collectionViewContentSize
在Objective-C代码中使用 storyboard 时, 有时候会遇到一些细节问题. 比如, 在使用 NSCollectionView 时, 它会自动为每一个子视图绑定一个 contentSize 属性. 这一点需要注意, 因为我们常常会错误地认为这个 contentSize 是指整个 NSCollectionView 的大小. 其实不然, 这个 size 是被分配给 NSCollectionView 的子视图的. 正是因为这一点, 如果我们要实现只能横向滑动的效果, 我们只需要将这个 size的高度 设置成 NSCollectionView 的高度就可以了. 因为这个方法会被频繁调用, 所以最好在 prepareLayout 阶段预先计算好. 因此, 在 BGGrideLayout 类中我们只需返回之前计算好的内容尺寸即可.
- (CGSize)collectionViewContentSize{
return self.contentSize;
}
代码解读
最后,覆写layoutAttributesForElementsInRect:方法
此方法将返回一个名为CellViewAndDecorationViewAndSupplementaryView对象,并用于表示在指定区域中应显示哪些视图以及这些视图的相关属性。由于此方法会被调用多次,在其执行过程中(即在此前),最好是在prepareLayout已经完成布局的信息基础上使用该对象以确保数据一致性。
在BGGrideLayout组件中, 我们会遍历二维数组以寻找那些与指定区域边界相交的UICollectionViewLayoutAttributes对象, 然后将这些符合条件的对象存储在一个临时数组中最后会将这些对象传递给父组件以供进一步处理
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray *layoutAttributesArr = [NSMutableArray array];
[self.layoutInfoArr enumerateObjectsUsingBlock:^(NSArray *array, NSUInteger i, BOOL * _Nonnull stop) { [array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *obj, NSUInteger idx, BOOL * _Nonnull stop) { if(CGRectIntersectsRect(obj.frame, rect)) { [layoutAttributesArr addObject:obj]; } }]; }]; return layoutAttributesArr; }
代码解读
