目前APP中卡片布局方式非常常见,例如:淘宝、支付宝、百度地图等均有使用。
作为一名苦逼程序猿,不知大家写过了多少卡片布局。写的多了,自然就想如何封装一下,以方便之后更好的复用。考虑到复用,我们必须能够做到以下几点:
- 具有足够的灵活性,每个卡片内部一定是可定制的,so,我们的封装应该是不care内容的;
- 需要考虑性能,卡片一般会放在tableview中,因此在tableview滑动过程中,有部分卡片会出现、隐藏,考虑到性能问题,我们最好支持卡片每项的复用;
- 卡片的每行列数应该支持外部设置,同事根据卡片总数量调整整体View的布局。
基于以上几点,我考虑使用UICollectionView来封装。可能很多人面对这样的需求会考虑使用for循环,add每一个卡片,这样固然简单,但这样就没办法复用,如果整个页面多出出现这样的布局,对性能消耗也是不小的。话不多说,上代码:
#import <UIKit/UIKit.h>
@class GNColGridView;
//用collectionView实现
@protocol GNColGridViewDelegagte <NSObject>
@required
//每行的高度
- (CGFloat)heightForRowInGridView:(GNColGridView *)gridView;
//gridView列的数量
- (NSInteger)numberOfColumsInGridView:(GNColGridView *)gridView;
//gridView中item总量
- (NSInteger)totolNumberOfGridView:(GNColGridView *)gridView;
//返回每个item
- (GNButton *)gridView:(GNColGridView *)gridView gridAtIndex:(NSInteger)index;
@optional
//item点击事件
- (void)gridView:(GNColGridView *)gridView onItemClick:(NSInteger)index;
@end
@interface GNColGridView : UIView
@property (nonatomic, weak) id<GNColGridViewDelegagte> delegate;
- (void)reLoadData;
@end
上面是对外暴露的接口,接口封装还是比较清晰的。【注:GNButton是我Demo工程中对UIButton的封装,读者把他当成UIButton就行】
下面是具体的实现:
#import "GNColGridView.h"
#define kGNCOLGRIDVIEWID @"GNCOLGRIDVIEWID"
@protocol GNColGridViewLayoutDelegate <NSObject>
- (NSInteger)numberOfColums;
- (NSInteger)totolNumber;
- (CGFloat)height;
@end
@interface GNColGridViewLayout : UICollectionViewFlowLayout
@property (nonatomic, weak) id<GNColGridViewLayoutDelegate> delegate;
@property (nonatomic, assign) NSInteger col;
@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, assign) CGFloat height; //每行高度
@end
@implementation GNColGridViewLayout
- (NSInteger)col {
if (_delegate && [_delegate respondsToSelector:@selector(numberOfColums)]) {
return [_delegate numberOfColums];
}
return 0;
}
- (NSInteger)sum {
if (_delegate && [_delegate respondsToSelector:@selector(totolNumber)]) {
return [_delegate totolNumber];
}
return 0;
}
- (CGFloat)height {
if (_delegate && [_delegate respondsToSelector:@selector(height)]) {
return [_delegate height];
}
return 0;
}
- (CGSize)collectionViewContentSize {
if (self.col <= 0) {
return CGSizeZero;
}
NSInteger row = self.sum / self.col + ((self.sum % self.col) > 0 ? 1: 0);
CGFloat width = SCREEN_WIDTH;
CGFloat height = self.height * row;
return CGSizeMake(width, height);
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
- (void)prepareLayout
{
[super prepareLayout];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath];
if (self.col <= 0) {
return attributes;
}
CGFloat itemWidth = SCREEN_WIDTH / self.col;
attributes.center = CGPointMake((indexPath.row % self.col + 0.5) * itemWidth, self.height * (indexPath.row / self.col + 0.5));
attributes.size = CGSizeMake(itemWidth, self.height);
attributes.indexPath = indexPath;
return attributes;
}
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *array = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:5];
for (int i = 0; i < [array count]; i ++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
}
return attributes;
}
@end
@interface GNColGridView () <UICollectionViewDataSource, UICollectionViewDelegate, GNColGridViewLayoutDelegate>
@property (nonatomic, strong) UICollectionView *collectionView;
@end
@implementation GNColGridView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initUI];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
}
- (void)initUI {
GNColGridViewLayout *layout = [[GNColGridViewLayout alloc] init];
[layout setDelegate:self];
self.collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:layout];
_collectionView.dataSource = self;
_collectionView.delegate = self;
[_collectionView setBackgroundColor:[UIColor clearColor]];
[_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:kGNCOLGRIDVIEWID];
[self addSubview:_collectionView];
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
NSInteger sum = 0;
if (_delegate && [_delegate respondsToSelector:@selector(totolNumberOfGridView:)]) {
sum = [_delegate totolNumberOfGridView:self];
}
return sum;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kGNCOLGRIDVIEWID forIndexPath:indexPath];
if (_delegate && [_delegate respondsToSelector:@selector(gridView:gridAtIndex:)]) {
for (UIView *subView in cell.contentView.subviews) {
if (subView.subviews) {
[subView removeFromSuperview];
}
}
GNButton *view = [GNButton new];
view = [_delegate gridView:self gridAtIndex:indexPath.row];
view.tag = indexPath.row;
[view addTarget:self action:@selector(onItemClick:) forControlEvents:UIControlEventTouchUpInside];
view.centerX = cell.width / 2;
view.centerY = cell.height / 2;
[cell.contentView addSubview:view];
}
return cell;
}
#pragma mark - GNColGridViewLayoutDelegate
- (NSInteger)numberOfColums {
if (_delegate && [_delegate respondsToSelector:@selector(numberOfColumsInGridView:)]) {
return [_delegate numberOfColumsInGridView:self];
}
return 0;
}
- (NSInteger)totolNumber {
if (_delegate && [_delegate respondsToSelector:@selector(totolNumberOfGridView:)]) {
return [_delegate totolNumberOfGridView:self];
}
return 0;
}
- (CGFloat)height {
if (_delegate && [_delegate respondsToSelector:@selector(heightForRowInGridView:)]) {
return [_delegate heightForRowInGridView:self];
}
return 0.0;
}
#pragma mark - action
- (void)onItemClick:(GNButton*) button{
if (_delegate && [_delegate respondsToSelector:@selector(gridView:onItemClick:)]) {
[_delegate gridView:self onItemClick:button.tag];
}
}
#pragma mark - public
- (void)reLoadData {
NSInteger row = [self totolNumber] / [self numberOfColums] + (([self totolNumber] % [self numberOfColums]) > 0 ? 1: 0);
CGFloat width = SCREEN_WIDTH;
CGFloat height = self.height * row;
_collectionView.height = height;
[_collectionView reloadData];
}
@end