瀑布流UICollectionViewFlowLayout

来源:互联网 发布:数据统计的英文 编辑:程序博客网 时间:2024/05/17 07:49

第一篇:
现在我们要实现如下的效果:

1.首先创建瀑布流
  UICollectionView *collectionView = [[UICollectionView alloc]init];
    
CGFloat collectionWH= self.view.frame.size.width;
    collectionView.
frame = CGRectMake(0200, collectionWH, collectionWH);
    [
self.view addSubview:collectionView];
2.直接运行会报错
崩溃信息: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter
原因:没有给它传布局,可以先给它传一个默认的布局UICollectionViewLayout

3.将上面的代码改成如下形式

    CGFloat collectionWH= self.view.frame.size.width;
    
CGRect frame = CGRectMake(0200, collectionWH, collectionWH);
    
UICollectionView *collectionView = [[UICollectionView alloc]initWithFrame:frame collectionViewLayout:[[UICollectionViewFlowLayout alloc]init]];
    [
self.view addSubview:collectionView];


此时运行程序,是一个黑屏,没有任何东西.不要着急,因为此时没有数据源,我们给它设置数据源,通过collectionView.dataSource =self;并设置响应的代理UICollectionViewDataSource

    //注册cell
    [collectionView 
registerClass:[UICollectionViewCell classforCellWithReuseIdentifier:WJCellId];


#pragma mark - <UICollectionViewDataSource>
//返回Item个数
- (
NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    
return 50;
}


- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:WJCellId forIndexPath:indexPath];
    cell.
backgroundColor = [UIColor orangeColor];
    
return cell;
}

此时,运行会看到效果



这样的显示方式是因为我们给它传入了流水布局,如果想要其他的布局,只需要改变layout就行了.至此,我们已经利用流水布局实现水平滚动了,但是我想在实现水平滚动的基础上加一些功能,所以这里我们需要自定义流水布局.




 //创建布局
    
WJLineLayout *layout = [[WJLineLayout alloc]init];
    layout.
itemSize = CGSizeMake(100100);
    
//水平滚动
    layout.
scrollDirection = UICollectionViewScrollDirectionHorizontal;
    
    
CGFloat collectionW= self.view.frame.size.width;
    
CGFloat collectionH= 200;
    
CGRect frame = CGRectMake(0150, collectionW, collectionH);
    
UICollectionView *collectionView = [[UICollectionView alloc]initWithFrame:frame collectionViewLayout:layout];
    collectionView.
dataSource =self;
    [
self.view addSubview:collectionView];

当然这个效果离我们要实现的效果还是有很大的差距,在这里我们重写自定义布局的一些属性:
WJLineLayout.m

/**
*1.cell的放大缩小
*2.停止滚动时,cell的剧中.
*/

- (
instancetype)init
{
    
if (self = [super init]) {
        
/*
          UICollectionViewLayoutAttributes *attrs;
         1.
一个cell对应一个UICollectionViewLayoutAttributes
         2.UICollectionViewLayoutAttributes
对象决定了cellframe
        */

    }
    
return self;
}

/*
 *
这个方法的返回值是一个数组(数组里面存放着rect范围内所有元素的布局属性)
 *
这个方法的返回值决定了rect范围内所有元素的排布(frame)
 */
 
- (
NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
//    NSLog(@"%s",__func__);
    
NSArray *array =[super layoutAttributesForElementsInRect:rect] ;
    
return array;
}



此时运行的话会打印一下信息
2016-07-09 09:28:37.306 CollectionViewDemo[1047:55703] (
    "<UICollectionViewLayoutAttributes: 0x7fbbb252ab30> index path: (<NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}); frame = (0 50; 100 100); ",
    "<UICollectionViewLayoutAttributes: 0x7fbbb252ae60> index path: (<NSIndexPath: 0xc000000000200016> {length = 2, path = 0 - 1}); frame = (110 50; 100 100); ",
    "<UICollectionViewLayoutAttributes: 0x7fbbb252afa0> index path: (<NSIndexPath: 0xc000000000400016> {length = 2, path = 0 - 2}); frame = (220 50; 100 100); ",
    "<UICollectionViewLayoutAttributes: 0x7fbbb252b0e0> index path: (<NSIndexPath: 0xc000000000600016> {length = 2, path = 0 - 3}); frame = (330 50; 100 100); ",
    "<UICollectionViewLayoutAttributes: 0x7fbbb252b320> index path: (<NSIndexPath: 0xc000000000800016> {length = 2, path = 0 - 4}); frame = (440 50; 100 100); ",
    "<UICollectionViewLayoutAttributes: 0x7fbbb252b490> index path: (<NSIndexPath: 0xc000000000a00016> {length = 2, path = 0 - 5}); frame = (550 50; 100 100); ",
    "<UICollectionViewLayoutAttributes: 0x7fbbb252b5d0> index path: (<NSIndexPath: 0xc000000000c00016> {length = 2, path = 0 - 6}); frame = (660 50; 100 100); "
)


