如何实现一个不规则排列的图片布局算法

本文转载至  http://www.tuicool.com/articles/RfeqiiQ

原文  http://www.cocoachina.com/ios/20150619/12172.html

主题 算法iOS开发

一直在 500px 上看照片,发照片。以前看它的首页图片展示就只是觉得好看,洋气,也没想过自己在iOS上实现一下。昨天不知怎么的就开始想其中的算法了,现在我把思考的过程在这里贴出来分享一下,如果你有更好的算法欢迎探讨。

最终我做出的效果是这样的:

垂直滚动

水平滚动

算法总体思路

先说一下总体上的思路。既然图片的大小、位置各不一样,我们很自然地会想到需要算出每个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. 实现contentView的水平滚动并实现上面的不规则布局。

以上两个功能我已经实现并进行了封装,你可以像普通的UICollectionViewLayout一样使用。可以在 这里 使用和学习。

时间: 2024-12-20 21:04:41

如何实现一个不规则排列的图片布局算法的相关文章

iOS开发——UI篇OC篇&amp;不规则排列的图片布局

不规则排列的图片布局 一直在500px上看照片,发照片.以前看它的首页图片展示就只是觉得好看,洋气,也没想过自己在iOS上实现一下.昨天不知怎么的就开始想其中的算法了,现在我把思考的过程在这里贴出来分享一下,如果你有更好的算法欢迎探讨. 最终我做出的效果是这样的: 垂直滚动 水平滚动 算法总体思路 先说一下总体上的思路.既然图片的大小.位置各不一样,我们很自然地会想到需要算出每个item的frame,然后把这些frame赋值给当前item的UICollectionViewLayoutAttrib

【iOS】随机三角瓷砖布局算法

你已经看够iOS鉴于这些默认的正方形块,整齐地显示? 本篇给出一个随机算法设计的三角布局的瓷砖和实施. 这样的规则,并提出妥协随机排列间.它看起来很凌乱,不会有一个新事物. 重点是设计和实施,以实现布局算法,能够改变颜色或者加入图片. 最新源码下载地址:https://github.com/duzixi/Varied-Layouts(持续维护.欢迎互粉) 博文首发地址:http://blog.csdn.net/duzixi 布局生成效果例如以下: watermark/2/text/aHR0cDo

随机三角形平铺布局算法(iOS实现)

你是否已经看够iOS里默认给出的那些方方正正的块状和规规矩矩的陈列? 本篇给出一种随机三角形平铺布局的算法设计和实现.这种布局在规矩与随机之间做了折中,使其看上去有新鲜感又不会很乱. 本次实现重点在于布局算法的设计和实现,可以改变颜色或者添加图片. 最新源代码下载地址:https://github.com/duzixi/Varied-Layouts(持续维护,欢迎互粉) 博文首发地址:http://blog.csdn.net/duzixi 布局生成效果如下:         核心算法设计以及代码

【转】一个DIV+CSS代码布局的简单导航条

原文地址:http://www.divcss5.com/shili/s731.shtml 简单的DIV CSS代码布局实现导航条 一个蓝色主题的导航条布局案例,本CSS小实例,采用DIV CSS实现.同时不用图片做背景,直接使用背景色实现,鼠标经过悬停对应栏目名称是对应背景蓝色变深. 导航条部分效果截图 一般导航条采用ul li列表布局,这里也不例外DIVCSS5实例也采用列表标签ul li+ CSS布局. 一.布局思维思考   -   TOP 在实际DIV+CSS布局项目中,一般不会只使用一次

左边logo 右边广告图片布局 div css左右浮动布局实例

左边logo 右边广告图片布局(div css左右浮动布局实例) 一般网页头部是左边网站标志logo,右边为广告图片或电话号码图片,这里DIVCSS5为大家介绍对float浮动使用实例布局介绍. 需要div+css布局案例效果图需要div+css布局案例效果图(缩小)一.DIVCSS5实例布局技术点说明 - TOP 1.图片切出说明:首先切好左边logo图片,右边广告图片(切图注意不影响图片质量情况尽量宽度高度尺寸切小).2.采用float:left和float:right布局:一般遇到内容靠左

移动应用开发(IOS/android等)中一个通用的图片缓存方案讲解(附流程图)

在移动应用开发中,我们经常会遇到从网络请求图片到设备上展示的场景. 如果每次都重复发起请求,浪费流量.浪费电量,用户体验也不佳: 将图片持久化到磁盘也不失为一种策略:但每次从文件读取图片也存在一定的io开销,就算采用此策略,我们也需要控制磁盘缓存的容量,以免占用过多系统资源. 其实没有一个方案可以说是完美的方案,只有最适合自己业务需求的方案,才可以说是一个好方案. 我们下面所讲解的方案具备很强的通用性,设计思路简单而清晰: 1.假设每个网络图片的url具有唯一性,如果网络上的图片变化了,会引起输

实现一个最简单图片列表所引发的问题

前一阵看了些Universal-Image-Loager的源码.我觉得看源码很累的一个原因就是除了看怎么实现,就是去揣测为什么这么实现.这个揣测的过程很容易走马观花,看到后面似懂非懂. 人懒到一个地步一句话来说是能躺着就绝对不坐着,能坐着就绝对不蹲着,能蹲着就绝对不站着.有时候看源码也是,能看懂就不会想着去debug,debug能看明白的就懒得去动手写写. 看和写的感受是不一样的.看的是结果,写的是过程. 第三方库的使用让开发变得很方便,大量图片请求的实现,大多数不再是说实现的核心,而是直接说使

CSS背景图片布局

CSS背景图片布局 <!DOCTYPE html><html><body>...Your content goes here...</body></html> 给body标签指定背景图,这样背景图就可以填充整个浏览器viewport了. 其实,该方案对所有的块级容器都可以生效.块级容器的宽高是动态的,那么背景图将自动伸缩,充满整个容器. CSS body标签的样式如下: body {/* 加载背景图 */background-image: url

一个关于汉字查找的算法的猜想

有没有想过当你按下ctrl+F的时候程序是怎样做到查找你要查找的内容,例如在这篇文章里查询"程序设计"我的猜想是它首先会查找到所有有"程"字的词语,将下标存在一个数组里,在查找,直到把这片文章查完,然后再在含有"序"字的地方,过程同上,然后查"设"字,然后再查"计",这用递归应该比较合适,因为操作都是相似的,有限步之内能完成的.所以可以递归,现在就是要设计出程序,加油. 就在刚才想到了另一种办法,就是当查到