由于项目需求,需要在同一个collectionView上面,实现不同的layout布局。一个是固定item大小,没有动画的layout,这个用UICollectionViewFlowLayout就可以实现;另一种是实现水平布局,中间放大,能够看见两边item边缘的效果。
基于以上需求,在对第一个布局,我直接设置一下UICollectionViewFlowLayout对象的itemSize就可以完成。但是对于第二种的水平移动中间放大的效果,需要自定义layout。下面我就详细的叙述一下,我在自定义layout时,遇到的坑。
在自定义UICollectionViewFlowLayout的过程中,需要重写三个方法:
1.- (void)prepareLayout;主要用于相关参数的初始化,比如itemSize,minimumLineSpacing,minimumInteritemSpacing,setScrollDirection(设置滚动方向,sectionInset(整个组的四周边距);
/**
*实现一些初始化工作
*/
- (void)prepareLayout{
lastOffset = self.collectionView.contentOffset.x;
//设置每一个cell的尺寸
float itemWidth = SCR_WIDTH - 134;
//设置每一个cell的尺寸
self.itemSize = CGSizeMake(itemWidth, getCellHeight(itemWidth));
self.minimumLineSpacing = 38;
//滑动方向
[self setScrollDirection:UICollectionViewScrollDirectionHorizontal];
CGFloat inset = (self.collectionView.width - itemWidth) / 2.0;
//把item的左右边切掉,让item处在屏幕中间位置
// UIEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right)
self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset);
}
在这方法中,一定要注意设置sectionInset,因为它决定了你横向移动首位item的位置。我在一开始,当你发现第一个位置,和最后一个位置,不是自己想要的,你就要开始从这个地方入手考虑了。
2.- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;这个方法是获取所有item的layoutAttributes;
/**
* 所有item的布局属性
*/
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect{
//计算可见的矩形框
CGRect visiableRect;
visiableRect.size = self.collectionView.frame.size;
visiableRect.origin = self.collectionView.contentOffset;
//1.取得cell原来的UICollectionViewLayoutAttributes
NSArray *array = [super layoutAttributesForElementsInRect:rect];
//屏幕中间的X
CGFloat screenCenterX = self.collectionView.contentOffset.x + self.collectionView.width/2.0;
//2.遍历所有的布局属性
[array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (CGRectIntersectsRect(visiableRect, obj.frame)) {
//每个item的centerX
CGFloat itemCenterX = obj.center.x;
//差距越小,缩放越大
//计算缩放比例
CGFloat scale = 1 + 0.25 * (1 - ABS(itemCenterX - screenCenterX) / (self.collectionView.width/2));
obj.transform3D = CATransform3DMakeScale(scale, scale, 1);
}
}];
return array;
}
在这个方法中,可以获取当前屏幕显示的显示的item,也就是视觉上所看到,从而找到需要做动画的item
3.- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity。这个方法是获取滑动停止的位置,根据我的需求,我需要在停止的时候,找到离屏幕中间最近的item,并将它显示在屏幕中间。代码如下:
/**
*用来设置collectionView停止滚动的那一刻的位置 proposedContentOffset: collectionView停止滚动的位置 velocity : 滚动速度
*/
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{ // 判断是否为第一个 if (proposedContentOffset.xself.collectionViewContentSize.width-SCR_WIDTH*1.5+self.sectionInset.right) {
return CGPointMake(self.collectionViewContentSize.width-SCR_WIDTH, 0);
}
//1.获取collectionView最后停留的范围
CGRect lastRect;
lastRect.origin = proposedContentOffset;
lastRect.size = self.collectionView.frame.size;
//2.取出这个范围类所有布局属性
NSArray *array = [self layoutAttributesForElementsInRect:lastRect];
//3.遍历所有布局的属性
//停止滑动时item应该在的位置
__block CGFloat adjustOffsetX = MAXFLOAT;
//屏幕中间的X
CGFloat screenCenterX = proposedContentOffset.x + self.collectionView.width/2.0;
[array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (ABS(obj.center.x - screenCenterX) < ABS(adjustOffsetX)) {
adjustOffsetX = obj.center.x - screenCenterX;
}
}];
return CGPointMake(proposedContentOffset.x + adjustOffsetX, proposedContentOffset.y);
}
以上3个方法,可以基本完成一个水平滑动的中间放大的layout的要求,在对两个layout的切换,只需要设置setCollectionViewLayout:方法就行了。对于我现在的任务来说,我目前无法完成,滑动速度的控制。