有时候为了区分和banner一样的播放效果,比如在一个界面有多处collectionView分页横向滚动,交互上为了区分,就引出了这种中间大,两侧小的的collectionView布局.如下图展示:
pagedScale.gif
因为这种布局仍然是流水布局,所以可以构建一个自定义布局,继承于collectionViewFlowLayout.再解决两个核心问题.
1.越靠近中间位置的item越大(其实是离中间越远越小,中间的就显得最大).重写系统的此方法
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
// 1.1拿到系统已经算好的布局. NSArray *attributes = [super layoutAttributesForElementsInRect:rect]; // 1.2 计算当前collectionView的中间位置的x值 CGFloat currentCenterX = self.collectionView.contentOffset.x + self.collectionView.bounds.size.width / 2.0; // 1.3遍历当前rect内所有layoutAttributes,计算它跟currentCenterX的距离,距离越远,计算出的缩放值越小,再赋值给layoutAttributes for (UICollectionViewLayoutAttributes *attri in attributes) { CGFloat itemCenterX = attri.center.x; CGFloat delta = ABS(currentCenterX - itemCenterX); // 这里取最小值是因为距离大于一个collectionView宽度的计算出来也没有用,不会显示 delta = MIN(delta, self.collectionView.bounds.size.width); CGFloat scale = (self.nearbyScale - self.centerScale) * delta / self.collectionView.bounds.size.width + self.centerScale; attri.transform = CGAffineTransformMakeScale(1, scale); } return attributes;}
然计算一次是不够的,在collectionView滚动的过程中,每个item的centerX都在变化,所以一旦collectionView发生滚动,就得重新计算.
// 覆写此方法即可.
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;}
第一个核心问题就解决了.
2.当手放开的时候,让离中间最近的item居中.当我们手滑动collectionView后离开,系统会立即调用下面的方法,proposedContentOffset就是系统算好的原本的目标位置,重写该方法,找出离得最近的item,根据这个item计算出新的targetContentOffset并返回给系统,即可实现.
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
// final rect CGRect rect; rect.origin.y = 0; rect.origin.x = proposedContentOffset.x; rect.size = self.collectionView.frame.size; NSArray *attributes = [super layoutAttributesForElementsInRect:rect]; // find nearest item CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;; CGFloat targetOffSetX = MAXFLOAT; for (UICollectionViewLayoutAttributes *attribute in attributes) { if (ABS(targetOffSetX) > ABS(centerX - attribute.center.x)) { targetOffSetX = attribute.center.x - centerX; } }return CGPointMake(proposedContentOffset.x + targetOffSetX, proposedContentOffset.y);
}
在下面两个方法中做一些初始化
- (instancetype)init
{
if (self = [super init]) { _centerScale = 1.0f; _nearbyScale = 0.8f; } return self;}
- (void)prepareLayout
{
[super prepareLayout]; CGFloat left = (self.collectionView.bounds.size.width - self.itemSize.width) / 2.0; self.sectionInset = UIEdgeInsetsMake(0, left, 0, left); self.scrollDirection = UICollectionViewScrollDirectionHorizontal; // 让collectionView滑动的慢一点,分页效果就出来了. self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast;}
至此,效果就完全达到了.
新版Xcode在运行该代码时会报一个警告 UICollectionViewFlowLayout has cached frame mismatch for index path..这个警告来源主要是在使用layoutAttributesForElementsInRect:方法返回的数组时,没有使用该数组的拷贝对象,而是直接使用了该数组。解决办法对该数组进行拷贝,并且是深拷贝。
(NSArray *)deepCopyWithArray:(NSArray *)arr {
NSMutableArray *arrM = [NSMutableArray array];
for (UICollectionViewLayoutAttributes *attr in arr) {
[arrM addObject:[attr copy]];}
return arrM;
}
...
NSArray *attributes = [self deepCopyWithArray:[super layoutAttributesForElementsInRect:rect]];
...
首页点击右上角change











网友评论