用collectionview实现瀑布流-转
算法总体思路
先说一下总体上的思路。既然图片的大小、位置各不一样,我们很自然地会想到需要算出每个item的frame,然后把这些frame赋值给当前item的UICollectionViewLayoutAttributes。
自定义UICollectionViewLayout的关键两步是先后重载下面两个方法:
- (void)prepareLayout;
和
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
所以我们的思路是在- (void)prepareLayout;方法中算出所有item的frame,并赋值给当前item的UICollectionViewLayoutAttributes。用图片的形式比较直观:
接下来问题就化归到了如何求每个item的frame。
这里我们抽象出一个列的概念:
除此之外,我们还需要维护一个存储高度的数组COLUMNSHEIGHTS。数组中有n个元素,n表示所有列数,如上图,n = 3。缓存的值表示当前列的高度,上图的例子中,COLUMNSHEIGHTS = [104,123,89]。
然后我们把item逐个放入列中,以这样的规则:从左到右,item优先放入COLUMNSHEIGHTS中最短的列。
打个比方,下一个item就应该放入最短的列,也就是第三列:
以此规则,循环下去,直到所有item都放置完毕。
细节
item.frame.origin:
用自然语言描述,
坐标x应该是这样的:(最短列的编号-1)x 列宽
坐标y应该是这样的:从COLUMNSHEIGHTS中取出最短列对应的高度
所以我们需要一个算法来找出当前COLUMNSHEIGHTS中的最短的列,最直接的方法就是0(n)时间复杂度的循环比较,这里还好因为数据量比较少,如果遇到数据量大的情况可能就需要考虑分治法了。
//寻找此时高度最短的列.第一列为0
-(NSUInteger)findShortestColumn{
NSUInteger shortestIndex = 0;
CGFloat shortestValue = MAXFLOAT;
NSUInteger index=0;//游标
for (NSNumber *columnHeight in self.COLUMNSHEIGHTS) {
if ([columnHeight floatValue] < shortestValue) {
shortestValue = [columnHeight floatValue];
shortestIndex = index;
}
index++;
}
return shortestIndex;
}
找到了最短列,表达出item的x坐标和y坐标就很容易了:
NSUInteger origin_x = [self findShortestColumn] * [self columnWidth];
NSUInteger origin_y = [self.COLUMNSHEIGHTS[shtIndex] integerValue] ;
item.frame.size.width:
由于列数是有用户决定的,所以是个变量,由此可以获得列宽columnWidth = self.collectionView.bounds.size.width /self.columnsCount
然后我们规定,默认情况下item的宽度等于columnWidth。当满足当前列和下一列(如上图红色方块,就是属于当前列位于列2,下一列列3)高度相等时,可以横跨两栏。(再看红色方块,因为在它放进去之前,第二列高度为0,第三列高度也为0,满足横跨的条件)
但是!
如果出现了下面的这种情况:
也就是说,单单满足当前列和下一列高度相等还不够,因为只要一旦满足这个条件,接下去将会一直是横跨的状态。所以我们还需要再加一个条件来筛选这些即使满足当前列和下一列高度相等的item。我们可以用随机数:
NSUInteger randomOfWhetherDouble = arc4random() % 100;//随机数标记是否要双行
arc4random() % 100;会随机生成一个0~100的整数。然后我们设定一个阈值,比如40.只有当同时满足当前列和下一列高度相等和 randomOfWhetherDouble < 40 的item才能真正实现跨行。换句话说,即使当前列和下一列高度相等,也只有百分之40的几率出现跨行的item,这样就很好的保证了宽度不一的item随机出现!
所以宽度的代码是:
if (shtIndex < self.columnsCount - 1 && [self.COLUMNSHEIGHTS[shtIndex] floatValue] == [self.COLUMNSHEIGHTS[shtIndex+1] floatValue] && randomOfWhetherDouble < 40) {
size_width = 2*[self columnWidth];
}else{
size_width = [self columnWidth];
}
item.frame.size.height:
这个可以自由规定,因为在竖直方向高度没有特别的限制。比如我规定:
1.如果是横跨的,高度 = 宽度 * (0.75~1随机)
float extraRandomHeight = arc4random() % 25;
retVal = 0.75 + (extraRandomHeight / 100);
size_height = size_width * retVal;
2.如果是单列的,高度 = 宽度 * (0.75~1.25随机)
float extraRandomHeight = arc4random() % 50;
retVal = 0.75 + (extraRandomHeight / 100);
size_height = size_width * retVal; // 高度为宽度的0.75~1.25倍
补充
实际测试中发现,即使把出现横跨item的阈值调成0,也就是只要满足当前列和下一列高度相等,100%出现横跨的情况,出现横跨的情况也是少之又少。为什么呢?原因出在了数据类型上,之前我的用的数据类型全是CGFloat或者float的浮点类型,两个浮点数要相等的概率可想而知。改成NSUInteger之后就好多了。除此之外,为了增加横跨情况出现的概率,我还用到了四舍五入。拿图举个例子:
我们让item的高度对某个整数取余,比如以40为单位,让高度对40取余,再让item的高度剪掉这些余数。剩下的高度肯定是40的整数倍。
代码很简单:
size_height = size_height - (size_height % 40);
这可以把某个范围内的高度都归约到用一个高度,也就让左右两列高度相等的概率增加了,出现横跨item的可能性也变大了。
然后,在循环的过程中把每个item的frame赋值给对应的attributes,并把这些attributes保存到一个数组里。
//给attributes.frame 赋值,并存入 self.itemsAttributes
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.frame = CGRectMake(origin_x, origin_y, size_width, size_height);
[self.itemsAttributes addObject:attributes];
然后在layoutAttributesForElementsInRect方法中返回:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
return self.itemsAttributes;
}
最后
为了能让collectionView滑起来,我们还需要设置它的ContentSize.其实只要让ContentSize的高度变成COLUMNSHEIGHTS中最长列的高度即可。至于这个求数组中最长列的算法,和前面的求最短列类似。
-(CGSize)collectionViewContentSize{
CGSize size = self.collectionView.bounds.size;
NSUInteger longstIndex = [self findLongestColumn];
float columnMax = [self.COLUMNSHEIGHTS[longstIndex] floatValue];
size.height = columnMax;
return size;
}
效果
垂直滚动1
垂直滚动2
水平滚动1
水平滚动2
用collectionview实现瀑布流-转的更多相关文章
- 用collectionview实现瀑布流-转(后面附demo,供参考)
算法总体思路 先说一下总体上的思路.既然图片的大小.位置各不一样,我们很自然地会想到需要算出每个item的frame,然后把这些frame赋值给当前item的UICollectionViewLayou ...
- CollectionView水平和竖直瀑布流的实现
最近在项目中需要实现一个水平的瀑布流(即每个Cell的高度是固定的,但是长度是不固定的),因为需要重写系统 UICollectionViewLayout中的一些方法通过计算去实现手动布局,所以本着代码 ...
- 【iOS开发】collectionView 瀑布流实现
一.效果展示 二.思路分析 1> 布局的基本流程 当设置好collectionView的布局方式之后(UICollectionViewFlowLayout),当系统开始布局的时候,会调用 pre ...
- iOS瀑布流实现(Swift)
这段时间突然想到一个很久之前用到的知识-瀑布流,本来想用一个简单的方法,发现自己走入了歧途,最终只能狠下心来重写UICollectionViewFlowLayout.下面我将用两种方法实现瀑布流,以及 ...
- iOS开发之窥探UICollectionViewController(四) --一款功能强大的自定义瀑布流
在上一篇博客中<iOS开发之窥探UICollectionViewController(三) --使用UICollectionView自定义瀑布流>,自定义瀑布流的列数,Cell的外边距,C ...
- iOS开发之窥探UICollectionViewController(三) --使用UICollectionView自定义瀑布流
上篇博客的实例是自带的UICollectionViewDelegateFlowLayout布局基础上来做的Demo, 详情请看<iOS开发之窥探UICollectionViewControlle ...
- WaterfallFlowLayout瀑布流用重写UICollectionViewFlowLayout类实现
最近调研瀑布流,在gitHub上下了个Demo发现它的所有视图都是用Main.storyboard拖的, 自己研究半天没研究明白; 然后就又找了一个Demo, 它的视图全是手打的, 但是实现的方法不太 ...
- 自定义UICollectionLayout布局 —— UIKit之学习UICollectionView记录一《瀑布流》
一.思路 思路一:比较每一行所有列的cell的高度,从上到下(也就是从第一行开始),从最短的开始计算,(记录下b的高度和索引,从开始计算,依次类推) 思路二:设置上.下.左.右间距和行间距.列间距及列 ...
- 自定义UICollectinviewFlowLayout,即实现瀑布流
如图所示,通过实现不规则的网格分布,来显示出不同的效果.因为集合视图必须要指定布局还可以显示,所以自定义布局就可以实现瀑布流的效果. //创建布局对象 WaterFlowLayout *flowLay ...
随机推荐
- 448. Find All Numbers Disappeared in an Array Add to List
题目描述 题目分析 有个[1,n]的条件要充分利用起来. 题目代码 public class Solution { public List<Integer> findDisappeared ...
- javascript guid(uuid)
http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript function generateU ...
- C library function - freopen()
Description The C library function FILE *freopen(const char *filename, const char *mode, FILE *strea ...
- [poj3321]Apple Tree(dfs序+树状数组)
Apple Tree Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 26762 Accepted: 7947 Descr ...
- How to remove k__BackingField from Json data
当用DataContractJsonSerializer类序列化对象为Json时,发现序列化后的json对象,每个属性都带有k__BackingField后缀,像这样: 解决办法:为要被序列化的类打上 ...
- Circular progress bar in Unity 3D
Circular progress bar in Unity 3D - UnityScripthttp://stackoverflow.com/questions/22662706/circular- ...
- 转linq中的Single()、First()、Take(1) LINQ 标准的查询操作符 排序 orderby、thenby、Take
Single():操作一个集合,同时强要求只有一个对象匹配,并返回这一个. First():操作一个集合,可以有多个对象匹配,但是只返回第一个. Take(1):操作一个集合,可以有对个对象匹配,单只 ...
- Project中分清楚挣值项
在项目管理非常重要的挣值管理,有一些关键项,像PV,EV,AC,BAC,EAC,ETC等等这些都是关键项,如果这个没分清楚,计算出很多东西都是错的,下面两个图是我一个项目快要完成的报表. 图1 图2 ...
- uva 1658(最小费用最大流)
题意:一个带权有向图,求起点到终点的两条路径权值之和最小,且两条路径没有公共点(除起点,终点): 分析:拆点法,将u拆成u和u',u-u'容量为1,费用为0,这样就能保证每个点只用一次,起点s-s'容 ...
- Windows Store App 关键帧动画
关键帧动画和插值动画类似,同样可以根据目标属性值的变化产生相应的动画效果,不同的是,插值动画是在两个属性值之间进行渐变,而关键帧动画打破了仅通过两个属性值控制动画的局限性,它可以在任意多个属性值之间进 ...