collectionView布局原理及瀑布流布局方式
一直以来都想研究瀑布流的具体实现方法(起因是因为一则男女程序员应聘的笑话,做程序的朋友应该都知道)。最近学习到了瀑布流的实现方法,瀑布流的实现方式有多种,这里应用collectionView来重写其UICollectionViewLayout进行布局是最为简单方便的。但再用其布局之前必须了解其布局原理。为方便大家学习理解此处补上demo地址https://github.com/PurpleSweetPotatoes/CollcetionViewLayout_demo
在这里笔者挑出其中较为重要的几个方法来进行讲解。
1.- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds 当collectionView视图位置有新改变(发生移动)时调用,其若返回YES则重新布局
2.- (void)prepareLayout 准备好布局时调用。此时collectionView所有属性都已确定。读者在这里可以将collectionView当做画布,有了画布后,我们便可以在其上面画出每个item
3.- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 返回collectionView视图中所有视图的属性(UICollectionViewLayoutAttributes)数组
4.- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 返回indexPath对应item的属性
5.- (CGSize)collectionViewContentSize 设置collectionView的可显示范围
这些方法中最重要的便是3,4方法,在3方法中返回所有视图属性数组,并根据这些属性进行布局,而4方法则返回每个item的属性,我们则在这里设置每个item的属性(主要是frame),就可以让collectionView按照我们的意愿进行布局了!(在这里我们不需要用到1方法,若item属性根据滑动改变,此时就需要随时进行布局改变)
瀑布流的实现示意图如下
由图示意可看出除开最开始3个item,后面的item都是存放3列中的最短列上面,因此我们只需要计算出每个item的frame,并摆放的话那我们的瀑布流自然就成功了。
.h文件中
typedef CGFloat(^HeightBlock)(NSIndexPath *indexPath , CGFloat width);
@interface BQWaterLayout : UICollectionViewLayout
/** 列数 */
@property (nonatomic, assign) NSInteger lineNumber;
/** 行间距 */
@property (nonatomic, assign) CGFloat rowSpacing;
/** 列间距 */
@property (nonatomic, assign) CGFloat lineSpacing;
/** 内边距 */
@property (nonatomic, assign) UIEdgeInsets sectionInset;
/**
* 计算各个item高度方法 必须实现
*
* @param block 设计计算item高度的block
*/
- (void)computeIndexCellHeightWithWidthBlock:(CGFloat(^)(NSIndexPath *indexPath , CGFloat width))block;
@end
为了方便修改瀑布流的布局我们需要设置排列布局的各个接口,因为瀑布流中只能通过列数计算出item的宽,因此需要使用computeIndexCellHeightWithWidthBlock来计算出每个item的高度(利用宽高比)!
.m文件中
代码中注释已经写的很明白了,无需多做解释,此处写法只能实现item的布局,不能添加headview或footview!
@interface BQWaterLayout()
/** 存放每列高度字典*/
@property (nonatomic, strong) NSMutableDictionary *dicOfheight;
/** 存放所有item的attrubutes属性*/
@property (nonatomic, strong) NSMutableArray *array;
/** 计算每个item高度的block,必须实现*/
@property (nonatomic, copy) HeightBlock block;
@end @implementation BQWaterLayout
- (instancetype)init
{
self = [super init];
if (self) {
//对默认属性进行设置
/**
默认行数 3行
默认行间距 10.0f
默认列间距 10.0f
默认内边距 top:10 left:10 bottom:10 right:10
*/
self.lineNumber = ;
self.rowSpacing = 10.0f;
self.lineSpacing = 10.0f;
self.sectionInset = UIEdgeInsetsMake(, , , );
_dicOfheight = [NSMutableDictionary dictionary];
_array = [NSMutableArray array];
}
return self;
} /**
* 准备好布局时调用
*/
- (void)prepareLayout{
[super prepareLayout];
NSInteger count = [self.collectionView numberOfItemsInSection:];
//初始化好每列的高度
for (NSInteger i = ; i < self.lineNumber ; i++) {
[_dicOfheight setObject:@(self.sectionInset.top) forKey:[NSString stringWithFormat:@"%ld",i]];
}
//得到每个item的属性值进行存储
for (NSInteger i = ; i < count; i ++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:];
[_array addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
}
}
/**
* 设置可滚动区域范围
*/
- (CGSize)collectionViewContentSize{
NSLog(@"collectionViewContentSize");
__block NSString *maxHeightline = @"";
[_dicOfheight enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *obj, BOOL *stop) {
if ([_dicOfheight[maxHeightline] floatValue] < [obj floatValue] ) {
maxHeightline = key;
}
}];
return CGSizeMake(self.collectionView.bounds.size.width, [_dicOfheight[maxHeightline] floatValue] + self.sectionInset.bottom);
}
/**
* 计算indexPath下item的属性的方法
*
* @return item的属性
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
//通过indexPath创建一个item属性attr
UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
//计算item宽
CGFloat itemW = (self.collectionView.bounds.size.width - (self.sectionInset.left + self.sectionInset.right) - (self.lineNumber - ) * self.lineSpacing) / self.lineNumber;
CGFloat itemH;
//计算item高
if (self.block != nil) {
itemH = self.block(indexPath, itemW);
}else{
NSAssert(itemH != ,@"Please implement computeIndexCellHeightWithWidthBlock Method");
}
//计算item的frame
CGRect frame;
frame.size = CGSizeMake(itemW, itemH);
//循环遍历找出高度最短行
__block NSString *lineMinHeight = @"";
[_dicOfheight enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *obj, BOOL *stop) {
if ([_dicOfheight[lineMinHeight] floatValue] > [obj floatValue]) {
lineMinHeight = key;
}
}];
int line = [lineMinHeight intValue];
//找出最短行后,计算item位置
frame.origin = CGPointMake(self.sectionInset.left + line * (itemW + self.lineSpacing), [_dicOfheight[lineMinHeight] floatValue]);
_dicOfheight[lineMinHeight] = @(frame.size.height + self.rowSpacing + [_dicOfheight[lineMinHeight] floatValue]);
attr.frame = frame; return attr;
}
/**
* 返回视图框内item的属性,可以直接返回所有item属性
*/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
return _array;
}
/**
* 设置计算高度block方法
*
* @param block 计算item高度的block
*/
- (void)computeIndexCellHeightWithWidthBlock:(CGFloat (^)(NSIndexPath *, CGFloat))block{
if (self.block != block) {
self.block = block;
}
}
@end
至此一个简单的collectionViewLayout瀑布流布局便设置完成,只需要在自己代码中使用此布局便可以得到一个瀑布流了!
下图是笔者的效果图:
2列效果 3列效果
后记:
笔者本来开始还担心如果item过多,那么设置的属性就会过多,比如数组内存放一千或一万个item的属性,后来经过笔者测试后发现,系统应该每次都是事先计算好了所有item的属性(通过tableView计算每行高度的代理方法来思考),因此直接初始化好所有item的属性做法应该不会有太大弊端!如果笔者所做有什么错误或不妥之处望指出!谢谢!
collectionView布局原理及瀑布流布局方式的更多相关文章
- collectionView布局原理及瀑布流布局方式--备用
最近学习到了瀑布流的实现方法,瀑布流的实现方式有多种,这里应用collectionView来重写其UICollectionViewLayout进行布局是最为简单方便的.但再用其布局之前必须了解其布局原 ...
- 【CSS】瀑布流布局的两种方式:传统多列浮动和绝对定位布局
传统多列浮动 各列固定宽度,并且左浮动: 一列中的数据块为一组,列中的每个数据块依次排列即可: 更多数据加载时,需要分别插入到不同的列上: 优点: (1)布局简单,应该说没啥特别的难点: (2)不用明 ...
- 关于瀑布流的布局原理分析(纯CSS瀑布流与JS瀑布流)
瀑布流 又称瀑布流式布局,是比较流行的一种网站页面布局方式.即多行等宽元素排列,后面的元素依次添加到其后,等宽不等高,根据图片原比例缩放直至宽度达到我们的要求,依次按照规则放入指定位置. 为什么使用瀑 ...
- Xamarin自定义布局系列——瀑布流布局
Xamarin.Forms以Xamarin.Android和Xamarin.iOS等为基础,自己实现了一整套比较完整的UI框架,包含了绝大多数常用的控件,如下图 虽然XF(Xamarin.Forms简 ...
- 瀑布流布局使用详解——JQuery插件Isotope(动态实现子项目筛选)
瀑布流布局,听起来听牛逼的样子,其实就是简单的子元素筛选功能.不过这一功能在网站页面布局当中还是很常用的,特别是在电商网站中 经常会有点一个钮筛选,然后页面的子元素刷的以下变了样.接下来,我们先简单介 ...
- Django 项目补充知识(JSONP,前端瀑布流布局,组合搜索,多级评论)
一.JSONP 1浏览器同源策略 通过Ajax,如果在当前域名去访问其他域名时,浏览器会出现同源策略,从而阻止请求的返回 由于浏览器存在同源策略机制,同源策略阻止从一个源加载的文档或脚本获取或设置另一 ...
- jquery实现简单瀑布流布局
jquery实现简单瀑布流布局 是开头都会说的原理 瀑布流布局有两种,一种是固定列,一种是非固定列.在此主要记述第一种的实现. 固定列的特征是:无论页面如何缩放,每行的总列数都一致. 一行4列的瀑布流 ...
- 通过Measure & Arrange实现UWP瀑布流布局
简介 在以XAML为主的控件布局体系中,有用于完成布局的核心步骤,分别是measure和arrange.继承体系中由UIElement类提供Measure和Arrange方法,并由其子类Framewo ...
- 也来谈谈wap端瀑布流布局
Definition 瀑布流布局,在视觉上表现为参差不齐的多栏布局,随着页面滚动条向下滚动,新数据不断被加载进来. 瀑布流对于图片的展现,是高效而具有吸引力的,用户一眼扫过的快速阅读模式可以在短时间内 ...
随机推荐
- 如何使用Jquery自定义命名空间namespace
// 把生成命名空间的方法绑定在jQuery上 jQuery.namespace = function () { var a = arguments, o = null, i, j, d; for ( ...
- [转]javascript 快速隐藏/显示万行表格列的方法
原文地址:javascript 快速隐藏/显示万行表格列的方法 隐藏表格列,最常见的是如下方式: td.style.display = "none"; 这种方式的效率极低.例如,隐 ...
- ES6 module export options 模块导出、导入语法
http://stackoverflow.com/questions/25494365/es6-module-export-options A year and some later, here is ...
- 《Linux内核设计与实现》CHAPTER1,2阅读梳理
<Linux内核设计与实现>CHAPTER1,2阅读梳理 [学习时间:2.5hours] [学习内容:Linux内核简介——历史与现今版本:Linux内核源代码以及编译] CHAPTER1 ...
- 错误:Value '0000-00-00 00:00:00' can not be represented as java.sql.Timestamp;的解决
问题: 代码中查询MySQL的结果集时报错,提示Value '0000-00-00 00:00:00' can not be represented as java.sql.Timestamp;刚开始 ...
- Python之路-python(面向对象进阶(模块的动态导入、断言、Socket Server))
模块的动态导入 断言 Socket Server 一.模块的动态导入 class C(object): def __init__(self): self.name = "zhangsan&q ...
- 微软良心之作——Visual Studio Code 开源免费跨平台代码编辑器
微软良心之作——Visual Studio Code 开源免费跨平台代码编辑器 在 Build 2015 大会上,微软除了发布了 Microsoft Edge 浏览器和新的 Windows 10 预览 ...
- Salesforce.com Object Query Language (SOQL) 示例
Salesforce 中的用户操作 打开按公司名称排序的供应商记录列表视图.SOQL 查询 SELECT CompanyName__c,ContactName__c FROM Suppliers__x ...
- Java连接本地MySQL数据库进行增删改查操作
package Dao; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStat ...
- c++获取系统时间(引用别人的博文)
//方案— 优点:仅使用C标准库:缺点:只能精确到秒级#include <time.h> #include <stdio.h> int main( void ) { t ...