UICollectionView的使用学习

作者: 我家冰箱养企鹅 | 来源:发表于2021-09-06 14:20 被阅读0次

一.创建方法

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

相关文章

网友评论

    本文标题:UICollectionView的使用学习

    本文链接:https://www.haomeiwen.com/subject/bbahwltx.html