iOS开发——UI篇OC篇&不规则排列的图片布局
不规则排列的图片布局
一直在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 = ; CGFloat shortestValue = MAXFLOAT; NSUInteger index=;//游标 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() % ;//随机数标记是否要双行
arc4random() % 100;会随机生成一个0~100的整数。然后我们设定一个阈值,比如40.只有当同时满足 当前列和下一列高度相等 和 randomOfWhetherDouble < 40 的item才能真正实现跨行。换句话说,即使当前列和下一列高度相等,也只有百分之40的几率出现跨行的item,这样就很好的保证了宽度不一的item随机出现!
所以宽度的代码是:
&& [self.COLUMNSHEIGHTS[shtIndex] floatValue] == [self.COLUMNSHEIGHTS[shtIndex+] floatValue] && randomOfWhetherDouble < ) { size_width = *[self columnWidth]; }else{ size_width = [self columnWidth]; }
item.frame.size.height:
这个可以自由规定,因为在竖直方向高度没有特别的限制。比如我规定:
1.如果是横跨的,高度 = 宽度 * (0.75~1随机)
; retVal = ); size_height = size_width * retVal;
2.如果是单列的,高度 = 宽度 * (0.75~1.25随机)
; retVal = ); 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 % );
这可以把某个范围内的高度都归约到用一个高度,也就让左右两列高度相等的概率增加了,出现横跨item的可能性也变大了。
然后,在循环的过程中把每个item的frame赋值给对应的attributes,并把这些attributes保存到一个数组里。
//给attributes.frame 赋值,并存入 self.itemsAttributes NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:]; UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; attributes.frame = CGRectMake(origin_x, origin_y, size_width, size_height); [self.itemsAttributes addObject:attributes];
然后在layoutAttributesForElementsInRect方法中返回:
- (NSArray * }
最后
为了能让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; }
结语
如果你有兴趣,还可以试试
让图片在竖屏和横屏时拥有不同的列数,并以过渡动画切换。
实现contentView的水平滚动并实现上面的不规则布局。
以上两个功能我已经实现并进行了封装,你可以像普通的UICollectionViewLayout一样使用。可以在这里使用和学习。
iOS开发——UI篇OC篇&不规则排列的图片布局的更多相关文章
- iOS开发——UI精选OC篇&UIApplication,UIWindow,UIViewController,UIView(layer)简单介绍
UIApplication,UIWindow,UIViewController,UIView(layer)简单介绍 一:UIApplication:单例(关于单例后面的文章中会详细介绍,你现在只要知道 ...
- iOS开发——UI高级OC篇&自定义控件之调整按钮中子控件(图片和文字)的位置
自定义控件之调整按钮中子控件(图片和文字)的位置 其实还有一种是在storyBoard中实现的,只需要设置对应空间的左右间距: 这里实现前面两种自定义的方式 一:imageRectForContent ...
- iOS开发——网络实用技术OC篇&网络爬虫-使用青花瓷抓取网络数据
网络爬虫-使用青花瓷抓取网络数据 由于最近在研究网络爬虫相关技术,刚好看到一篇的的搬了过来! 望谅解..... 写本文的契机主要是前段时间有次用青花瓷抓包有一步忘了,在网上查了半天也没找到写的完整的教 ...
- ios开发——实用技术篇OC篇&iOS的主要框架
iOS的主要框架 阅读目录 Foundation框架为所有的应用程序提供基本系统服务 UIKit框架提供创建基于触摸用户界面的类 Core Data框架管着理应用程序数据模型 Core ...
- iOS开发——高级技术OC篇&运行时(Runtime)机制
运行时(Runtime)机制 本文将会以笔者个人的小小研究为例总结一下关于iOS开发中运行时的使用和常用方法的介绍,关于跟多运行时相关技术请查看笔者之前写的运行时高级用法及相关语法或者查看响应官方文档 ...
- iOS开发——运行时OC篇&使用运行时获取系统的属性:使用自己的手势修改系统自带的手势
使用运行时获取系统的属性:使用自己的手势修改系统自带的手势 有的时候我需要实现一个功能,但是没有想到很好的方法或者想到了方法只是那个方法实现起来太麻烦,一或者确实为了装逼,我们就会想到iOS开发中最牛 ...
- iOS开发——网络实用技术OC篇&网络爬虫-使用java语言抓取网络数据
网络爬虫-使用java语言抓取网络数据 前提:熟悉java语法(能看懂就行) 准备阶段:从网页中获取html代码 实战阶段:将对应的html代码使用java语言解析出来,最后保存到plist文件 上一 ...
- iOS开发——新特性OC篇&IOS9 SDK新特性
iOS9 SDK新特性 WWDC 2015苹果开发者大会是移动开发者一年一度的盛会,InfoQ中文站除了第一时间整理Keynote内容分享给大家之外,还邀请了资深的一线开发者分享他们的收获.本文为王巍 ...
- iOS开发——网络编程OC篇&使用WebView构建HyBird应用
使用WebView构建HyBird应用 HyBird是一种本地技术与Web相结合,能过实现跨平台的移动应用开发,最常用的一个框架:PhoneGap 一:首先,写好html代码 <!DOCTYPE ...
- iOS开发——图形编程OC篇&粘性动画以及果冻效果
粘性动画以及果冻效果 在最近做个一个自定义PageControl——KYAnimatedPageControl中,我实现了CALayer的形变动画以及CALayer的弹性动画,效果先过目: 先做个提纲 ...
随机推荐
- windows官方多语言方案
编写 Win32 多语言用户界面应用程序 Windows 2000 针对全球市场制定了新的增强支持标准,提供了许多国际化功能,例如完全支持 Unicode.预设支持数百种语言以及用于从右向左语言的镜像 ...
- set xact_abort 用法
默认行为 默认为SET XACT_ABORT OFF,没有事务行为. SET XACT_ABORT ON SET XACT_ABORT ON分为两种: 1.总体作为一个事务,整体提交或整体回滚,格式为 ...
- SPOJ 962 Intergalactic Map (从A到B再到C的路线)
[题意]在一个无向图中,一个人要从A点赶往B点,之后再赶往C点,且要求中途不能多次经过同一个点.问是否存在这样的路线.(3 <= N <= 30011, 1 <= M <= 5 ...
- 将你的Asp.NET应用程序嵌入到SharePoint
转:http://www.cnblogs.com/Clank/archive/2007/05/21/754073.html 为什么要将Asp.net应用程序嵌入到SharePoint?这个我们不讨论! ...
- 《深入Java虚拟机学习笔记》- 第3章 安全
3.1为什么需要安全性 Java的安全模型是其多个重要结构特点之一,它使Java成为适于网络环境的技术.因为网络提供了一条攻击连人的计算机的潜在途径,因此安全性是非常重要的.Java安全模型侧重于保护 ...
- Python脚本控制的WebDriver 常用操作 <二十八> 超时设置和cookie操作
超时设置 测试用例场景 webdriver中可以设置很多的超时时间 implicit_wait.识别对象时的超时时间.过了这个时间如果对象还没找到的话就会抛出异常 Python脚本 ff = webd ...
- 《C++ Primer Plus 第六版》读书笔记
CH1-3:处理数据 1 列表初始化 char c={31325}:不允许缩窄 char c={66}; char c={x}:不能为变量 2 强制类型转换 (typename) value type ...
- 初识SQL注入
什么是SQL注入(SQL Injection)? SQL注入是网站攻击途径之一,这里引用一下百度百科的解释:“所谓SQL注入,就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串, ...
- IGT一道笔试题
1到n连续的n个数 输入m 得出m个有序序列 比如 输入为n=5 ,m=3 则输出 543 542 541 532 531 521 432 431 421 321 当前长度为i,每个位上的取之范围为 ...
- bzoj 1060 [ZJOI2007]时态同步(树形DP)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1060 [题意] 求最少的增加量,使得以rt为根的树中由一个结点出发的所有到叶子结点的路 ...