之前写git商城的时候看到类似需求自定义的item排布,这两天有人又问到我具体怎么样自定义layout来实现,所以写了demo来分享下实现的过程。
类似APP实现截图
demo实现效果图
下面具体分析实现步骤
1.首先初始化懒加载一个CollectionView,注册Cell,Header,Footer~
#pragma mark - LazyLoad
- (UICollectionView *)collectionView
{
if (!_collectionView) {
DCItemSortLayout *dcLayout = [DCItemSortLayout new];
dcLayout.delegate = self;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:dcLayout];
_collectionView.frame = self.view.bounds;
_collectionView.delegate = self;
_collectionView.dataSource = self;
_collectionView.alwaysBounceVertical = YES;
[self.view addSubview:_collectionView];
[self.collectionView registerClass:[DCCollectionItemCell class] forCellWithReuseIdentifier:DCCollectionItemCellID]; //注册cell
[self.collectionView registerClass:[DCHeaderReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:DCHeaderReusableViewID]; //注册头部
[self.collectionView registerClass:[DCFooterReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:DCFooterReusableViewID]; //注册尾部
}
return _collectionView;
}
2.自定义DCItemSortLayout 在.h写两个代理方法,返回每组section头部和尾部的高度
@protocol DCItemSortLayoutDelegate <NSObject>
@optional;
/* 头部高度 */
-(CGFloat)dc_HeightOfSectionHeaderForIndexPath:(NSIndexPath *)indexPath;
/* 尾部高度 */
-(CGFloat)dc_HeightOfSectionFooterForIndexPath:(NSIndexPath *)indexPath;
@end
@interface DCItemSortLayout : UICollectionViewFlowLayout
@property (nonatomic, assign) id<DCItemSortLayoutDelegate>delegate;
3.在DCItemSortLayout .m文件中调用prepareLayout方法,声明一个高度和可变字典
@property (nonatomic, assign) CGFloat overallHeight; //整体高
@property (nonatomic, strong) NSMutableArray *attrsArr; //布局数组
/**
1.一个Cell对应一个UICollectionViewLayoutAttributes对象
2.UICollectionViewLayoutAttributes对象决定了cell的摆设位置(frame)
*/
#pragma mark - 初始化属性(section/item)
- (void)setUpAttributes
{
NSMutableArray *attributesArray = [NSMutableArray array];
NSInteger sectionCount = [self.collectionView numberOfSections];
for (int i = 0; i < sectionCount; i++) { //遍历
//sectionHeader
NSIndexPath *indexPath = [NSIndexPath indexPathWithIndex:i];
UICollectionViewLayoutAttributes *attrheader = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
[attributesArray addObject:attrheader];
//sectionItem
NSInteger itemCount = [self.collectionView numberOfItemsInSection:i];
for (int j = 0; j < itemCount; j++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[attributesArray addObject:attrs];
}
//sectionfooter
UICollectionViewLayoutAttributes *attrFooter = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:indexPath];
[attributesArray addObject:attrFooter];
}
self.attrsArr = [NSMutableArray arrayWithArray:attributesArray];
}
#pragma mark - 对应indexPath的位置的追加视图的布局属性
-(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *layoutAttrbutes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind withIndexPath:indexPath];
CGFloat height = 0;
if (elementKind == UICollectionElementKindSectionHeader) { //头部
if (_delegate != nil && [_delegate respondsToSelector:@selector(dc_HeightOfSectionHeaderForIndexPath:)]) {
height = [_delegate dc_HeightOfSectionHeaderForIndexPath:indexPath];
}
} else { //尾部
if (_delegate != nil && [_delegate respondsToSelector:@selector(dc_HeightOfSectionFooterForIndexPath:)]) {
height = [_delegate dc_HeightOfSectionFooterForIndexPath:indexPath];
}
}
layoutAttrbutes.frame = CGRectMake(0, self.overallHeight, ScreenW, height);
self.overallHeight += height;
return layoutAttrbutes;
}
#pragma mark - 返回rect中的所有的元素的布局属性
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
return self.attrsArr;
}
在我写的demo中我自定义了两组section
很明显的可以看出 上面这一组中有4个item,代码实现如下
#pragma mark - 自定义第一组section
- (void)layoutAttributesForCustomOneLayout:(UICollectionViewLayoutAttributes *)layoutAttributes indexPath: (NSIndexPath *) indexPath {
CGFloat itemY = self.overallHeight;
if (indexPath.item == 0) {
CGFloat itemH = 80;
layoutAttributes.frame = CGRectMake(0, itemY, ScreenW, itemH);
self.overallHeight += itemH;
} else {
NSInteger row = (indexPath.item -1) % 3;
CGFloat itemH = 100;
CGFloat itemW = ScreenW / 3;
layoutAttributes.frame = CGRectMake(row * itemW, itemY, itemW, itemH);
if (indexPath.item == 3 || indexPath.item == [self.collectionView numberOfItemsInSection:indexPath.section] - 1) {
self.overallHeight += itemH;
}
}
}
这一组中有3个item,代码实现如下
#pragma mark - 自定义第二组section
- (void)layoutAttributesForCustomTwolayout:(UICollectionViewLayoutAttributes *)layoutAttributes indexPath: (NSIndexPath *) indexPath {
CGFloat itemY = self.overallHeight;
CGFloat itemW = ScreenW / 2;
CGFloat itemH = 160;
switch (indexPath.item) {
case 0:
layoutAttributes.frame = CGRectMake(0, itemY, itemW, itemH);
break;
case 1:
layoutAttributes.frame = CGRectMake(itemW, itemY, itemW, itemH/2);
break;
case 2:
layoutAttributes.frame = CGRectMake(itemW, (itemH/2) + itemY, itemW, itemH / 2.0);
break;
default:
break;
}
if (indexPath.item == [self.collectionView numberOfItemsInSection:indexPath.section] - 1) {
self.overallHeight += itemH;
}
}
这样我们在自定义的UICollectionViewFlowLayout中已经完成了对两组Items的布局,接下来我们来到控制器界面~
数据源UICollectionViewDataSource
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 2;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return (section == 0) ? 4 : 3;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
DCCollectionItemCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:DCCollectionItemCellID forIndexPath:indexPath];
cell.backgroundColor = [self RandomColor];
return cell;
}else{
DCCollectionItemCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:DCCollectionItemCellID forIndexPath:indexPath];
cell.backgroundColor = [self RandomColor];
return cell;
}
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
if (kind == UICollectionElementKindSectionHeader) {
DCHeaderReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:DCHeaderReusableViewID forIndexPath:indexPath];
headerView.headLabel.text = [NSString stringWithFormat:@"第%zd组头部",indexPath.section];
return headerView;
} else if (kind == UICollectionElementKindSectionFooter) {
DCFooterReusableView *footerView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:DCFooterReusableViewID forIndexPath:indexPath];
footerView.backgroundColor = [UIColor lightGrayColor];
return footerView;
}
return [UICollectionReusableView new];
}
代理注意包括我们自定义的代理 DCItemSortLayoutDelegate,UICollectionViewDelegate
#pragma mark - UICollectionViewDelegate
#pragma mark - item点击
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"点击了第%zd组,第%zd个item",indexPath.section ,indexPath.row);
}
#pragma mark - DCItemSortLayoutDelegate
#pragma mark - 底部高度
-(CGFloat)dc_HeightOfSectionFooterForIndexPath:(NSIndexPath *)indexPath {
return 20;
}
#pragma mark - 头部高度
-(CGFloat)dc_HeightOfSectionHeaderForIndexPath:(NSIndexPath *)indexPath {
return 50;
}
- 这样自定义layout就完成了,之前也参考过几个git上几个哥们的布局对于自定义layout 的理解,很有收获非常感谢~
- 如上实现源码基本已全部展示,如果有需要我写的demo,可以给我留言私法~