第一篇:
现在我们要实现如下的效果:
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
这些资料是我在看小马哥讲解的时候整理总结的,希望能帮助到有需要的朋友们,如有侵权,请联系我,我立即删除.: