采用流式布局的简单集合视图
在这里制作了简单的集合视图控制器,并且使开发者可以指定它的头部和尾部。这里写了关键的数据源方法与委托方法,以便实现简单的网格状流式布局。苹果公司提供了很多属性,开发者可以通过与集合视图有关的一些委托方法,以及UICollectionViewDelegateFlowLayout协议中的委托方法来提供与这些属性相对应的值。并且稍微调整一下源代码,修改视图中的区段数量、每个区段内的条目数量,以及其他影响总体板式的布局细节.
@interface MyCollectionViewController : UICollectionViewController
@property (nonatomic,assign) BOOL useHeaders;
@property (nonatomic,assign) BOOL useFooters;
@property (nonatomic,assign) NSInteger numberOfSections;
@property (nonatomic,assign) NSInteger itemsInSection;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = NO;
// Register cell classes
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];
[self.collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header"];
[self.collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"footer"];
self.collectionView.backgroundColor = [UIColor lightGrayColor];
self.collectionView.allowsMultipleSelection = YES;
// Do any additional setup after loading the view.
}
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout
{
self = [super initWithCollectionViewLayout:layout];
if (self) {
self.useFooters = NO;
self.useHeaders = NO;
self.numberOfSections = 1;
self.itemsInSection = 1;
}
return self;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
{
return self.useFooters ? CGSizeMake(60, 30) : CGSizeZero;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
return self.useHeaders ? CGSizeMake(60, 30) : CGSizeZero;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return self.numberOfSections;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.itemsInSection;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor whiteColor];
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:CGRectZero];
cell.selectedBackgroundView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
return cell;
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
if (kind == UICollectionElementKindSectionHeader) {
UICollectionReusableView *header = [self.collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header" forIndexPath:indexPath];
header.backgroundColor = [UIColor blackColor];
return header;
}
else if(kind == UICollectionElementKindSectionFooter){
UICollectionReusableView *footer = [self.collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"footer" forIndexPath:indexPath];
footer.backgroundColor = [UIColor darkGrayColor];
return footer;
}
return nil;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Selected %@",indexPath);
}
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Deselected %@",indexPath);
}
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
MyCollectionViewController *mcc = [[MyCollectionViewController alloc] initWithCollectionViewLayout:layout];
mcc.edgesForExtendedLayout = UIRectEdgeNone;
layout.itemSize = CGSizeMake(50, 50);
layout.sectionInset = UIEdgeInsetsMake(10, 10, 50, 10);
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.minimumLineSpacing = 10;
layout.minimumInteritemSpacing = 10;
mcc.numberOfSections = 10;
mcc.itemsInSection = 12;
mcc.useHeaders = YES;
mcc.useFooters = YES;
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:mcc];
_window.rootViewController = nav;
[_window makeKeyAndVisible];
代码中的两个布尔属性分别用来决定集合视图是否使用头部及尾部。如果使用的话,可以通过headerReferenceSize及footerReferenceSize来设置头部及尾部。UICollectionViewDelegateFlowLayout协议的委托方法如果把0作为头部或者尾部的尺寸返回给调用者,就表明集合视图的相关区段不需要使用头部或尾部。若是返回其他尺寸,那么集合视图还会继续询问将要用作头部或尾部的补充视图时什么。
在数据源中使用单元格或补充视图之前,一定要先把它们注册号。注册好之后,就可以根据需要从队列里面取出这些实例了。开发者无需检查取出的实例是否可用,因为负责从队列中取出实例的方法会在必要时自行创建并初始化相关实例。
创建交互式的布局效果
流式布局是完全可控的。我们可以从UICollectionViewFlowLayout中继承子类,以便实时地控制每个条目的尺寸及其在屏幕上的摆放地点。而这对于开发者来说。是个相当强大的功能,它使得我们可以非常精确地指定每个条目的位置,从而模拟出三维布局效果,并使我们能够突破线性模式,把行列形式转换成圆圈、堆叠、贝塞尔曲线等形式。
可以定制的布局属性包括标准的布局元素(frame、center、size)、透明度(alpha和hidden)、z轴位置(zIndex),以及坐标变换方式(transform3D)。
这个流式布局会根据条目距离屏幕中心的远近来缩放其尺寸,距离屏幕中心越近,尺寸越大,距离屏幕中心越远,尺寸越小。它会计算每个条目距离屏幕的水平中心有多远,然后根据余弦函数来决定缩放倍数。
@interface PuncheLayout : UICollectionViewFlowLayout
@implementation PuncheLayout
{
CGSize boundsSize;
CGFloat midX;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
- (void)prepareLayout
{
[super prepareLayout];
boundsSize = self.collectionView.bounds.size;
midX = boundsSize.width / 2;
}
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *array = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attributes in array) {
attributes.transform3D = CATransform3DIdentity;
if (!CGRectIntersectsRect(attributes.frame, rect)) {
continue;
}
CGPoint contentOffset = self.collectionView.contentOffset;
CGPoint itemCenter = CGPointMake(attributes.center.x - contentOffset.x, attributes.center.y - contentOffset.y);
CGFloat distance = ABS(midX - itemCenter.x);
CGFloat normalized = distance / midX;
normalized = MIN(1, normalized);
CGFloat zoom = cos(normalized *M_PI_4);
attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1);
}
return array;
}
@end
这个流式布局会根据条目距离屏幕中心的远近来缩放其尺寸,距离屏幕中心越近,尺寸越大,距离屏幕中心越远,尺寸越小。它会计算每个条目距离屏幕的水平中心有多远,然后根据余弦函数来决定缩放倍数。
滚动之后自动调整位置
我们不妨把屏幕中心的物件自动调整到最合适的位置上。我们可以实现一个用于布局的方法,把集合视图的内容调整到特定的边界处。
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
CGFloat offsetAdjustment = CGFLOAT_MAX;
CGRect targetRect = CGRectMake(proposedContentOffset.x, 0, boundsSize.width, boundsSize.height);
NSArray *array = [super layoutAttributesForElementsInRect:targetRect];
CGFloat proppsedCenterX = proposedContentOffset.x + midX;
for (UICollectionViewLayoutAttributes *layoutAttributes in array) {
CGFloat distance = layoutAttributes.center.x - proppsedCenterX;
if (ABS(distance) < ABS(offsetAdjustment)) {
offsetAdjustment = distance;
}
}
CGPoint desiredPoint = CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
if (!(proposedContentOffset.x == 0) || (proposedContentOffset.x >= (self.collectionViewContentSize.width - boundsSize.width))) {
NSNotification *note = [NSNotification notificationWithName:@"PleaseRecenter" object:[NSValue valueWithCGPoint:desiredPoint]];
[[NSNotificationCenter defaultCenter] postNotification:note];
return proposedContentOffset;
}
return desiredPoint;
}
用户滚动集合视图时,系统会调用名为targetContentOffsetForProposedContentOffset:的方法,该方法描述了在不加人工干预的前提下集合视图会滚动到何处。覆写该方法的时候,我们遍历屏幕上的所有物件,找到距离视图水平中心最近的那个,然后调整该方法所要返回的偏移量,令该物件的中心与视图的中心相互重合。