因为一个LayoutAttributes对象代表一个cell,在这个方法里,我们在父类已经算好的基础上,加以改进.我们改了LayoutAttributes就相当于改了cell的排布,这里先随便给一个随机数,用于测试
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{

    
NSArray *array =[super layoutAttributesForElementsInRect:rect] ;
    
for (UICollectionViewLayoutAttributes *attrs in array) {
        
CGFloat scale = arc4random_uniform(100)/100.0;
        attrs.
transform = CGAffineTransformMakeScale
(scale, scale);
    }

    
    
return array;
}

再次运行得到如下效果,每个cell的大小都不一样,都是随机的




现在我们需要修改scale的属性值,来达到我们想要的结果.它的规律就是越往中间越大,越往两边越小.这里我选择通过cell的中心点和collection的中心点比较(因为中间cell的中心线正好和collection的中心线重合)
注意:这里有个误区,就是这些cell的坐标原点不一定在collectionView(0,0),是在contentSize里面,是内容的坐标(0,0).所以这里算collectionView的中心点的时候要以content size 来算,这样才有可比性.如果坐标原点不一样,不具有可比性.

这里我们改变数组里rect范围内所有元素的属性,具体代码如下:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    
//获得super已经计算好的布局属性
    
NSArray *array =[super layoutAttributesForElementsInRect:rect] ;
    
//计算collectionView最中心点的X的值(contentsizeX偏移量+collectionView宽度的一半)
    
CGFloat centerX = self.collectionView.contentOffset.x +self.collectionView.frame.size.width*0.5;
    
    
//在原有布局的基础上,进行微调
    
for (UICollectionViewLayoutAttributes *attrs in array) {
        
CGFloat delta = ABS(attrs.center.x - centerX);
    
//根据间距值 计算cell的缩放比例
        
CGFloat scale =1- delta/self.collectionView.frame.size.width ;
    
//设置缩放比例
        attrs.
transform = CGAffineTransformMakeScale(scale, scale);
    }
    
return
 array;
}

运行得到如下结果


这里我们发现感觉有些样子,但是很乱.实际上是当你滑动的时候,动一下就改变
因为layoutAttributesForElementsInRect运行的时候,一进来的时候调用一次,但是滑动时并不会调用.所以现在没法达到我动一下,就根据最新点的x来再算一遍.所以这个时候要实现另外一个方法
//collectionView的显示范围发生改变的时候,是否需要重新刷新布局
//一旦重新刷新布局,就会重新调用layoutAttributesForElementsInRect方法
- (
BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    
return YES;
}

但是这个效果并没有实现当我手一松开的时候,它有个cell在最中间,最好还需要实现另一个方法
/*
 *
这个方法的返回时就决定了collectionView停止滚动时的偏移量(即将停止滚动的时候调用)
 */

- (
CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
    
return CGPointZero;
}


至此,再运行就是这个正确的缩放效果了



这个效果是只会返回1的cell在最中间,我们想要实现那个cell离中心店最近,就把他偏移到中心点.
首先要判断那个cell离中心点最近,我们在- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect方法从数组中可以拿到cell的中心点,具体代码如下

/*
 *
这个方法的返回时就决定了collectionView停止滚动时的偏移量
 */

