我们平时经常见到一些很酷的滚动效果,卡片式的堆叠效果、瀑布流、单元格移动等,这些炫酷的效果我们可以通过collectionview自定义布局来实现。
继承UICollectionViewLayout
我们自定义layout,需要继承UICollectionViewLayout,有一些核心方法需要我们来实现,这些核心方法可以帮助我们来完成布局任务。
prepareLayout
方法,为布局计算做一些准备工作;collectionViewContentSize
方法,返回内容区域的size;layoutAttributesForElementsInRect:
方法,返回矩形区域内cells或者views的属性;
说明图片:
示例代码
创建自定义布局类
collectionViewLayout.h 文件
interface collectionViewLayout : UICollectionViewLayout
property (nonatomic) CGSize itemSize;
@property (nonatomic) NSInteger visibleCount;
@property (nonatomic) UICollectionViewScrollDirection scrollDirection;
@end
collectionViewLayout.m 文件
#import "collectionViewLayout.h"
#define INTERSPACEPARAM 0.65
@interface collectionViewLayout ()
{
CGFloat _viewHeight;
CGFloat _itemHeight;
}
@end
//为自定义布局做准备工作,确定滚动区域大小
-(void)prepareLayout {
[super prepareLayout];
if (self.visibleCount < 1) {
self.visibleCount = 5;
}
_viewHeight = CGRectGetWidth(self.collectionView.frame);
_itemHeight = self.itemSize.width;
self.collectionView.contentInset = UIEdgeInsetsMake(0, (_viewHeight - _itemHeight) / 2, 0, (_viewHeight - _itemHeight) / 2);
self.collectionView.showsHorizontalScrollIndicator = NO;
self.collectionView.showsVerticalScrollIndicator = NO;
}
//处理现实区域布局属性
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];
CGFloat centerY = self.collectionView.contentOffset.x + _viewHeight / 2;
NSInteger index = centerY / _itemHeight;
NSInteger count = (self.visibleCount - 1) / 2;
NSInteger minIndex = MAX(0, (index - count));
NSInteger maxIndex = MIN((cellCount - 1), (index + count));
NSMutableArray *array = [NSMutableArray array];
for (NSInteger i = minIndex; i <= maxIndex; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[array addObject:attributes];
}
return array;
}
//此方法可以立即获取某一布局属性
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.size = self.itemSize;
CGFloat cY = self.collectionView.contentOffset.x + _viewHeight / 2;
CGFloat attributesY = _itemHeight * indexPath.row + _itemHeight / 2;
attributes.zIndex = -ABS(attributesY - cY);
CGFloat delta = cY - attributesY;
CGFloat ratio = - delta / (_itemHeight * 2);
CGFloat scale = 1 - ABS(delta) / (_itemHeight * 6.0) * cos(ratio * M_PI_4);
attributes.transform = CGAffineTransformMakeScale(scale, scale);
CGFloat centerY = attributesY;
centerY = cY + sin(ratio * M_PI_2) * _itemHeight * INTERSPACEPARAM;
attributes.center = CGPointMake(centerY, CGRectGetHeight(self.collectionView.frame) / 2);
return attributes;
}
//这个方法的返回值,就决定了collectionView停止滚动时的偏移量
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
CGFloat index = roundf((proposedContentOffset.x + _viewHeight / 2 - _itemHeight / 2) / _itemHeight);
proposedContentOffset.x = _itemHeight * index + _itemHeight / 2 - _viewHeight / 2;
return proposedContentOffset;
}
//这个方法的返回值,决定了当collectionview的显示范围发生改变的时候,是否需要刷新布局
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return !CGRectEqualToRect(newBounds, self.collectionView.bounds);
}
使用自定义布局
ViewController.m
#import "CollectionCell.h"
#import "collectionViewLayout.h"
@interface ViewController ()<UICollectionViewDataSource,UICollectionViewDelegate>
@property (strong, nonatomic) IBOutlet UICollectionView *collectionView;
@property (strong, nonatomic) NSArray * imageArray;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_imageArray = @[@"p4.jpg",@"p0.jpg",@"p1.jpg",@"p2.jpg",@"p3.jpg",@"p4.jpg",@"p0.jpg",@"p1.jpg"];
collectionViewLayout * layout = [[collectionViewLayout alloc] init];
layout.itemSize = CGSizeMake(250, 160);
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionView.frame = CGRectMake(0, 100, self.view.frame.size.width, layout.itemSize.height);
_collectionView.backgroundColor = [UIColor grayColor];
[self.collectionView setCollectionViewLayout:layout animated:YES];
_collectionView.delegate = self;
_collectionView.dataSource = self;
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//使collectionview默认滑动到第二张图片位置
_collectionView.contentOffset = CGPointMake(250, 0);
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return _imageArray.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CollectionCell" forIndexPath:indexPath];
cell.imageView.image = [UIImage imageNamed:_imageArray[indexPath.row]];
return cell;
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSIndexPath *curIndexPath = [self curIndexPath];
if (indexPath.row == curIndexPath.row) {
return YES;
}
NSLog(@"current row %ld",(long)indexPath.row);
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionNone animated:YES];
return NO;
}
//获取当前点击的IndexPath
- (NSIndexPath *)curIndexPath {
NSArray *indexPaths = [self.collectionView indexPathsForVisibleItems];
NSIndexPath *curIndexPath = nil;
NSInteger curzIndex = 0;
for (NSIndexPath *path in indexPaths.objectEnumerator) {
UICollectionViewLayoutAttributes *attributes = [self.collectionView layoutAttributesForItemAtIndexPath:path];
if (!curIndexPath) {
curIndexPath = path;
curzIndex = attributes.zIndex;
continue;
}
if (attributes.zIndex > curzIndex) {
curIndexPath = path;
curzIndex = attributes.zIndex;
}
}
return curIndexPath;
}
//实现无限循环滚动
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x<=250) {
scrollView.contentOffset = CGPointMake(5*250+scrollView.contentOffset.x, 0);
}
if (scrollView.contentOffset.x > (_imageArray.count - 2)*250) {
scrollView.contentOffset = CGPointMake(250+scrollView.contentOffset.x-(_imageArray.count - 2)*250, 0);
}
}
效果如下:
本示例还存在一个问题,就是点击非焦点cell时不能使其很好的滚动到中间位置,望大神们指点一下。