123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- //
- // XLPlainFlowLayout.m
- // XLPlainFlowLayout
- //
- // Created by hebe on 15/7/30.
- // Copyright (c) 2015年 ___ZhangXiaoLiang___. All rights reserved.
- //
- // 工作邮箱E-mail: k52471@126.com
- #import "XLPlainFlowLayout.h"
- #import "XHCollectionViewLayoutAttributes.h"
- #import "XHCollectionReusableView.h"
- NSString *const XHCollectionViewSectionBackground = @"XHCollectionViewSectionBackground";
- @implementation XLPlainFlowLayout
- -(instancetype)init
- {
- self = [super init];
- if (self)
- {
- self.decorationViewAttrs = [NSMutableArray array];
- _naviHeight = 64.0;
- [self setup];
- }
- return self;
- }
- - (void)setup
- {
- [self registerClass:[XHCollectionReusableView class] forDecorationViewOfKind:XHCollectionViewSectionBackground];
- }
- - (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect
- {
- //UICollectionViewLayoutAttributes:我称它为collectionView中的item(包括cell和header、footer这些)的《结构信息》
- //截取到父类所返回的数组(里面放的是当前屏幕所能展示的item的结构信息),并转化成不可变数组
- NSMutableArray *superArray = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
-
- //创建存索引的数组,无符号(正整数),无序(不能通过下标取值),不可重复(重复的话会自动过滤)
- NSMutableIndexSet *noneHeaderSections = [NSMutableIndexSet indexSet];
- //遍历superArray,得到一个当前屏幕中所有的section数组
- for (UICollectionViewLayoutAttributes *attributes in superArray)
- {
- //如果当前的元素分类是一个cell,将cell所在的分区section加入数组,重复的话会自动过滤
- if (attributes.representedElementCategory == UICollectionElementCategoryCell)
- {
- [noneHeaderSections addIndex:attributes.indexPath.section];
- }
- }
-
- //遍历superArray,将当前屏幕中拥有的header的section从数组中移除,得到一个当前屏幕中没有header的section数组
- //正常情况下,随着手指往上移,header脱离屏幕会被系统回收而cell尚在,也会触发该方法
- for (UICollectionViewLayoutAttributes *attributes in superArray) {
- //如果当前的元素是一个header,将header所在的section从数组中移除
- if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
- [noneHeaderSections removeIndex:attributes.indexPath.section];
- }
- }
-
- //遍历当前屏幕中没有header的section数组
- [noneHeaderSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
-
- //取到当前section中第一个item的indexPath
- NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
- //获取当前section在正常情况下已经离开屏幕的header结构信息
- UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
-
- //如果当前分区确实有因为离开屏幕而被系统回收的header
- if (attributes) {
- //将该header结构信息重新加入到superArray中去
- [superArray addObject:attributes];
- }
- }];
-
- //遍历superArray,改变header结构信息中的参数,使它可以在当前section还没完全离开屏幕的时候一直显示
- for (UICollectionViewLayoutAttributes *attributes in superArray) {
-
- //如果当前item是header
- if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
-
- //得到当前header所在分区的cell的数量
- NSInteger numberOfItemsInSection = [self.collectionView numberOfItemsInSection:attributes.indexPath.section];
- //得到第一个item的indexPath
- NSIndexPath *firstItemIndexPath = [NSIndexPath indexPathForItem:0 inSection:attributes.indexPath.section];
- //得到最后一个item的indexPath
- NSIndexPath *lastItemIndexPath = [NSIndexPath indexPathForItem:MAX(0, numberOfItemsInSection-1) inSection:attributes.indexPath.section];
- //得到第一个item和最后一个item的结构信息
- UICollectionViewLayoutAttributes *firstItemAttributes, *lastItemAttributes;
- if (numberOfItemsInSection>0) {
- //cell有值,则获取第一个cell和最后一个cell的结构信息
- firstItemAttributes = [self layoutAttributesForItemAtIndexPath:firstItemIndexPath];
- lastItemAttributes = [self layoutAttributesForItemAtIndexPath:lastItemIndexPath];
- }else{
- //cell没值,就新建一个UICollectionViewLayoutAttributes
- firstItemAttributes = [UICollectionViewLayoutAttributes new];
- //然后模拟出在当前分区中的唯一一个cell,cell在header的下面,高度为0,还与header隔着可能存在的sectionInset的top
- CGFloat y = CGRectGetMaxY(attributes.frame)+self.sectionInset.top;
- firstItemAttributes.frame = CGRectMake(0, y, 0, 0);
- //因为只有一个cell,所以最后一个cell等于第一个cell
- lastItemAttributes = firstItemAttributes;
- }
-
- //获取当前header的frame
- CGRect rect = attributes.frame;
-
- /**
- * 算法
- */
- //当前的滑动距离 + 因为导航栏产生的偏移量,默认为64(如果app需求不同,需自己设置)
- CGFloat offset = self.collectionView.contentOffset.y + _naviHeight;
- //第一个cell的y值 - 当前header的高度 - 可能存在的sectionInset的top
- CGFloat headerY = firstItemAttributes.frame.origin.y - rect.size.height - self.sectionInset.top;
-
- //哪个大取哪个,保证header悬停
- //针对当前header基本上都是offset更加大,针对下一个header则会是headerY大,各自处理
- CGFloat maxY = MAX(offset,headerY);
-
- //最后一个cell的y值 + 最后一个cell的高度 + 可能存在的sectionInset的bottom - 当前header的高度
- //当当前section的footer或者下一个section的header接触到当前header的底部,计算出的headerMissingY即为有效值
- CGFloat headerMissingY = CGRectGetMaxY(lastItemAttributes.frame) + self.sectionInset.bottom - rect.size.height;
-
- //给rect的y赋新值,因为在最后消失的临界点要跟谁消失,所以取小
- rect.origin.y = MIN(maxY,headerMissingY);
- //给header的结构信息的frame重新赋值
- attributes.frame = rect;
-
- //如果按照正常情况下,header离开屏幕被系统回收,而header的层次关系又与cell相等,如果不去理会,会出现cell在header上面的情况
- //通过打印可以知道cell的层次关系zIndex数值为0,我们可以将header的zIndex设置成1,如果不放心,也可以将它设置成非常大,这里随便填了个7
- attributes.zIndex = 7;
- }
- }
-
- for (UICollectionViewLayoutAttributes *attr in self.decorationViewAttrs) {
-
- if (CGRectIntersectsRect(rect, attr.frame)) {
- [superArray addObject:attr];
- }
- }
-
- //转换回不可变数组,并返回
- return [superArray copy];
-
- }
- //return YES;表示一旦滑动就实时调用上面这个layoutAttributesForElementsInRect:方法
- - (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
- return YES;
- }
- #pragma mark ===============
- - (void)prepareLayout
- {
- [super prepareLayout];
- [self.decorationViewAttrs removeAllObjects];
-
- NSInteger numberOfSections = [self.collectionView numberOfSections];
- id delegate = self.collectionView.delegate;
- if (!numberOfSections || ![delegate conformsToProtocol:@protocol(XHCollectionViewDelegateFlowLayout)]) {
- return;
- }
-
- for (NSInteger section = 0; section < numberOfSections; section++) {
- NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section];
- if (numberOfItems <= 0) {
- continue;
- }
- UICollectionViewLayoutAttributes *firstItem = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
- UICollectionViewLayoutAttributes *lastItem = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:numberOfItems - 1 inSection:section]];
- if (!firstItem || !lastItem) {
- continue;
- }
-
- UIEdgeInsets sectionInset = [self sectionInset];
-
- if ([delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) {
- UIEdgeInsets inset = [delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section];
- sectionInset = inset;
- }
-
-
- CGRect sectionFrame = CGRectUnion(firstItem.frame, lastItem.frame);
- sectionFrame.origin.x -= sectionInset.left;
- sectionFrame.origin.y -= sectionInset.top;
-
- if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
- sectionFrame.size.width += sectionInset.left + sectionInset.right;
- sectionFrame.size.height = self.collectionView.frame.size.height;
- } else {
- sectionFrame.size.width = self.collectionView.frame.size.width;
- sectionFrame.size.height += sectionInset.top + sectionInset.bottom;
- }
-
- // 2、定义
- XHCollectionViewLayoutAttributes *attr = [XHCollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:XHCollectionViewSectionBackground withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
- attr.frame = sectionFrame;
- attr.zIndex = -1;
-
- attr.backgroundColor = [delegate collectionView:self.collectionView layout:self backgroundColorForSection:section];
- [self.decorationViewAttrs addObject:attr];
- }
-
- }
- - (nullable UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath
- {
- if ([elementKind isEqualToString:XHCollectionViewSectionBackground]) {
- return [self.decorationViewAttrs objectAtIndex:indexPath.section];
- }
- return [super layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:indexPath];
- }
- @end
|