swift:自定义UICollectionViewFlowLayout
swift:自定义UICollectionViewFlowLayout
写作目的
UICollectionView是ios中一个十分强大的控件,利用它能够十分简单的实现一些很好看的效果。UICollectionView的效果又依赖于UICollectionViewLayout或者它的子类UICollectionViewFlowLayout。而关于自定义UICollectionViewFlowLayout网上介绍的比较少。出于这一目的,写下这边文章,希望能够帮助初学者(我也是)实现一些简单的流水布局效果。下面的演示就是本篇文章的目标。最终版代码和所有图片素材(图片名和项目中有点不一样)已经上传至Github,大家可以下载学习。
![](http://7xnwdv.com1.z0.glb.clouddn.com/%E8%87%AA%E5%AE%9A%E4%B9%89UICollectionViewFlowLayoutLineLayout%E6%BC%94%E7%A4%BA.gif)
几个简单的概念
- UICollectionViewLayout与UICollectionViewFlowLayout
UICollectionView的显示效果几乎全部由UICollectionViewLayout负责(甚至是cell的大小)。所以,一般开发中所说的自定义UICollectionView也就是自定义UICollectionViewLayout。而UICollectionViewFlowLayout是继承自UICollectionViewLayout的,由苹果官方实现的流水布局效果。如果想自己实现一些流水布局效果可以继承自最原始UICollectionViewLayout从头写,也可以继承自UICollectionViewFlowLayout进行修改。文本是继承自UICollectionViewFlowLayt*
- UICollectionViewLayoutAttributes
第二点就说了UICollectionView的显示效果几乎全部由UICollectionViewLayout负责,而真正存储着每一个cell的位置、大小等属性的是UICollectionViewLayoutAttributes。每一个cell对应着一个属于自己的UICollectionViewLayoutAttributes,而UICollectionViewLayout正是利用UICollectionViewLayoutAttributes里存在的信息对每一个cel进行布局。
- 流水布局
所谓流水布局就是:就是cell以一定的规律进行如同流水一般的有规律的一个接着一个的排列。最经典的流水布局便是九宫格布局,绝大部分的图片选择器也是流水布局。
准备工作
- xcode7.0
- swift2.0
- 自己我提供的素材并在控制器中添加如下代码
class ViewController: UIViewController,UICollectionViewDelegate, UICollectionViewDataSource { lazy var imageArray: [String] = { var array: [String] = [] for i in 1...20 { array.append("\(i)-1") } return array }() override func viewDidLoad() { super.viewDidLoad() let collectionView = UICollectionView(frame: CGRectMake(0, 100, self.view.bounds.width, 200), collectionViewLayout: UICollectionViewFlowLayout()) collectionView.backgroundColor = UIColor.blackColor() collectionView.dataSource = self collectionView.delegate = self collectionView.registerClass(ImageTextCell.self, forCellWithReuseIdentifier: "ImageTextCell") self.view.addSubview(collectionView) } func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.imageArray.count; } func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ImageTextCell", forIndexPath: indexPath) as! ImageTextCell cell.imageStr = self.imageArray[indexPath.item] return cell } } //这里是自定义cell的代码 class ImageTextCell: UICollectionViewCell { var imageView: UIImageView? var imageStr: NSString? { didSet { self.imageView!.image = UIImage(named: self.imageStr as! String) } } override init(frame: CGRect) { super.init(frame: frame) self.imageView = UIImageView() self.addSubview(self.imageView!) } override func layoutSubviews() { super.layoutSubviews() self.imageView?.frame = self.bounds } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
效果应该是这样的
![](http://7xnwdv.com1.z0.glb.clouddn.com/%E8%87%AA%E5%AE%9A%E4%B9%89UICollectionViewFlowLayout%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202015-11-06%2016.03.33.png)
编码
水平排列
创建一个名为LineLayout.swift的文件(继承自UICollectionViewFlowLayout)。添加如下几行代码
var itemW: CGFloat = 100var itemH: CGFloat = 100 override init() { super.init() //设置每一个元素的大小 self.itemSize = CGSizeMake(itemW, itemH) //设置滚动方向 self.scrollDirection = .Horizontal //设置间距 self.minimumLineSpacing = 0.7 * itemW } //苹果推荐,对一些布局的准备操作放在这里 override func prepareLayout() { //设置边距(让第一张图片与最后一张图片出现在最中央)ps:这里可以进行优化 let inset = (self.collectionView?.bounds.width ?? 0) * 0.5 - self.itemSize.width * 0.5 self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset) }
效果就成了这样
![](http://7xnwdv.com1.z0.glb.clouddn.com/%E8%87%AA%E5%AE%9A%E4%B9%89UICollectionViewFlowLayout%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202015-11-06%2016.33.42.png)
shouldInvalidateLayoutForBoundsChange方法与layoutAttributesForElementsInRect方法关系
标题所写出的是十分重要的两方法,先看我添加的如下测试代码
/**返回true只要显示的边界发生改变就重新布局:(默认是false)内部会重新调用prepareLayout和调用layoutAttributesForElementsInRect方法获得部分cell的布局属性*/override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { print(newBounds) return true } /** 用来计算出rect这个范围内所有cell的UICollectionViewLayoutAttributes, 并返回。 */ override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { print("layoutAttributesForElementsInRect==\(rect)") let ret = super.layoutAttributesForElementsInRect(rect) // print(ret?.count) return ret }
为了解释,我添加了几个打印语句,在shouldInvalidateLayoutForBoundsChange返回值设置为true后,会发现layoutAttributesForElementsInRect方法调用十分频繁,几乎是每滑动一点就会调用一次。观察打印信息可以发现很多秘密。
- 启动程序有如下打印
layoutAttributesForElementsInRect==(0.0, 0.0, 568.0, 568.0)
好像看不太懂,没事,尝试滑动。
- 滑动
(0.5, 0.0, 320.0, 200.0) //这个是shouldInvalidateLayoutForBoundsChange方法的打印的newBounds layoutAttributesForElementsInRect==(0.0, 0.0, 568.0, 568.0)//这个是layoutAttributesForElementsInRect打印的rect (1.5, 0.0, 320.0, 200.0) layoutAttributesForElementsInRect==(0.0, 0.0, 568.0, 568.0) (3.5, 0.0, 320.0, 200.0) layoutAttributesForElementsInRect==(0.0, 0.0, 568.0, 568.0) ...
不难发现,shouldInvalidateLayoutForBoundsChange的参数newBounds的意思是UICollectionView的可见矩形。什么叫可见矩阵?,因为UICollectionView也是UIScrollView的子类,所以它真正的“内容”远远不止我们屏幕上看到的那么多(这里不再话时间继续解释可见矩阵)。那好像layoutAttributesForElementsInRect打印出来的东西没有啥变化是怎么回事?不急继续滑动。
- 解密
继续滑动后有这些信息,经过删除一些无用信息,显示如下。(注意看有注释的行)
...
(248.0, 0.0, 320.0, 200.0) layoutAttributesForElementsInRect==(0.0, 0.0, 568.0, 568.0) (249.0, 0.0, 320.0, 200.0) //这里是可见矩阵 layoutAttributesForElementsInRect==(0.0, 0.0, 1136.0, 568.0) //这里变化了1136.0是568.0的2倍(1136代表的是宽度的意思应该知道不需要解释吧) (250.0, 0.0, 320.0, 200.0) layoutAttributesForElementsInRect==(0.0, 0.0, 1136.0, 568.0) ... (567.5, 0.0, 320.0, 200.0) layoutAttributesForElementsInRect==(0.0, 0.0, 1136.0, 568.0) (568.5, 0.0, 320.0, 200.0)//这里是可见矩阵 layoutAttributesForElementsInRect==(568.0, 0.0, 568.0, 568.0) // 这里又变化了,x变成了568,宽度变成了568 (571.0, 0.0, 320.0, 200.0) layoutAttributesForElementsInRect==(568.0, 0.0, 568.0, 568.0) ... (815.0, 0.0, 320.0, 200.0) layoutAttributesForElementsInRect==(568.0, 0.0, 568.0, 568.0) (817.0, 0.0, 320.0, 200.0) layoutAttributesForElementsInRect==(568.0, 0.0, 1136.0, 568.0) //还有这里 ... (1135.0, 0.0, 320.0, 200.0) layoutAttributesForElementsInRect==(568.0, 0.0, 1136.0, 568.0) (1136.0, 0.0, 320.0, 200.0) layoutAttributesForElementsInRect==(1136.0, 0.0, 568.0, 568.0) //还有这里
上面的的数据展示其实已经足够解释一切了。读到这里,推荐你自己去找找规律,通过自己发现的奥秘绝对比直接看我写出答案有意义的多!下面这张图例已经说明了一切
![](http://7xnwdv.com1.z0.glb.clouddn.com/%E8%87%AA%E5%AE%9A%E4%B9%89UICollectionViewFlowLayoutnewBounds%E4%B8%8Erect.png)
至于为什么会是568的倍数。。因为我是用的5s模拟器。你换成4s就变成480了。至于这样设计的理由,我猜测是为了方便进行范围的确定。
缩放效果
了解了上面shouldInvalidateLayoutForBoundsChange方法与layoutAttributesForElementsInRect方法关系后,可以继续进行编码了。因为主要的内容已经讲解完毕,剩下的就只是一些动画的计算,所以不再继续讲解,直接贴出代码。
class LineLayout: UICollectionViewFlowLayout {var itemW: CGFloat = 100 var itemH: CGFloat = 100 lazy var inset: CGFloat = { //这样设置,inset就只会被计算一次,减少了prepareLayout的计算步骤 return (self.collectionView?.bounds.width ?? 0) * 0.5 - self.itemSize.width * 0.5 }() override init() { super.init() //设置每一个元素的大小 self.itemSize = CGSizeMake(itemW, itemH) //设置滚动方向 self.scrollDirection = .Horizontal //设置间距 self.minimumLineSpacing = 0.7 * itemW } //苹果推荐,对一些布局的准备操作放在这里 override func prepareLayout() { //设置边距(让第一张图片与最后一张图片出现在最中央) self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } /** 返回true只要显示的边界发生改变就重新布局:(默认是false) 内部会重新调用prepareLayout和调用 layoutAttributesForElementsInRect方法获得部分cell的布局属性 */ override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { return true } /** 用来计算出rect这个范围内所有cell的UICollectionViewLayoutAttributes, 并返回。 */ override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { //取出rect范围内所有的UICollectionViewLayoutAttributes,然而 //我们并不关心这个范围内所有的cell的布局,我们做动画是做给人看的, //所以我们只需要取出屏幕上可见的那些cell的rect即可 let array = super.layoutAttributesForElementsInRect(rect) //可见矩阵 let visiableRect = CGRectMake(self.collectionView!.contentOffset.x, self.collectionView!.contentOffset.y, self.collectionView!.frame.width, self.collectionView!.frame.height) //接下来的计算是为了动画效果 let maxCenterMargin = self.collectionView!.bounds.width * 0.5 + itemW * 0.5; //获得collectionVIew中央的X值(即显示在屏幕中央的X) let centerX = self.collectionView!.contentOffset.x + self.collectionView!.frame.size.width * 0.5; for attributes in array! { //如果不在屏幕上,直接跳过 if !CGRectIntersectsRect(visiableRect, attributes.frame) {continue} let scale = 1 + (0.8 - abs(centerX - attributes.center.x) / maxCenterMargin) attributes.transform = CGAffineTransformMakeScale(scale, scale) } return array } /** 用来设置collectionView停止滚动那一刻的位置 - parameter proposedContentOffset: 原本collectionView停止滚动那一刻的位置 - parameter velocity: 滚动速度 - returns: 最终停留的位置 */ override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { //实现这个方法的目的是:当停止滑动,时刻有一张图片是位于屏幕最中央的。 let lastRect = CGRectMake(proposedContentOffset.x, proposedContentOffset.y, self.collectionView!.frame.width, self.collectionView!.frame.height) //获得collectionVIew中央的X值(即显示在屏幕中央的X) let centerX = proposedContentOffset.x + self.collectionView!.frame.width * 0.5; //这个范围内所有的属性 let array = self.layoutAttributesForElementsInRect(lastRect) //需要移动的距离 var adjustOffsetX = CGFloat(MAXFLOAT); for attri in array! { if abs(attri.center.x - centerX) < abs(adjustOffsetX) { adjustOffsetX = attri.center.x - centerX; } } return CGPointMake(proposedContentOffset.x + adjustOffsetX, proposedContentOffset.y) } }
如果在控制器中加入下面两个方法,在你点击控制器,或者点击某个cell会有很炫的动画产生,这都是苹果帮我们做好的。
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {self.imageArray.removeAtIndex(indexPath.item) collectionView.deleteItemsAtIndexPaths([indexPath]) } override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) { if self.collectionView!.collectionViewLayout.isKindOfClass(LineLayout.self) { self.collectionView!.setCollectionViewLayout(UICollectionViewFlowLayout(), animated: true) }else { self.collectionView!.setCollectionViewLayout(LineLayout(), animated: true) } }
总结
本篇文章记录了我在自定义UICollectionViewFlowLayout过程中遇到的一些问题和解决方式(其实有一些坑爹的问题我没有列出,怕误导大家)。上面的全部都是基于UICollectionViewFlowLayout进行的更改。而我在GitHub上面上传的也有一份继承自UICollectionViewLayout的非流水布局。效果如下,因为原理性的东西都差不多,就不再进行分析(代码也有注释)。感兴趣的可以这Github上面下载。如果文章中有什么错误或者更好的方法、建议之类,感谢您的指出。我们共同学习!O(∩_∩)O!
![](http://7xnwdv.com1.z0.glb.clouddn.com/%E8%87%AA%E5%AE%9A%E4%B9%89UICollectionViewFlowLayoutStackLayout%E6%BC%94%E7%A4%BA.gif)
后记
我不会告诉你,介绍UICollectionView的自定义布局这篇文章,是我下一个实验的前传。不过最近被老师强迫帮他们去写文档,估计进度得缓缓。
转载于:https://www.cnblogs.com/Free-Thinker/p/5237397.html
swift:自定义UICollectionViewFlowLayout相关推荐
- swift 自定义滑动视图_在Swift中创建一个向上滑动菜单视图(以编程方式)
swift 自定义滑动视图 This is a quick tutorial on how to create a slide-up menu view in iOS 这是有关如何在iOS中创建向上滑 ...
- Swift - 自定义UIActivity分享
UIActivity可以十分方便地将文字.图片等内容进行分享,比如分享到微信.微博.发送邮件.短信等等.我们不仅可以分享内容出来,也可以在自己的App里添加自己的分享按钮或隐藏已有的分享按钮来实现定制 ...
- Swift - 自定义单元格实现微信聊天界面
1,下面是一个放微信聊天界面的消息展示列表,实现的功能有: (1)消息可以是文本消息也可以是图片消息 (2)消息背景为气泡状图片,同时消息气泡可根据内容自适应大小 (3)每条消息旁边有头像,在左边表示 ...
- swift 自定义TabBarItem
1.效果图 2.NewsViewController.swift [objc] view plaincopy // // NewsViewController.swift // Navig ...
- Swift——自定义转场动画(一)
弹窗转场/过度动画(Popover效果) 避免浪费大家时间,快速查看运行效果可以直接拉到最后看 [五.完整代码] 部分,如果要看递推逻辑,可以从前往后看. 一.基本设置 弹出一个控制器:系统提供了以下 ...
- swift 自定义画渐变色折线图
Github上的demo下载地址:https://github.com/JayFwj/swift---/tree/master/publicVersion ====================== ...
- 创建 Swift 自定义集合类
原文:Building a Custom Collection in Swift 作者:Eric Cerney 译者:kmyhy 数组.字典和集合是常见的集合类型,它们都内置在 Swift 标准库中. ...
- Swift自定义导航栏返回按钮
如何去除swift系统自带的导航栏返回按钮?可以自定义返回按钮 在swift中,怎么替换系统自带的导航栏返回按钮?比如说我要替换成一张返回按钮图片,点击返回到上一页 首先,看一下系统自带的导航栏返回按 ...
- Swift 自定义UITableView
github demo:https://github.com/LINGLemon/LXFSwiftApp UITableView是我们开发过程中比较常用的,用于显示一系列对象,UITableView继 ...
最新文章
- Javascript 检查一组 radio 中的哪一个被勾选
- android webview开启html5支持
- Typescript,Vue you enjoy it ?
- docker 安装nginx_使用 Docker 在你的 mac 上搭建个服务器
- ffmpeg转码_音视频处理神器FFmpeg
- 优达学城深度学习(之四)——jupyter notebook
- java类声明语句_Java面向对象编程-类的声明周期
- linux ps命令查看当前线程正在执行的程序
- 红帽Linux登录密码忘了,redhat linux忘记登陆密码之解决办法
- python视频格式转换命令_音视频格式转换
- windows打开rpm文件
- Adobe或QQ的oxc000007b错误解决方案.
- Samsung/三星I9128 Galaxy Grand root教程_方法
- mysql数据库全部大写吗_mysql中数据库名字分大小写吗
- socket系列之什么是socket
- 去哪儿2017校园招聘 开发工程师(第一批次)- 题解
- Win32 PE病毒原理分析
- Word模板文件,替换内容 文本、图片、表格、列表、区块、嵌套等
- 如何设置网络投票制作投票链接售价多少钱平台投票
- 6代之后的CPU安装win7的方法
热门文章
- windows进程中的内存结构(转)
- 架构,改善程序复用性的设计~第二讲 什么应该提取出来,什么应该保留
- Eclipse中的visual editor 獲取和安装
- python selenium中文文档-selenium-python中文版文档
- SQL 优化极简法则,你掌握几个?
- 谈谈线下消费分期的风险点
- 基于逻辑回归算法模型搭建思路
- 基于 Canal 和 Kafka 实现 MySQL 的 Binlog 近实时同步
- 客房预订+餐饮预订+酒店app+移动端通用版电商app模板+Axure高保真酒店预订管理系统+积分商城+餐饮预订系统+优惠券+订单管理+移动端酒店管理原型+酒店电商原型
- 地方旅游产业运行监测与应急指挥平台/旅游资源管理平台/旅游产业监测平台/旅游应急指挥平台/旅游资源统计/旅游线路数据/旅游产业可视化大屏管理系统/餐饮场所数据/游客流量监测/景区数据监测/视频监控