UICollectionView 04 - 仿UITableView 懸浮Header
一,概述
基于單section設計布局下實現的UICollectionView Header懸浮功能,多section的思路類似。
二,思路
在UITableView中,Header懸浮功能只需設置TableView的style即可實現,但在UICollectionView中由于布局分離出來了,需要在布局中自己計算實現。使用UICollectionView的supplementaryView來作為HeaderView,在方法
//計算rect內的元素的UICollectionViewLayoutAttributes - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
中添加上計算SupplementaryView的UICollectionViewLayoutAttributes的邏輯,即可實現此功能。
三,修改自定義的布局
在自定義的布局類中需要處理的方法有3個:
- 在上面的第一個方法中,除了計算item的UICollectionViewLayoutAttributes外,加上計算SupplementaryView的UICollectionViewLayoutAttributes的邏輯。
/// 當CollectionView開始刷新后,會調用此方法并傳遞rect參數(即當前可視區域) /// @param rect 我們需要利用rect參數判斷出在當前可視區域中有哪幾個indexPath會被顯示(無視rect而全部計算將會帶來不好的性能) /// 最后計算相關indexPath的layoutAttributes,加入數組中并返回 - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *attributesArray = self.attributesArray; NSArray<NSIndexPath *> *indexPaths; //1、計算rect中出現的items indexPaths = [self p_indexPathForItemsInRect:rect]; for(NSIndexPath *indexPath in indexPaths){ //計算對應的LayoutAttributes UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; [attributesArray addObject:attributes]; } //2、計算rect中出現的SupplementaryViews //這里只計算了kSupplementaryViewKindHeader indexPaths = [self p_indexPathForSupplementaryViewsOfKind:kSupplementaryViewKindHeader InRect:rect]; for(NSIndexPath *indexPath in indexPaths){ //計算對應的LayoutAttributes UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:kSupplementaryViewKindHeader atIndexPath:indexPath]; [attributesArray addObject:attributes]; } return attributesArray; } ///計算目標rect中含有的某類SupplementaryView - (NSMutableArray<NSIndexPath *> *)p_indexPathForSupplementaryViewsOfKind:(NSString *)kind InRect:(CGRect)rect { NSMutableArray<NSIndexPath *> *indexPaths = [NSMutableArray array]; if([kind isEqualToString:kSupplementaryViewKindHeader]){ //在這個瀑布流自定義布局中,只有一個位于列表頂部的SupplementaryView NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; //如果當前區域可以看到SupplementaryView,則返回 //CGFloat height = [self.delegate collectionViewLayout:self heightForSupplementaryViewAtIndexPath:indexPath]; //if(CGRectGetMinY(rect) <= height + _insets.top){ //Header默認總是需要顯示 [indexPaths addObject:indexPath]; //} } return indexPaths; }
用kSupplementaryViewKindHeader來分辨不同的SupplementaryView。indexPathForSupplementaryViewsOfKind: InRect:方法用于計算在rect內需要展示那些指定kind的SupplementaryView。
-
重寫父類的 -layoutAttributesForSupplementaryViewOfKind:atIndexPath:方法,計算指定kind和indexPath上的SupplementaryView的LayoutAttributes。
///MARK:根據kind、indexPath,計算對應的LayoutAttributes - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind withIndexPath:indexPath]; //計算LayoutAttributes if([elementKind isEqualToString:kSupplementaryViewKindHeader]){ CGFloat w = self.collectionView.bounds.size.width; CGFloat h = [self.delegate lx_collectionViewLayout:self heightForSupplementaryViewAtIndexPath:indexPath]; CGFloat x = 0; //根據offset計算kSupplementaryViewKindHeader的y //y = offset.y-(header高度-固定高度) CGFloat offsetY = self.collectionView.contentOffset.y; CGFloat y = MAX(0,offsetY-(h-kSupplementaryViewKindHeaderPinnedHeight)); attributes.frame = CGRectMake(x, y, w, h); attributes.zIndex = 1024; } return attributes; }
為了顯示HeaderView并讓其懸浮在列表頂部,需要知道它的高度,所以添加代理方法collectionViewLayout:heightForSupplementaryViewAtIndexPath:來獲取高度,同時設置zIndex讓其保持在所有視圖的最前方。在這個方法中,主要計算HeaderView在CollectionView中的y坐標,保證在CollectionView的滾動過程中,HeaderView始終在CollectionView的頂部露出固定高度。
![]()
-
InvalidateLayout 最后需要覆蓋父類的shouldInvalidateLayoutForBoundsChange方法,讓其返回YES,使UICollectionView在滑動時不斷刷新Layout。
///每當offset改變時,是否需要重新布局,newBounds為offset改變后的rect ///瀑布流中不需要,因為滑動時,cell的布局不會隨offset而改變 ///如果需要實現懸浮Header,需要改為YES - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { //return [super shouldInvalidateLayoutForBoundsChange:newBounds]; return YES; }
四,最后
最后創建我們的自定義headerView,在collectionView初始化的時候注冊,并在UICollectionViewDataSource的代理方法中創建相應的HeaderView就大功告成啦。

浙公網安備 33010602011771號