英语原文共 19 页,剩余内容已隐藏,支付完成后下载完整资料
自定义 Collection View 布局
Ole Begemann
UICollectionView 在 iOS6 中第一次被引入,也是 UIKit 视图类中的一颗新星。它和 UITableView 共享一套 API 设计,但也在 UITableView 上做了一些扩展。UICollectionView 最强大、同时显著超出 UITableView 的特色就是其完全灵活的布局结构。在这篇文章中,我们将会实现一个相当复杂的自定义 collection view 布局,并且顺便讨论一下这个类设计的重要部分。项目的示例代码在 GitHub 上。
布局对象 (Layout Objects)
UITableView 和 UICollectionView 都是 data-source 和 delegate 驱动的。它们在显示其子视图集的过程中仅扮演容器角色(dumb containers),且对子视图集真正的内容毫不知情。
UICollectionView 在此之上进行了进一步抽象。它将其子视图的位置,大小和外观的控制权委托给一个单独的布局对象。通过提供一个自定义布局对象,你几乎可以实现任何你能想象到的布局。布局继承自UICollectionViewLayout 抽象基类。iOS6 中以 UICollectionViewFlowLayout 类的形式提出了一个具体的布局实现。
我们可以使用 flow layout 实现一个标准的 grid view,这可能是在 collection view 中最常见的使用案例了。尽管大多数人都这么想,但是 Apple 很聪明,没有明确的命名这个类为 UICollectionViewGridLayout,而使用了更为通用的术语 flow layout,更好的描述了该类的功能:它通过一个接一个的放置 cell 来建立自己的布局,当需要的时候,插入横排或竖排的分栏符。通过自定义滚动方向,大小和 cell 之间的间距,flow layout 也可以在单行或单列中布局 cell。实际上,UITableView 的布局可以想象成 flow layout 的一种特殊情况。
在你准备自己写一个 UICollectionViewLayout 的子类之前,你需要问你自己,你是否能够使用UICollectionViewFlowLayout 实现你心里的布局。这个类是很容易定制的,并且可以继承本身进行进一步的定制。感兴趣的看这篇文章。
Cells 和其他 Views
为了适应任意布局,collection view 建立了一个类似、但比 table view 更灵活的视图层级(view hierarchy)。像往常一样,你的主要内容显示在 cell 中,cell 可以被任意分组到 section 中。Collection view 的 cell 必须是UICollectionViewCell 的子类。除了 cell,collection view 额外管理着两种视图:supplementary views 和 decoration views。
collection view 中的 Supplementary views 相当于 table view 的 section header 和 footer views。像 cells 一样,他们的内容都由数据源对象驱动。然而和 table view 中用法不一样,supplementary view 并不一定会作为 header 或 footer view;他们的数量和放置的位置完全由布局控制。
Decoration views 纯粹为一个装饰品。他们完全属于布局对象,并被布局对象管理,他们并不从 data source 获取的 contents。当布局对象指定需要一个 decoration view 的时候,collection view 会自动创建,并将布局对象提供的布局参数应用到上面去。并不需要为自定义视图准备任何内容。
Supplementary views 和 decoration views 必须是 UICollectionReusableView 的子类。布局使用的每个视图类都需要在 collection view 中注册,这样当 data source 让它们从 reuse pool 中出列时,它们才能够创建新的实例。如果你是使用的 Interface Builder,则可以通过在可视编辑器中拖拽一个 cell 到 collection view 上完成 cell 在 collection view 中的注册。同样的方法也可以用在 supplementary view 上,前提是你使用了UICollectionViewFlowLayout。如果没有,你只能通过调用 registerClass: 或者 registerNib: 方法手动注册视图类了。你需要在 viewDidLoad 中做这些操作。
自定义布局
作为一个非常有意义的自定义 collection view 布局的例子,我们不妨设想一个典型的日历应用程序中的周 (week) 视图。日历一次显示一周,星期中的每一天显示在列中。每一个日历事件将会在我们的 collection view 中以一个 cell 显示,位置和大小代表事件起始日期时间和持续时间。
一般有两种类型的 collection view 布局:
1.独立于内容的布局计算。这正是你所知道的像 UITableView 和 UICollectionViewFlowLayout 这些情况。每个 cell 的位置和外观不是基于其显示的内容,但所有 cell 的显示顺序是基于内容的顺序。可以把默认的 flow layout 做为例子。每个 cell 都基于前一个 cell 放置(或者如果没有足够的空间,则从下一行开始)。布局对象不必访问实际数据来计算布局。
2.基于内容的布局计算。我们的日历视图正是这样类型的例子。为了计算显示事件的起始和结束时间,布局对象需要直接访问 collection view 的数据源。在很多情况下,布局对象不仅需要取出当前可见 cell 的数据,还需要从所有记录中取出一些决定当前哪些 cell 可见的数据。
在我们的日历示例中,布局对象如果访问某一个矩形内 cells 的属性,那就必须迭代数据源提供的所有事件来决定哪些位于要求的时间窗口中。 与一些相对简单,数据源独立计算的 flow layout 比起来,这足够计算出 cell 在一个矩形内的 index paths 了(假设网格中所有cells的大小都一样)。
如果有一个依赖内容的布局,那就是暗示你需要写自定义的布局类了,同时不能使用自定义的UICollectionViewFlowLayout,所以这正是我们需要做的事情。
UICollectionViewLayout的文档列出了子类需要重写的方法。
collectionViewContentSize
由于 collection view 对它的 content 并不知情,所以布局首先要提供的信息就是滚动区域大小,这样 collection view 才能正确的管理滚动。布局对象必须在此时计算它内容的总大小,包括 supplementary views 和 decoration views。注意,尽管大多数经典的 collection view 限制在一个轴方向上滚动(正如UICollectionViewFlowLayout 一样),但这不是必须的。
在我们的日历示例中,我们想要视图垂直的滚动。比如,如果我们想要在垂直空间上一个小时占去 100 点,这样显示一整天的内容高度就是 2400 点。注意,我们不能够水平滚动,这就意味这我们 collection view 只能显示一周。为了能够在日历中的多个星期间分页,我们可以在一个独立(分页)的 scroll view (可以使用UIPageViewController)中使用多个collection view(一周一个),或者坚持使用一个 collection view 并且返回足够大的内容宽度,这会使得用户感觉在两个方向上滑动自由。
- (CGSize)collectionViewContentSize
{
// Dont scroll horizontally
CGFloat contentWidth = self.collectionView.bounds.size.width;
// Scroll vertically to display a full day
CGFloat contentHeight = DayHeaderHeight (HeightPerHour * HoursPerDay);
CGSize contentSize = CGSizeMake(contentWidth, contentHeight);
return contentSize;
}
为了清楚起见,我选择布局在一个非常简单的模型上:假定每周天数相同,每天时长相同,也就是说天数用 0-6 表示。在一个真实的日历程序中,布局将会为自己的计算大量使用基于 NSCalendaar 的日期。
layoutAttributesForElementsInRect:
这是任何布局类中最重要的方法了,同时可能也是最容易让人迷惑的方法。collection view 调用这个方法并传递一个自身坐标系统中的矩形过去。这个矩形代表了这个视图的可见矩形区域(也就是它的 bounds ),你需要准备好处理传给你的任何矩形。
你的实现必须返回一个包含 UICollectionViewLayoutAttributes 对象的数组,为每一个 cell 包含一个这样的对象,supplementary view 或 decoration view 在矩形区域内是可见的。UICollectionViewLayoutAttributes 类包含了 collection view 内 item 的所有相关布局属性。默认情况下,这个类包含frame,center,size,transform3D,alpha,zIndex 和 hidden属性。如果你的布局想要控制其他视图的属性(比如背景颜色),你可以建一个 UICollectionViewLayoutAttributes 的子类,然后加上你自己的属性。
布局属性对象 (layout attributes objects) 通过 indexPath 属性和他们对应的 cell,supplementary view 或者 decoration view 关联在一起。collection view 为所有 items 从布局对象中请求到布局属性后,它将会实例化所有视图,并将对应的属性应用到每个视图上去。
注意!这个方法涉及到所有类型的视图,也就是 cell,supplementary views 和 decoration views。一个幼稚的实现可能会选择忽略传入的矩形,并且为 collection view 中的所有视图返回布局属性。在原型设计和开发布局阶段,这是一个有效的方法。但是,这将对性能产生非常坏的影响,特别是可见 cell 远少于所有 cell 数量的时候,collection view 和布局对象将会为那些不可见的视图做额外不必要的工作。
你的实现需要做这几步:
创建一个空的可变数组来存放所有的布局属性。
确定 index paths 中哪些 cells 的 frame 完全或部分位于矩形中。这个计算需要你从 collection view 的数据源中取出你需要显示的数据。然后在循环中调用你实现的layoutAttributesForItemAtIndexPath: 方法为每个 index path 创建并配置一个合适的布局属性对象,并将每个对象添加到数组中。
如果你的布局包含 supplementary views,计算矩形内可见 supplementary view 的 index paths。在循环中调用你实现的layoutAttributesForSupplementaryViewOfKind:atIndexPath: ,并且将这些对象加到数组中。通过为 kind 参数传递你选择的不同字符,你可以区分出不同种类的supplementary views(比如headers和footers)。当需要创建视图时,collection view 会将 kind 字符传回到你的数据源。记住 supplementary 和 decoration views 的数量和种类完全由布局控制。你不会受到 headers 和 footers 的限制。
如果布局包含 decoration views,计算矩形内可见 decoration views 的 index paths。在循环中调用你实现的 layoutAttributesForDecorationViewOfKind:atIndexPath: ,并且将这些对象加到数组中。
返回数组。
我们自定义的布局没有使用 decoration views,但是使用了两种 supplementary views(column headers和row headers):
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *layoutAttributes = [NSMutableArray array];
剩余内容已隐藏,支付完成后下载完整资料
资料编号:[485312],资料为PDF文档或Word文档,PDF文档可免费转换为Word
以上是毕业论文外文翻译,课题毕业论文、任务书、文献综述、开题报告、程序设计、图纸设计等资料可联系客服协助查找。