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

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

 

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

一直在 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一样使用。可以在 这里 使用和学习。

转载于:https://www.cnblogs.com/Camier-myNiuer/p/4588384.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 流式布局算法是用于在网页中布局内容的常用方法。它的基本原理是将内容分成一个个小块,然后按照一定的规则将这些小块排列在网页上。 流式布局算法的具体实现方式可能有所差异,但通常会包括以下步骤: 1. 设置布局的容器和内容的小块。在 HTML 中,容器可以是 div 元素,而小块则是 p 元素或其他元素。 2. 设置布局的样式。在 CSS 中,可以使用 display: flex 或 display: grid 等属性来设置流式布局。 3. 设置布局的方向。在 CSS 中,可以使用 flex-direction 或 grid-template-columns 等属性来设置布局的方向。 4. 设置布局的间隔。在 CSS 中,可以使用 margin 或 padding 等属性来设置布局的间隔。 5. 设置布局的尺寸。在 CSS 中,可以使用 width 或 height 等属性来设置布局的尺寸。 6. 调整布局的其他属性。根据实际需要,还可以调整布局的其他属性,如对齐方式、换行方式等。 以上是流式布局算法的基本实现步骤,但是具体的实现方法可能会因为不同的需求而有所差异。 ### 回答2: 流式布局算法是一种用于处理具有不确定宽度和高度的网页元素的布局技术。其主要目的是将元素自动适应容器,并在容器宽度不足时自动换行。下面是流式布局算法的具体实现过程: 1. 确定容器的宽度:获取容器的宽度,通常是通过CSS样式指定的。 2. 确定元素的宽度:根据元素的内容、样式和布局规则来确定元素的宽度。可以使用百分比、固定像素等不同的单位。 3. 计算元素的位置:根据元素的宽度和容器的宽度,计算出元素在容器中的位置。通常情况下,第一个元素位于容器的左上角,后续元素则依次向右排列。 4. 判断是否需要换行:在每个元素放置后,判断容器的宽度是否足够容纳下一个元素。如果不够,则需要进行换行操作。 5. 计算换行后的位置:在需要换行时,将下一个元素放置到下一行的起始位置。根据元素的宽度和容器的宽度,确定元素在新行中的位置。 6. 重复步骤3到步骤5,直到所有元素都被放置到容器中。 流式布局算法的核心是计算容器的宽度和确定元素的位置。通过不断地计算和判断,保证元素能够按照指定的布局规则进行排列,并自动适应容器的宽度。这种布局方式广泛应用于响应式设计中的网页布局,可以实现在不同尺寸的设备上都能够显示出良好的效果。 ### 回答3: 流式布局算法是一种网页设计中常用的布局算法,其目的是实现页面元素的自适应排列。该算法的具体实现可以分为以下几个步骤: 1. 对元素进行排序:首先,根据元素的顺序决定它们在页面上的排列顺序。通常情况下,从左到右、从上到下的顺序是流式布局的基本规则。 2. 计算元素宽度:根据网页容器的宽度和元素所占的比例,计算出每个元素的实际宽度。可以使用百分比、固定像素值或其他单位来确定宽度。如果元素的宽度超过了容器的宽度,则可能需要对元素进行换行处理。 3. 处理元素换行:在进行元素换行时,需要注意每行元素的高度和间距情况,并适当调整元素的位置。若元素的高度不一致,则需要对齐元素的底部或顶部。换行的具体策略可以根据设计需求来决定。 4. 处理元素间距:在元素排列的过程中,需要考虑元素间的间距,以保证整体布局的美观性。可以通过设置元素之间的外边距、内边距或添加分隔线等方式来实现。 5. 响应式布局:为了适应不同设备和屏幕大小的需求,流式布局算法还需要考虑响应式设计。根据屏幕宽度的变化,动态调整元素的排列方式和宽度,以达到更好的用户体验。 总的来说,流式布局算法实现可以根据具体的需求和设计风格来进行调整和优化。通过合理的设置元素宽度、行高和间距,可以实现一个动态、自适应的布局效果,使得网页在不同分辨率下都能够呈现出较好的展示效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值