一.创建方法
1 创建
UICollectionView* cv = [UICollectionView alloc] initWithFrame:<#(CGRect)#>
collectionViewLayout:<#(nonnull UICollectionViewLayout *)#>
第二个参数应该传递一个UICollectionViewLayout类型的对象,用于设置UICollectionView的布局,通常传递的是UICollectionViewFlowLayout对象,它是UICollectionViewLayout的子类,常用的属性如下:
- layout.itemSize //每个cell的宽高
- layout.minimumInteritemSpacing //cell与cell横向之间的最小间距,与滚动方向有关
- layout.minimumLineSpacing //cell与cell纵向之间的最小间距,与滚动方向有关
- layout.scrollDirection //设置cell的滑动方向,默认是纵向滑动
- layout.headerReferenceSize; //设置section的headerView的宽高
- layout.footerReferenceSize; //设置section的footerView的宽高
- layout.sectionInset; //设置section的内边距,不包含section的headerView和footerView,这里需要特别注意,sectionInset算作collectionView内容的一部分,而collectionView的contentInset不算作内容
-
layout.sectionHeadersPinToVisibleBounds //设置section的headerView可悬浮,效果图如下,直到当前section结束headView才会滑动
image.png - layout.sectionFootersPinToVisibleBounds //设置footerView可悬浮
关于minimumInteritemSpacing属性作一点补充,它仅表示cell与cell横向的间距大于等于它,如果一定要使cell之间的间距等于该值需要先说明一下系统的布局方式:以滑动方向为纵向为例,系统会从collectionView的最左端开始放置cell, 先以该值计算是否能正好放下多个item并且最右端的cell距离collectionView的最右端没有空隙,如果不能则让两边的cell与sectionInset的左右对齐并平分cell之间的间距,因此要实现item左右的间距固定为minimumLineSpacing,如果所有cell等宽只需要计算并设置sectionInset的左右距离,如果cell不等宽则需要重写UICollectionViewFlowLayout的prepareLayout方法,见第四部分。
需要注意的是,以上的一些属性设置的是UICollectionView的全局属性(对所有cell或者section生效),如果要个性化设置样式可以使用以下的代理方法<UICollectionViewDelegateFlowLayout>:
1.设置indexPath位置的cell宽高
- (CGSize)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:
(NSIndexPath *)indexPath;
2.设置某一组的内边距
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:
(NSInteger)section;
3.设置某一组cell与cell横向之间的最小间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;
4.设置某一组cell与cell纵向之间的最小间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout
minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
5.设置某一组的headerView的宽高
- (CGSize)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout
referenceSizeForHeaderInSection:(NSInteger)section;
6.设置某一组的footerView的宽高
- (CGSize)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout
referenceSizeForFooterInSection:(NSInteger)section;
2 注册
为了方便复用,所有的cell,headerView,footerView都要在创建UICollectionView进行注册,并且注册使用的id和创建使用的id必须是相同的,注册方法如下:
// 注册cell
[self.collectionView registerClass:[XMClassificationContentsCell class]//cell类名
forCellWithReuseIdentifier:reuseIdentifier];
//注册headview
[self.collectionView registerClass:[XMCVHeaderView class] //headerView类名
forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:reuseIdentifier];
3.属性
UICollectionView和UITableView一样都是继承自UIScrollView,所以scrollToTop, scrollEnabled, showsVerticalScrollIndicator, contentInset, contentOffset, bounces等常用属性以及scrollView的代理方法依然有用。
二.数据源方法<UICollectionViewDataSource>
1.一组有多少个cell,必须实现
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section;
2. 创建cell并设置数据,必须实现
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView
*)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
3.一共有多少组数据
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
4.创建indexPath位置处的组头视图或组尾视图
//kind有两个选择UICollectionElementKindSectionHeader和
//UICollectionElementKindSectionFooter,分别表示头和尾
- (UICollectionReusableView *)collectionView:(UICollectionView
*)collectionView viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath;
5.设置哪些数据可以移动
- (BOOL)collectionView:(UICollectionView *)collectionView
canMoveItemAtIndexPath:(NSIndexPath *)indexPath
6.设置cell移动发生的操作
- (void)collectionView:(UICollectionView *)collectionView
moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:
(NSIndexPath*)destinationIndexPath API_AVAILABLE(ios(9.0));
7.设置headView,footView的尺寸
- (CGSize)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout
referenceSizeForHeaderInSection:(NSInteger)section;
8.创建headView和footView
- (UICollectionReusableView *)collectionView:(UICollectionView
*)collectionView viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath;
7和8两个代理方法后面继续深入学习。
三.代理方法<UICollectionViewDelegate>
首先,这几个代理方法与cell的点击事件有关
1.设置cell是否有点击高亮效果
- (BOOL)collectionView:(UICollectionView *)collectionView
shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
2.cell被手指按住不动时的状态
- (void)collectionView:(UICollectionView *)collectionView
didHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
3.手指拿开时cell的状态
- (void)collectionView:(UICollectionView *)collectionView
didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath;
4.设置cell是否可以被选中
- (BOOL)collectionView:(UICollectionView *)collectionView
shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
5.设置cell选中状态是否可以被取消,一般与第四次方法配合使用,在选中新的cell时改变之前选中的cell的某种属性或状态
- (BOOL)collectionView:(UICollectionView *)collectionView
shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath; // called when
the user taps on an already-selected item in multi-select mode
6.点击了某个cell时发生的事件
- (void)collectionView:(UICollectionView *)collectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
7.某个cell被取消选中时发生的属性改变或状态改变
- (void)collectionView:(UICollectionView *)collectionView
didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
对于2和3两个代理方法,实际效果类似于button的高亮效果。
对于6和7两个代理方法来说,从第二次点击开始,如果点击的cell与上次不同,则会先调用方法7,再调用方法6。但是当在方法6中调用reloadItemsAtIndexPaths
或者reloadData
这两个方法来刷新cell的数据时,方法7不会调用了,因为在调用reloadItemsAtIndexPaths
或者reloadData
,系统内部是会调用数据源方法cellForItemAtIndexPath
, 此时cell的selected属性又重新被初始化为NO了,所以此时cell的是处于非选中状态的,不会调用这个方法7来取消选中。
1.将要加载某个cell时调用的方法,可用来设置cell的背景颜色
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:
(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
2.将要加载头尾视图时调用的方法
- (void)collectionView:(UICollectionView *)collectionView
willDisplaySupplementaryView:(UICollectionReusableView *)view
forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
3.已经加载完成某个cell时调用的方法
- (void)collectionView:(UICollectionView *)collectionView
didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:
(NSIndexPath *)indexPath;
4.已经展示某个头尾视图时触发的方法
- (void)collectionView:(UICollectionView *)collectionView
didEndDisplayingSupplementaryView:(UICollectionReusableView *)view
forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath*)indexPath;
四.自定义UICollectionViewFlowLayout
如果要改变cell的布局方式,例如动态改变cell的大小,cell的摆放方式,设置弹簧效果等等,就需要自定义UICollectionViewFlowLayout并重写某些方法,常用的重写方法有以下几个:
//一般用来计算和保存每个cell的对应UICollectionViewLayoutAttributes对象,重写必须调用super方法,
//具体做法是:根据元素的数量,调用[self layoutAttributesForItemAtIndexPath:indexPath]方法获
//取和这个item对应的UICollectionViewLayoutAttributes对象并保存到特定的数组中,重写该方法一般是
//collectionView的item的位置需要与默认情形不同时,并且需要同时重写
//layoutAttributesForItemAtIndexPath方法
- (void)prepareLayout;
//计算collectionView的ContentSize
- (CGSize)collectionViewContentSize;
//指定一个区域,返回这个区域内item的尺寸,这个方法比较常用,
//它在collectionView的滑动过程中会频繁调用,如果item的某些属性例如size,
//旋转角度,透明度等等需要在滑动过程中动态变化,则需要重写此方法,使用步骤是:
//调用super的此方法获取rect区域内的UICollectionViewLayoutAttributes对象数组,
//随后遍历数组并根据当前滑动的位置来计算每个item的属性,最后返回修改之后的数组
- (NSArray<__kindof UICollectionViewLayoutAttributes *>
*)layoutAttributesForElementsInRect:(CGRect)rect;
//在滚动的时候是否允许刷新布局,如果有滑动动画效果应该设置为YES;
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
//返回的是collectionView的最终偏移量,需要注意它在手指一松开就会调用,
//但是返回的是collectionView的滑动速度减为0时的偏移
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;
下面列出两个自定义布局的案例:
1.设置所有cell逆时针旋转45度,改变cell自身的状态一般只需重写layoutAttributesForElementsInRect方法
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attr in attributes ) {
CGFloat degrees = M_PI * (-45.0f/180.0f);
attr.transform = CGAffineTransformMakeRotation(degrees);
}
return attributes;
}
显示效果如下:
image.png
2.流水布局
//这个方法会在滑动的过程中多次调用
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
//获取当前可见的cell
NSArray *attrs = [super layoutAttributesForElementsInRect:self.collectionView.bounds];
for (UICollectionViewLayoutAttributes *attr in attrs) {
//计算cell相对于collectionView的中心的距离
CGFloat delta = fabs((attr.center.x - self.collectionView.contentOffset.x) -
self.collectionView.bounds.size.width * 0.5);
//离中心越远,比例越小
CGFloat scale = 1 - delta / (self.collectionView.bounds.size.width * 0.5) *
0.25;
attr.transform = CGAffineTransformMakeScale(scale, scale);
}
return attrs;
}
//在滚动的时候是否允许刷新布局
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return YES;
}
//确定最终偏移量,在collectionView停止滚动时调用
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{
// 拖动比较快 最终偏移量 不等于 手指离开时偏移量
CGFloat collectionW = self.collectionView.bounds.size.width;
// 最终偏移量
CGPoint targetP = [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity];
// 0.获取最终显示的区域
CGRect targetRect = CGRectMake(targetP.x, 0, collectionW, MAXFLOAT);
// 1.获取最终显示的cell
NSArray *attrs = [super layoutAttributesForElementsInRect:targetRect];
// 获取最小间距
CGFloat minDelta = MAXFLOAT;
for (UICollectionViewLayoutAttributes *attr in attrs) {
// 获取距离中心点距离:注意:应该用最终的x
CGFloat delta = (attr.center.x - targetP.x) - self.collectionView.bounds.size.width * 0.5;
if (fabs(delta) < fabs(minDelta)) {
minDelta = delta;
}
}
// 移动间距
targetP.x += minDelta;
NSLog(@"%f",targetP.x);
if (targetP.x < 0) {
targetP.x = 0;
}
return targetP;
}
显示效果如下:
image.png
网友评论