瀑布流UICollectionViewFlowLayout

第一篇:

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

点击打开链接

1.首先创建瀑布流

UICollectionView *collectionView
= [[UICollectionView alloc]init];

CGFloat collectionWH= self.view.frame.size.width;

collectionView.frame = CGRectMake(0, 200,
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(0, 200,
collectionWH, collectionWH);

UICollectionView *collectionView = [[UICollectionView alloc]initWithFrame:frame collectionViewLayout:[[UICollectionViewFlowLayout alloc]init]];

[self.view addSubview:collectionView];

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

//注册cell

[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier: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(100, 100);

//水平滚动

layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;

CGFloat collectionW= self.view.frame.size.width;

CGFloat collectionH= 200;

CGRect frame = CGRectMake(0, 150,
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对象决定了cell的frame

*/

}

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的值(用contentsize的X偏移量+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 (weak, nonatomic) IBOutlet UIImageView *imageView;一个属性,重写set方法

#import "WJPhotoCell.h"

@interface WJPhotoCell()

@property (weak, nonatomic) IBOutlet 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

这些资料是我在看小马哥讲解的时候整理总结的,希望能帮助到有需要的朋友们,如有侵权,请联系我,我立即删除.:

时间: 2024-10-19 00:06:13

瀑布流UICollectionViewFlowLayout的相关文章

自定义UICollectionViewLayout之瀑布流

目标效果 因为系统给我们提供的 UICollectionViewFlowLayout 布局类不能实现瀑布流的效果,如果我们想实现 瀑布流 的效果,需要自定义一个 UICollectionViewLayout  类,实现瀑布流效果.效果如右图. 依赖工具: 我们需要一个图片大小和图片地址的Josn数据, 和 SDWebImage图片加载的第三方工具 RootViewController.m 1 #import "RootViewController.h" 2 #import "

UICollectionView创建瀑布流

UICollectionView与UITableView类似,它们的用法也差不多,所有的实现都是通过delegate以及dataSource来完成的,都有几个必须实现的协议,为以下协议. - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 1; } - (NSInteger)collectionView:(UICollectionView *)collectionVie

[iOS高级] UICollectionView实现瀑布流效果

UICollectionView在2012年被提出,已经不是什么新技术了,在此只是做一下简单的实现. 集合视图:UICollectionView UICollectionView和UITableView类似,它也是datasource和delegate设计模式的:datasource为view提供数据源,告诉view要显?示些什么东?以及如何显示它们,delegate提供一些样式的?细节以及?户交互的响应. 在collectionView中,对于cell的布局比较复杂,专?使?了?个类来对col

【IOS开发】collectionView 瀑布流实现

一.效果展示 二.思路分析 1> 布局的基本流程 当设置好collectionView的布局方式之后(UICollectionViewFlowLayout),当系统开始布局的时候,会调用 prepareLayout 来布局 - (void)prepareLayout; 与此同时,collectionViewCell 的每个控件的布局属性都会调用 以下方法来设置(可以重写方法来修改每个cell 的数值) 1 - (NSArray<UICollectionViewLayoutAttributes

iOS横向瀑布流的封装

前段时间, 做一个羡慕, 需要使用到瀑布流! 说道瀑布流, 或许大家都不陌生, 瀑布流的实现也有很多种! 从scrollView 到 tableView 书写的瀑布流, 然后再到2012年iOS6 苹果API 新加进的collectionView进行的瀑布流封装! 确实, 不论是写起来还是用起来都要方便很多! 由于项目开发中需要使用到很像瀑布流, 本来想着懒省事, 直接搜一个第三方, 可搜了一会, 并没有搜到有关横向瀑布流的第三方! 于是就决定自己写一个吧! 里边用到的就是UIColleciti

UICollectionView 很简单的写个瀑布流

你项目中要用到它吗? 可能会在你的项目中用到这玩意,最近也是要用就简单的写了一个 Demo.没多少代码,就不放Git了,下面会详细点的说说代码的,要还有什么问题的小伙伴可以直接Q我,也可以把Demo发给你,这里有Q可以找一下加我 多多交流,互相学习! 下面是简单的一个效果图,先给看看效果图! 先说说控制器里面的代码,控制器里面就是我们的  UICollectionView  的一些基本的创建了.其实它和 UITableView 相比较的话,但从创建使用看的话,是挺相似的,但其实它真的比 UITa

iOS瀑布流实现(Swift)

这段时间突然想到一个很久之前用到的知识-瀑布流,本来想用一个简单的方法,发现自己走入了歧途,最终只能狠下心来重写UICollectionViewFlowLayout.下面我将用两种方法实现瀑布流,以及会介绍第一种实现的bug. <1>第一种 效果图如下所示: 这种实现方法的思路: 1)首先调用随机函数,产生随机高度,并把它保存到数组中 - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollection

扩展版瀑布流

<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>瀑布流-扩展版02</title> <style type="text/css"> * { margin: 0; padding: 0; } #list { list-style: none; position: relative; margin: 0 auto; }

PSCollectionView瀑布流实现

[-] 一基本原理 二具体实现 相关数据结构 视图更新方式 relayoutViews方法 removeAndAddCellsIfNecessary方法 select方法 重用数据块视图机制 三使用方法 四其他瀑布流实现 PSCollectionView是一个实现较简洁的仿Pinterest瀑布流iOS版实现,使用UIScrollView做容器,每列列宽固定,高度可变,使用方式类似UITableView.其效果如图: 一.基本原理 其基本实现原理为: 列数固定,根据列数每列存储一个当前列的高度值