iOS:UICollectionView流式布局及其在该布局上的扩展的线式布局
@property (nonatomic) CGFloat minimumLineSpacing; //每一个item之间最小的行间距
@property (nonatomic) CGFloat minimumInteritemSpacing;//每一个item之间最小的列间距
@property (nonatomic) CGSize itemSize; //每一个item的大小
@property (nonatomic) CGSize estimatedItemSize; //每一个item的预测大小
@property (nonatomic) UICollectionViewScrollDirection scrollDirection; // 集合视图的滚动方向,默认垂直
@property (nonatomic) CGSize headerReferenceSize; //表头视图大小
@property (nonatomic) CGSize footerReferenceSize; //表尾视图大小
@property (nonatomic) UIEdgeInsets sectionInset; //和集合视图上下左右四边的间距
使用流式布局很简单,不需要我们做任何的操作,只需要创建它的实例,然后把它放入创建的集合视图中即可。然而,流式布局看起来比较的单一,没有很炫酷的效果。基于此,我们可以在流式布局的基础上进行一些布局的扩展,比如线式布局等。。。
下面就做一个流式布局和线式布局的切换,点击时,可以自由切换,使集合视图的item排列呈现出不同的效果,举例如下:
1、首先创建一个自定义的单元格类,并附带创建一个.xib文件,在.xib文件中设置一个UIImageView,将它IBOutLet到对应的类中
2、准备一些图片素材
3、在ImagesCell类中
.h
#import <UIKit/UIKit.h> @interface ImagesCell : UICollectionViewCell @property (strong,nonatomic)UIImage *image; @end
.m
#import "ImagesCell.h" @interface ImagesCell() @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation ImagesCell - (void)awakeFromNib { //设置图像视图图层的属性 self.imageView.layer.borderWidth = 3; self.imageView.layer.borderColor = [[UIColor redColor]CGColor]; self.imageView.layer.cornerRadius = 5; self.imageView.clipsToBounds = YES; //切割边角 } -(void)setImage:(UIImage *)image { _image = image; //显示图片 [_imageView setImage:_image]; } @end
3.在控制器类ViewController类中
#import "ViewController.h" #import "ImagesCell.h" #import "CustomLineLayout.h" @interface ViewController ()<UICollectionViewDataSource,UICollectionViewDelegate> @property (strong,nonatomic)UICollectionView *collectionView; @property (strong,nonatomic)NSMutableArray *images; @end @implementation ViewController static NSString *const reuseIndentifier = @"image"; //懒加载 -(NSMutableArray *)images { if (!_images) { _images = [NSMutableArray array]; for (int i=1; i<=25; i++) { [_images addObject:[NSString stringWithFormat:@"clothes%d",i]]; } } return _images; } - (void)viewDidLoad { [super viewDidLoad]; //创建集合视图 CGFloat width = self.view.frame.size.width; CGRect rect = CGRectMake(0, 100, width, 200); self.collectionView = [[UICollectionView alloc]initWithFrame:rect collectionViewLayout:[[CustomLineLayout alloc]init]]; //设置数据源和代理 self.collectionView.dataSource = self; self.collectionView.delegate = self; //注册单元格 [self.collectionView registerNib:[UINib nibWithNibName:@"ImagesCell" bundle:nil] forCellWithReuseIdentifier:reuseIndentifier]; //添加视图 [self.view addSubview:self.collectionView]; //UICollectionViewLayout:最根本的布局,自定义布局时,完全需要自己重新去写需要的布局 //UICollectionViewFlowLayout :流水布局,自定义布局时,有时可以在它的布局基础上再进行扩展布局 } //切换布局方式 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if ([self.collectionView.collectionViewLayout isKindOfClass:[CustomLineLayout class]]) { [self.collectionView setCollectionViewLayout:[[UICollectionViewFlowLayout alloc]init] animated:YES]; } else { [self.collectionView setCollectionViewLayout:[[CustomLineLayout alloc]init] animated:YES]; } } #pragma mark - <UICollectionDataSourceDelegate> //返回组数 -(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 1; } //返回个数 -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return self.images.count; } //显示conllectionView的单元格 -(ImagesCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { //设置重用单元格 ImagesCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIndentifier forIndexPath:indexPath]; //设置cell的内容 cell.image = [UIImage imageNamed:[self.images objectAtIndex:indexPath.item]]; return cell; } //选中item时删除它 -(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { //1.先删除掉对应的模型数据 [self.images removeObjectAtIndex:indexPath.item]; //2.删除item(刷新UI) [self.collectionView deleteItemsAtIndexPaths:@[indexPath]]; } @end
4、自定义线式布局,它继承于流式布局,即
在.m文件中设置每一个item的布局属性如下:
#import "CustomLineLayout.h" //设置item的固定的宽和高 static const CGFloat itemWH = 100; //设置缩放时的有效距离 static const CGFloat activeDistance = 150; //设置缩放因数,值越大,缩放效果越明显 static const CGFloat scaleFactor = 0.6; @implementation CustomLineLayout //UICollectionViewLayoutAttributes:很重要,布局属性设置 //每一个cell(item)都有自己的UICollectionViewLayoutAttributes //每一个indexPath都有自己的UICollectionViewLayoutAttributes -(instancetype)init{ if (self = [super init]){ } return self; } //每一次重新布局前,都会准备布局(苹果官方推荐使用该方法进行一些初始化) -(void)prepareLayout { [super prepareLayout]; //初始化,设置默认的item属性 self.itemSize = CGSizeMake(itemWH, itemWH); self.scrollDirection = UICollectionViewScrollDirectionHorizontal; self.minimumLineSpacing = itemWH * scaleFactor; //将第一个和最后一个item始终显示在中间,即分别将它们设置到组头和组尾的距离 CGFloat inset = (self.collectionView.frame.size.width - itemWH) / 2; self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset); } //是否要重新刷新布局(只要显示的item边界发生改变就重新布局) //只要每一次重新布局内部就会调用下面的layoutAttributesForElementsInRect:获取所有cell(item)的属性 -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return YES; } //用来设置colectionView停止滚动时的那一刻位置,内部会自动调用 #pragma targetContentOffset : 原本colectionView停止滚动时的那一刻位置 #pragma velocity : 滚动的速率,根据正负可以判断滚动方向是向左还是向右 -(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { //1.计算colectionView最终停留的位置 CGRect lastRect; lastRect.origin = proposedContentOffset; lastRect.size = self.collectionView.frame.size; //2.取出这个范围内的所有item的属性 NSArray *array = [self layoutAttributesForElementsInRect:lastRect]; //3.计算最终屏幕的中心x CGFloat centerX = proposedContentOffset.x+ self.collectionView.frame.size.width/2; //4.遍历所有的属性,通过计算item与最终屏幕中心的最小距离,然后将item移动屏幕的中心位置 CGFloat adjustOffsetX = MAXFLOAT; for (UICollectionViewLayoutAttributes *attris in array) { if (ABS(attris.center.x - centerX) < ABS(adjustOffsetX)) { adjustOffsetX = attris.center.x - centerX; } } //5.返回要移动到中心的item的位置 return CGPointMake(proposedContentOffset.x + adjustOffsetX , proposedContentOffset.y); } //返回需要重新布局的所有item属性 -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { //0.计算可见的矩形框属性 CGRect visiableRect; visiableRect.size = self.collectionView.frame.size; visiableRect.origin = self.collectionView.contentOffset; //1.取出默认的cell的UICollectionViewLayoutAttributes NSArray *array = [super layoutAttributesForElementsInRect:rect]; //计算屏幕最中心的x(滚出去的所有的item的偏移 + collectionView宽度的一半) CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width/2; //2.遍历所有的布局属性 for(UICollectionViewLayoutAttributes *attrs in array) { //如果遍历的item和可见的矩形框的frame不相交,即不e是可见的,就直接跳过,只对可见的item进行放缩 if (!CGRectIntersectsRect(visiableRect, attrs.frame)) continue; //每一个item的中心x CGFloat itemCenterX = attrs.center.x; //差距越大,缩放比例越小 //计算每一个item的中心x和屏幕最中心x的绝对值距离,然后可以计算出缩放比例,即 //<1>计算间距/屏幕一半时的比例,得出的数一定<1 //CGFloat ratio = ABS(itemCenterX - centerX) / (self.collectionView.frame.size.width/2); //CGFloat ratio = ABS(itemCenterX - centerX) / 150; //<2>实现放大 //CGFloat scale = 1 + (1 - ratio); //attrs.transform3D = CATransform3DMakeScale(scale, scale, 1.0); //attrs.transform = CGAffineTransformMakeScale(scale, scale); //当item的中心x距离屏幕的中心x距离在有效距离150以内时,item才开始放大 if (ABS(itemCenterX - centerX) <= activeDistance) { //CGFloat ratio = ABS(itemCenterX - centerX) / (self.collectionView.frame.size.width/2); CGFloat ratio = ABS(itemCenterX - centerX) / activeDistance; //<2>实现放大 CGFloat scale = 1 + scaleFactor*(1 - ratio); attrs.transform3D = CATransform3DMakeScale(scale, scale, 1.0); //attrs.transform = CGAffineTransformMakeScale(scale, scale); } else { attrs.transform = CGAffineTransformMakeScale(1, 1); } } return array; } @end
演示结果如下:
流式布局: 切换为线式布局: