2018-03-07

自定义UICollectionViewFlowLayout

UICollectionViewFlowLayout是UICollectionView的布局对象,我们可以通过自定义布局对象,达到我们想要的效果。它可以对UICollectionView中每个item进行布局

  • 下面是自定义UICollectionViewFlowLayout需要实现的一些重要方法:
  1. prepareLayout 方法可以对布局中得内容进行初始化的准备工作。

      - (void)prepareLayout {  
      [super prepareLayout];  
       ...  
      }
    
  2. 当CollectionView Bounds发生变化时,是否重新进行布局要实现的方法

    - (BOOL)shouldInvalidateLayoutForBoundsChange:
    (CGRect)newBounds {  
    return YES;  
    }  
    
  3. 流式处理中最关键的方法如下:

    //该方法内返回每个item,用来获取CollectionView的所有Item项的layout,进行相应的处理
    -(NSArray *)layoutAttributesForElementsInRect:
    (CGRect)rect {  
    
    } 
    
  4. 对于Item的细节计算,可以调用下面的方法,完成定位处理。

    //参数proposedContentOffset为ConllectionView将要滑到位置;参数velocity 滑动的速度
    - (CGPoint)targetContentOffsetForProposedContentOffset:
    (CGPoint)proposedContentOffset withScrollingVelocity:
    (CGPoint)velocity {  
     ...  
    }  
    

下面是一个案例实现代码,案例是item的中心距离中心位置越远item越小。

 #define kItemWidth 100  

 #import "LFFlowLayout.h"  

 @implementation LFFlowLayout   
 - (void)prepareLayout {  
[super prepareLayout];  
// 设置为水平滚动  
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;  
// 设置每个Item之间的距离  
self.minimumLineSpacing = 100;  
// 重新设置Item的尺寸,不然的话,有等比例缩小的可能  
self.itemSize = CGSizeMake(kItemWidth, kItemWidth);  
}  

 #pragma mark - 重写父类的方法  当CollectionView Bounds发生改变的时候,是否重新布局
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {  
return YES;  
}  

// 获取CollectionView的所有Item项,进行相印的处理(移动过程中,控制各个Item的缩放比例)  
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {  
NSArray *array = [super layoutAttributesForElementsInRect:rect];  
  
CGFloat inset = (self.collectionView.frame.size.width - kItemWidth) * 0.5;  
// 设置第一个和最后一个默认居中显示  
self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);  
  
CGRect visibleRect;  
visibleRect.origin =self.collectionView.contentOffset;  
visibleRect.size = self.collectionView.frame.size;  
  
CGFloat collectionViewCenterX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;  
  
for (UICollectionViewLayoutAttributes *attrs in array) {  
    // 只处理正在界面上面显示的Item  
    if(!CGRectIntersectsRect(visibleRect, attrs.frame)) continue;  
      
    // 计算各个Item的缩放比例(距离中线越近,缩放比例就越大)  
    CGFloat scale;  
    // 防止突变的情况(当Item的中心与collectionView中心的距离大于等于collectionView宽度的一半时,Item不缩放,平稳过度)  
    if(ABS(attrs.center.x - collectionViewCenterX) >= self.collectionView.frame.size.width * 0.5){  
        scale = 1;  
    }  
    else{  
        scale = 1 + 0.8 * (1 - ABS(attrs.center.x - collectionViewCenterX) / (self.collectionView.frame.size.width * 0.5));  
    }  
    attrs.transform3D = CATransform3DMakeScale(scale, scale, 1);  
}  
  
  
return array;  
}  

// 当UICollectionView停止的那一刻ContentOffset的值(控制UICollectionView停止时,有一个Item一定居中显示)  
// 1.proposedContentOffset默认的ContentOffset  
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {  
  
//1. 获取UICollectionView停止的时候的可视范围  
CGRect contentFrame;  
contentFrame.size = self.collectionView.frame.size;  
contentFrame.origin = proposedContentOffset;  
  
NSArray *array = [self layoutAttributesForElementsInRect:contentFrame];  
  
//2. 计算在可视范围的距离中心线最近的Item  
CGFloat minCenterX = CGFLOAT_MAX;  
CGFloat collectionViewCenterX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;  
for (UICollectionViewLayoutAttributes *attrs in array) {  
    if(ABS(attrs.center.x - collectionViewCenterX) < ABS(minCenterX)){  
        minCenterX = attrs.center.x - collectionViewCenterX;  
    }  
}  
  
//3. 补回ContentOffset,则正好将Item居中显示  
return CGPointMake(proposedContentOffset.x + minCenterX, proposedContentOffset.y);  
}  

@end  

你可能感兴趣的:(2018-03-07)