口袋优选

XLPlainFlowLayout.m 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. //
  2. // XLPlainFlowLayout.m
  3. // XLPlainFlowLayout
  4. //
  5. // Created by hebe on 15/7/30.
  6. // Copyright (c) 2015年 ___ZhangXiaoLiang___. All rights reserved.
  7. //
  8. // 工作邮箱E-mail: k52471@126.com
  9. #import "XLPlainFlowLayout.h"
  10. #import "XHCollectionViewLayoutAttributes.h"
  11. #import "XHCollectionReusableView.h"
  12. NSString *const XHCollectionViewSectionBackground = @"XHCollectionViewSectionBackground";
  13. @implementation XLPlainFlowLayout
  14. -(instancetype)init
  15. {
  16. self = [super init];
  17. if (self)
  18. {
  19. self.decorationViewAttrs = [NSMutableArray array];
  20. _naviHeight = 64.0;
  21. [self setup];
  22. }
  23. return self;
  24. }
  25. - (void)setup
  26. {
  27. [self registerClass:[XHCollectionReusableView class] forDecorationViewOfKind:XHCollectionViewSectionBackground];
  28. }
  29. - (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect
  30. {
  31. //UICollectionViewLayoutAttributes:我称它为collectionView中的item(包括cell和header、footer这些)的《结构信息》
  32. //截取到父类所返回的数组(里面放的是当前屏幕所能展示的item的结构信息),并转化成不可变数组
  33. NSMutableArray *superArray = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
  34. //创建存索引的数组,无符号(正整数),无序(不能通过下标取值),不可重复(重复的话会自动过滤)
  35. NSMutableIndexSet *noneHeaderSections = [NSMutableIndexSet indexSet];
  36. //遍历superArray,得到一个当前屏幕中所有的section数组
  37. for (UICollectionViewLayoutAttributes *attributes in superArray)
  38. {
  39. //如果当前的元素分类是一个cell,将cell所在的分区section加入数组,重复的话会自动过滤
  40. if (attributes.representedElementCategory == UICollectionElementCategoryCell)
  41. {
  42. [noneHeaderSections addIndex:attributes.indexPath.section];
  43. }
  44. }
  45. //遍历superArray,将当前屏幕中拥有的header的section从数组中移除,得到一个当前屏幕中没有header的section数组
  46. //正常情况下,随着手指往上移,header脱离屏幕会被系统回收而cell尚在,也会触发该方法
  47. for (UICollectionViewLayoutAttributes *attributes in superArray) {
  48. //如果当前的元素是一个header,将header所在的section从数组中移除
  49. if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
  50. [noneHeaderSections removeIndex:attributes.indexPath.section];
  51. }
  52. }
  53. //遍历当前屏幕中没有header的section数组
  54. [noneHeaderSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
  55. //取到当前section中第一个item的indexPath
  56. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
  57. //获取当前section在正常情况下已经离开屏幕的header结构信息
  58. UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
  59. //如果当前分区确实有因为离开屏幕而被系统回收的header
  60. if (attributes) {
  61. //将该header结构信息重新加入到superArray中去
  62. [superArray addObject:attributes];
  63. }
  64. }];
  65. //遍历superArray,改变header结构信息中的参数,使它可以在当前section还没完全离开屏幕的时候一直显示
  66. for (UICollectionViewLayoutAttributes *attributes in superArray) {
  67. //如果当前item是header
  68. if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
  69. //得到当前header所在分区的cell的数量
  70. NSInteger numberOfItemsInSection = [self.collectionView numberOfItemsInSection:attributes.indexPath.section];
  71. //得到第一个item的indexPath
  72. NSIndexPath *firstItemIndexPath = [NSIndexPath indexPathForItem:0 inSection:attributes.indexPath.section];
  73. //得到最后一个item的indexPath
  74. NSIndexPath *lastItemIndexPath = [NSIndexPath indexPathForItem:MAX(0, numberOfItemsInSection-1) inSection:attributes.indexPath.section];
  75. //得到第一个item和最后一个item的结构信息
  76. UICollectionViewLayoutAttributes *firstItemAttributes, *lastItemAttributes;
  77. if (numberOfItemsInSection>0) {
  78. //cell有值,则获取第一个cell和最后一个cell的结构信息
  79. firstItemAttributes = [self layoutAttributesForItemAtIndexPath:firstItemIndexPath];
  80. lastItemAttributes = [self layoutAttributesForItemAtIndexPath:lastItemIndexPath];
  81. }else{
  82. //cell没值,就新建一个UICollectionViewLayoutAttributes
  83. firstItemAttributes = [UICollectionViewLayoutAttributes new];
  84. //然后模拟出在当前分区中的唯一一个cell,cell在header的下面,高度为0,还与header隔着可能存在的sectionInset的top
  85. CGFloat y = CGRectGetMaxY(attributes.frame)+self.sectionInset.top;
  86. firstItemAttributes.frame = CGRectMake(0, y, 0, 0);
  87. //因为只有一个cell,所以最后一个cell等于第一个cell
  88. lastItemAttributes = firstItemAttributes;
  89. }
  90. //获取当前header的frame
  91. CGRect rect = attributes.frame;
  92. /**
  93. * 算法
  94. */
  95. //当前的滑动距离 + 因为导航栏产生的偏移量,默认为64(如果app需求不同,需自己设置)
  96. CGFloat offset = self.collectionView.contentOffset.y + _naviHeight;
  97. //第一个cell的y值 - 当前header的高度 - 可能存在的sectionInset的top
  98. CGFloat headerY = firstItemAttributes.frame.origin.y - rect.size.height - self.sectionInset.top;
  99. //哪个大取哪个,保证header悬停
  100. //针对当前header基本上都是offset更加大,针对下一个header则会是headerY大,各自处理
  101. CGFloat maxY = MAX(offset,headerY);
  102. //最后一个cell的y值 + 最后一个cell的高度 + 可能存在的sectionInset的bottom - 当前header的高度
  103. //当当前section的footer或者下一个section的header接触到当前header的底部,计算出的headerMissingY即为有效值
  104. CGFloat headerMissingY = CGRectGetMaxY(lastItemAttributes.frame) + self.sectionInset.bottom - rect.size.height;
  105. //给rect的y赋新值,因为在最后消失的临界点要跟谁消失,所以取小
  106. rect.origin.y = MIN(maxY,headerMissingY);
  107. //给header的结构信息的frame重新赋值
  108. attributes.frame = rect;
  109. //如果按照正常情况下,header离开屏幕被系统回收,而header的层次关系又与cell相等,如果不去理会,会出现cell在header上面的情况
  110. //通过打印可以知道cell的层次关系zIndex数值为0,我们可以将header的zIndex设置成1,如果不放心,也可以将它设置成非常大,这里随便填了个7
  111. attributes.zIndex = 7;
  112. }
  113. }
  114. for (UICollectionViewLayoutAttributes *attr in self.decorationViewAttrs) {
  115. if (CGRectIntersectsRect(rect, attr.frame)) {
  116. [superArray addObject:attr];
  117. }
  118. }
  119. //转换回不可变数组,并返回
  120. return [superArray copy];
  121. }
  122. //return YES;表示一旦滑动就实时调用上面这个layoutAttributesForElementsInRect:方法
  123. - (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
  124. return YES;
  125. }
  126. #pragma mark ===============
  127. - (void)prepareLayout
  128. {
  129. [super prepareLayout];
  130. [self.decorationViewAttrs removeAllObjects];
  131. NSInteger numberOfSections = [self.collectionView numberOfSections];
  132. id delegate = self.collectionView.delegate;
  133. if (!numberOfSections || ![delegate conformsToProtocol:@protocol(XHCollectionViewDelegateFlowLayout)]) {
  134. return;
  135. }
  136. for (NSInteger section = 0; section < numberOfSections; section++) {
  137. NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section];
  138. if (numberOfItems <= 0) {
  139. continue;
  140. }
  141. UICollectionViewLayoutAttributes *firstItem = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
  142. UICollectionViewLayoutAttributes *lastItem = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:numberOfItems - 1 inSection:section]];
  143. if (!firstItem || !lastItem) {
  144. continue;
  145. }
  146. UIEdgeInsets sectionInset = [self sectionInset];
  147. if ([delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) {
  148. UIEdgeInsets inset = [delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section];
  149. sectionInset = inset;
  150. }
  151. CGRect sectionFrame = CGRectUnion(firstItem.frame, lastItem.frame);
  152. sectionFrame.origin.x -= sectionInset.left;
  153. sectionFrame.origin.y -= sectionInset.top;
  154. if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
  155. sectionFrame.size.width += sectionInset.left + sectionInset.right;
  156. sectionFrame.size.height = self.collectionView.frame.size.height;
  157. } else {
  158. sectionFrame.size.width = self.collectionView.frame.size.width;
  159. sectionFrame.size.height += sectionInset.top + sectionInset.bottom;
  160. }
  161. // 2、定义
  162. XHCollectionViewLayoutAttributes *attr = [XHCollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:XHCollectionViewSectionBackground withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
  163. attr.frame = sectionFrame;
  164. attr.zIndex = -1;
  165. attr.backgroundColor = [delegate collectionView:self.collectionView layout:self backgroundColorForSection:section];
  166. [self.decorationViewAttrs addObject:attr];
  167. }
  168. }
  169. - (nullable UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath
  170. {
  171. if ([elementKind isEqualToString:XHCollectionViewSectionBackground]) {
  172. return [self.decorationViewAttrs objectAtIndex:indexPath.section];
  173. }
  174. return [super layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:indexPath];
  175. }
  176. @end