- (
CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
    
//计算出最终显示的矩形框
    
CGRect rect;
    rect.
origin.y = 0;
    rect.
origin.x =proposedContentOffset.x;
    rect.
size = self.collectionView.frame.size;
    
    
//这里建议调用super,因为用self会把里面for循环的transform再算一遍,但我们仅仅想拿到中心点X,super中已经算好中心点X的值了
    
NSArray *array =[super layoutAttributesForElementsInRect:rect];
    
//计算collectionView最中心点的X的值
    
/*
     proposedContentOffset 
目的,原本
     
拿到最后这个偏移量的X,最后这些cell,距离最后中心点的位置
     */

     
CGFloat centerX = proposedContentOffset.x +self.collectionView.frame.size.width*0.5;
    
    
//存放最小的间距值
    
CGFloat minDelta = MAXFLOAT;
    
for (UICollectionViewLayoutAttributes *attrs in array) {
        
if (ABS(minDelta)  ]]>ABS(attrs.center.x - centerX)) {
            minDelta = attrs.
center.x
 - centerX;
        } ;

    }
    
//修改原有的偏移量
    proposedContentOffset.
x +=minDelta;
    
    
return proposedContentOffset;
}


再次运行,即可得到我们看到的效果了



虽然这个效果已经实现了,但是还是有些小问题,我们需要做下优化.

一般来说,布局的方法不要放在init方法里面,放在prepareLayout里面
/*
 *
用来做布局的初始化操作(不建议在init方法里面做布局的初始化操作)
 1.prepareLayout
 2.layoutAttributesForElementsInRect:
方法
 */

-(
void)prepareLayout
{
    
CGFloat insert = (self.collectionView.frame.size.width -self.itemSize.width)*0.5;
    
self.sectionInset = UIEdgeInsetsMake(0, insert, 0, insert);

}

运行可以看到第一个和最后一个都有一些间距了.至此这个基于流水布局的滑动效果基本上已经完成了.

小结:
1.实现-(void)prepareLayout(目的:做一些初始化)
2.实现- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
(目的:拿出它计算好的布局属性来做一个微调,实现cell不断变大变小)
3.实现- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
(目的:当我们手一松开,它最终停止滚动的时候,应该去在哪.它决定了collectionView停止滚动时候的偏移量)

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
(只要滑动就会重新刷新,就会调用prepareLayout和layoutAttributesForElementsInRect方法)


第二篇:
上一篇中我们确实实现了collection的布局,但是如果我们想点击cell的时候做些事情怎么办呢,这个时候就和布局没有关系了,布局只负责展示,所以我们创建一个自定义的cell



并给它@property (weaknonatomicIBOutlet UIImageView *imageView;一个属性,重写set方法
#import "WJPhotoCell.h"

@interface WJPhotoCell()
@property (weaknonatomicIBOutlet UIImageView *imageView;
@end

- (
void)setImageName:(NSString *)imageName
{
    
_imageName = [imageName copy];
    
self.imageView.image = [UIImage imageNamed:imageName];
}

@end

此时运行是这个效果



如果我们想给图片设置一个边框,可以通过以下方法实现
第一种:直接添加图片的边框的上下左右约束为10,view的背景设置成白色


第二种,通过代码法
- (void)awakeFromNib {
    [
super awakeFromNib];
    
self.imageView.layer.borderColor = [UIColor whiteColor].CGColor;
    
self.imageView.layer.borderWidth = 10;
}

此时,再次运行就是带边框的效果了.



第三篇  
我们对WJLineLayout的布局属性的代码做下优化
ViewController里面水平滚动的属性剪切到
-(void)prepareLayout
{
    
//水平滚动
    
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;

    
CGFloat insert = (self.collectionView.frame.size.width -self.itemSize.width)*0.5;
    
self.sectionInset = UIEdgeInsetsMake(0, insert, 0, insert);

}


总结:
自定义布局   继承UICollectionViewFlowLayout
1.重写prepareLayout方法(作用:在这个方法中做一些初始化操作)
2.重写layoutAttributesForElementsInRect方法(作用:这个方法的返回值是个数组,数组中存放的都是layoutAttributes对象,决定了cell的排布方式)
3.重写shouldInvalidateLayoutForBoundsChange方法(作用:返回yes,那么collectionView显示范围发生改变时,就会重新刷新布局; 一旦重新刷新布局,就会按顺序调用prepareLayout和layoutAttributesForElementsInRect方法)
4.重写 targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity

(作用:返回值决定了collectionView停止滚动时最终的content offset偏移量)

说明

demo下载地址:https://github.com/AllisonWangJiaoJiao/StudyMaterials/tree/master
这些资料是我在看小马哥讲解的时候整理总结的,希望能帮助到有需要的朋友们,如有侵权,请联系我,我立即删除.:


0 0
原创粉丝点击