title : UICollectionView、瀑布流布局
category : UI
UICollectionView、瀑布流布局
标签(空格分隔): UI
[TOC]
UICollectionViewController
- UICollectionViewController必须在初始化的时候设置布局参数,通常使用系统提供的流水布局UICollectionViewFlowLayout
// 创建流水布局
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
// 创建collectionView
UICollectionViewController *collectionViewVc = [[UICollectionViewController alloc] initWithCollectionViewLayout:layout];
// 设置collectionView的背景颜色
collectionViewVc.collectionView.backgroundColor = [UIColor redColor];
// 去掉滚动指示条
collectionViewVc.collectionView.showsHorizontalScrollIndicator = NO;
// 开启分页效果
collectionViewVc.collectionView.pagingEnabled = YES;
UICollectionView的数据源代理方法
#pragma mark - UICollectionViewDataSource
/**
* 有几组
*/
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 10;
}
/**
* 每一组有多少个cell
*/
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 3;
}
/**
* 每一个cell显示什么内容
*/
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
if (!cell) {
cell = [[UICollectionViewCell alloc] init];
}
return cell;
}
UICollectionViewFlowLayout流水布局
- layout可以用来设置cell的尺寸和位置
- 基本属性参数
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
// 设置cell的尺寸
layout.itemSize = CGSizeMake(100, 100);
// 设置cell的间距
layout.minimumInteritemSpacing = 20;
// 设置每一行之间的间距
layout.minimumLineSpacing = 20;
// 设置每一组的内间距
layout.sectionInset = UIEdgeInsetsMake(0, 10, 0, 10);
// 设置滚动方向为水平方向
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
- UICollectionViewLayoutAttributes:布局属性
- 1.一个cell对应一个UICollectionViewLayoutAttributes对象
- 2.UICollectionViewLayoutAttributes对象决定了cell的frame
-
注意
每一个cell的原点是contentView的左上角,而不是CollectionView的左上角
自定义布局常用几个方法
- prepareLayout
- 用来做布局的初始化操作(不建议在init方法中进行布局的初始化操作)
- (void)prepareLayout
- layoutAttributesForElementsInRect:
- 这个方法的返回值是一个数组(数组里面存放着rect范围内所有元素的布局属性)
- 这个方法的返回值决定了rect范围内所有元素的排布(frame)
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
- shouldInvalidateLayoutForBoundsChange:
- 当collectionView的显示范围发生改变的时候,是否需要重新刷新布局
- 一旦重新刷新布局,就会重新调用下面的方法:
- 1.prepareLayout
- 2.layoutAttributesForElementsInRect:方法
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
- targetContentOffsetForProposedContentOffset:
- 这个方法的返回值,就决定了collectionView停止滚动时的偏移量
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
实例程序
实例一:横向图片浏览器
注意
该自定义布局是继承自UICollectionViewFlowLayout
流水布局
- 使用时只需要将UICollectionView的布局换成该布局即可
- 小知识点:在给UIImageView加一个白色边框成相册的方法
- 方法一:在给ImageView添加约束的时候分别在四周留出一定的空间,并设置cell的背景为白色即可
- 方法二:给imageView加一个边框图层
self.imageView.layer.borderColor = [UIColor whiteColor].CGColor;
self.imageView.layer.borderWidth = 10;
@implementation JLLineLayout
/**
* 用来做布局的初始化操作,不要在init方法中做布局的初始化操作
*/
- (void)prepareLayout
{
[super prepareLayout];
// 设置滚动方向为水平滚动
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
// 设置内边距
CGFloat inset = (self.collectionView.frame.size.width - self.itemSize.width) * 0.5;
self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset);
}
/**
* 当collectionView的显示范围发生改变的时候是否需要重新刷新布局
* 一旦重新刷新布局,就会重新调用下面的两个方法:
1. prepareLayout
2. layoutAttributesForElementsInRect
*
*/
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
/**
* 这个方法的返回值是一个数组(数组里面存放着rect范围内所有元素的布局属性)
* 这个方法的返回值决定了rect范围内所有元素的排布(frame)
*/
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
// 获得super已经计算好的布局属性
NSArray *array = [super layoutAttributesForElementsInRect:rect];
// 计算collectionView最中心点的x值
CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
// 在super布局属性的基础上进行微调
for (UICollectionViewLayoutAttributes *attrs in array) {
// 计算cell的中心点X 和 collectionView最中心点的X值的间距
CGFloat delta = ABS(attrs.center.x - centerX);
// 根据间距,计算cell的缩放比例
CGFloat scale = 1 - delta / self.collectionView.frame.size.width;
// 修改cell的缩放比例
attrs.transform = CGAffineTransformMakeScale(scale, scale);
}
return array;
}
/**
* 这个方法的返回值,决定了collectionView停止滚动时的偏移量
*/
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
// 计算出滚动停止时最终显示的矩形框
CGRect rect;
rect.origin.x = proposedContentOffset.x;
rect.origin.y = 0;
rect.size = self.collectionView.frame.size;
// 获得super已经计算好的布局属性
NSArray *array = [super layoutAttributesForElementsInRect:rect];
// 计算collectionView最中心点的X值
CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;
// 存放最小的间距
CGFloat minDelta = MAXFLOAT;
for (UICollectionViewLayoutAttributes *attrs in array) {
if (ABS(minDelta) > ABS(centerX - attrs.center.x)) {
minDelta = attrs.center.x - centerX;
}
}
// 修改原有偏移量
proposedContentOffset.x += minDelta;
return proposedContentOffset;
}
@end
实例二:瀑布流
.h文件
#import <UIKit/UIKit.h>
@class JLWaterFlowLayout;
@protocol JLWaterFlowLayoutDelegate <NSObject>
@required
/**
* 根据传入的item的宽度返回每一个item的高度(必须实现)
*/
- (CGFloat)waterFlowLayout:(JLWaterFlowLayout *)waterFlowLayout heightForItemAtIndex:(NSInteger)index itemWidth:(CGFloat)itemWidth;
@optional
/** 总共显示多少列 */
- (CGFloat)columnCountInWaterFlowLayout:(JLWaterFlowLayout *)waterFlowLayout;
/** 每列之间的间距 */
- (CGFloat)columnMarginInWaterFlowLayout:(JLWaterFlowLayout *)waterFlowLayout;
/** 每一行之间的间距 */
- (CGFloat)rowMarginInWaterFlowLayout:(JLWaterFlowLayout *)waterFlowLayout;
/** collectionView的内边距 */
- (UIEdgeInsets)edgeInsetsInWaterFlowLayout:(JLWaterFlowLayout *)waterFlowLayout;
@end
@interface JLWaterFlowLayout : UICollectionViewLayout
/** 代理 */
@property (nonatomic, weak) id<JLWaterFlowLayoutDelegate> delegate;
@end
.m文件
#import "JLWaterFlowLayout.h"
/** 默认的列数 */
static const NSInteger JLDefaultColumnCount = 3;
/** 默认的行间距 */
static const CGFloat JLDefaultColumnMargin = 10;
/** 默认的列间距 */
static const CGFloat JLDefaultRowMargin = 10;
/** 默认边缘间距 */
static const UIEdgeInsets JLDefatultEdgeInsets = {10,10,10,10};
@interface JLWaterFlowLayout()
/** 存放所有的布局属性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
/** 存放所有列的当前高度 */
@property (nonatomic, strong) NSMutableArray *columnHeights;
/** 内容高度 */
@property (nonatomic, assign) CGFloat contentHeight;
/** 属性getter方法*/
- (CGFloat)rowMargin;
- (CGFloat)columnMargin;
- (NSInteger)columnCount;
- (UIEdgeInsets)edgeInsets;
@end
@implementation JLWaterFlowLayout
- (NSMutableArray *)columnHeights
{
if (!_columnHeights) {
_columnHeights = [NSMutableArray array];
}
return _columnHeights;
}
- (NSMutableArray *)attrsArray
{
if (!_attrsArray) {
_attrsArray = [NSMutableArray array];
}
return _attrsArray;
}
/**
* 初始化
*/
- (void)prepareLayout
{
// 1.注意要调用super的prepareLayout
[super prepareLayout];
// 清除以前计算的所有高度
[self.columnHeights removeAllObjects];
// 为所有列设置一个默认的高度
for (NSInteger i = 0; i < self.columnCount; i++) {
[self.columnHeights addObject:@(JLDefatultEdgeInsets.top)];
}
// 清除之前所有的布局属性,如果不清除的话,self.attrsArray将会越来越大
[self.attrsArray removeAllObjects];
// 2.开始创建每一个cell对应的布局属性
// 2.1查看对应的第0组有多少个item
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (NSInteger i = 0; i < count; i++) {
// 取出对应第0组第i个item对应的indexPath
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
// 获取indexPath位置cell对应的布局属性
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
// 添加布局属性
[self.attrsArray addObject:attrs];
}
}
/**
* 决定rect范围内cell的排布
* 这个方法在继承自UICollectionViewLayout的情况下,只要拖动UICollectionView就会调用。
* 但是如果在UICollectionViewFlowLayout情况下则不会调用很频繁,因为UICollectionViewFlowLayout布局会对它进行控制
* 由于计算item的布局只需要计算一次,所以,解决方案是将计算item布局的操作放到prepareLayout保存到一个数组中。在这里直接返回即可
*/
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
/**
* 返回indexPath位置cell对应的布局属性
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
// 为第indexPath的item创建一个布局属性
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
// 设置布局属性
// collectionView的宽度
CGFloat collectionViewW = self.collectionView.frame.size.width;
CGFloat cellW = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right - (self.columnCount - 1) * self.columnMargin) / self.columnCount;
CGFloat cellH = [self.delegate waterFlowLayout:self heightForItemAtIndex:indexPath.item itemWidth:cellW];
// 找出高度最短的那一列
// 当前找到的最短的列号
NSInteger destColumn = 0;
// 当前高度最小的那列的高度
CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
for (NSInteger i = 1; i < self.columnCount; i++) {
// 取得第i列的高度
CGFloat columnHeight = [self.columnHeights[i] doubleValue];
if (minColumnHeight > columnHeight) {
minColumnHeight = columnHeight;
destColumn = i;
}
}
CGFloat cellX = self.edgeInsets.left + destColumn * (cellW + self.columnMargin);
CGFloat cellY = minColumnHeight;
if (cellY != self.edgeInsets.top) {
cellY += self.rowMargin;
}
attrs.frame = CGRectMake(cellX, cellY, cellW, cellH);
// 更新最短那列的高度
self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
// 记录内容的高度
CGFloat columnHeight = [self.columnHeights[destColumn] doubleValue];
if (self.contentHeight < columnHeight) {
self.contentHeight = columnHeight;
}
// 返回布局属性
return attrs;
}
/**
* contentSize,设置collectionView的滚动范围
*/
- (CGSize)collectionViewContentSize
{
return CGSizeMake(0, self.contentHeight + self.edgeInsets.bottom + 100);
}
#pragma mark - <JLWaterFlowLayoutDelegate>
- (CGFloat)rowMargin
{
if ([self.delegate respondsToSelector:@selector(rowMarginInWaterFlowLayout:)]) {
return [self.delegate rowMarginInWaterFlowLayout:self];
}else{
return JLDefaultRowMargin;
}
}
- (CGFloat)columnMargin
{
if ([self.delegate respondsToSelector:@selector(columnMarginInWaterFlowLayout:)]) {
return [self.delegate columnMarginInWaterFlowLayout:self];
}else{
return JLDefaultColumnMargin;
}
}
- (NSInteger)columnCount
{
if ([self.delegate respondsToSelector:@selector(columnCountInWaterFlowLayout:)]) {
return [self.delegate columnCountInWaterFlowLayout:self];
}else{
return JLDefaultColumnCount;
}
}
- (UIEdgeInsets)edgeInsets
{
if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterFlowLayout:)]) {
return [self.delegate edgeInsetsInWaterFlowLayout:self];
}else{
return JLDefatultEdgeInsets;
}
}
@end
网友评论