用UICollectionViewFlowLayout
类来管理collectionView中的布局, 它是一个具体的类, 可以直接使用. flow layout实现了一种基于线性的断裂式布局, 也就是说collectionView中的item是按照线性顺序排列的, 该布局会尽量适配item的间距, 使得item排列在同一行, 如果超出一行会接着进行下一行的排列. 图3-1展示了水平方向上的flow layout.
使用flow layout除了可以实现一个网格状的collectionView, 还可以实现更多样式. 比如, 你可以调整间距来实现一个单行滚动的collectionView. 如果item的size可以不同, 这样可以产生非对称效果. 你可以通过代码和xib来配置你的flow layout对象, 步骤如下:
- 创建一个flow layout对象, 将其赋值给collectionView
- 设置cell的width和height
- 设置行间距, item的间距
- 如果你想要section header和footer, 给footer和header设置size
- 设置flow layout滚动的方向(scroll layout)
注意:配置layout时, 你至少需要配置cell的宽高, 不然item默认的宽高为0, 导致item不可见
自定义Flow Layout Attributes
flow layout对象暴露了几个属性用于配置item的外观, 当你设置这些属性时, 该属性对所有的item都有起作用. 比如你通过属性itemSize
来配置item的大小后, 所有的item大小都和属性中配置的一样.
如果你想item的size和spacing都不一样, 那么你需要实现flow layout的委托UICollectionViewDelegateFlowLayout
. 你可以将collectionView的delegate和flow layout的delegate设置成同一个. 当flow layout的delegate实现了相应的方法后, layout对象根据你实现的方法中返回的值来设置item的size和spacing.
设置item的size
- 如果collectionView中的cell大小相同, 那么flow layout中的item的size大小可以通过
itemSize
来确定. - 如果item的size要不同, 那么需要实现委托方法
collectionView:layout:sizeForItemAtIndexPath:
来确定不同item的size. 在委托方法中, 你可以根据index path来确定不同item, 从而确定特定item的size. 如图3-2展示了不同item的不同size
注意:如果item的size是不同的, 那么会导致不同行的item的数量不同
设置行间距和itme的间距
- 使用flow layout布局时, 你可以设置item间的最小间距, 以及最小行间距. 需要注意, 是最小间距, 而不是具体的间距, 也就是说实际显示的间距可能和你设置最小间距不同, 有可能大于最小间距.
- 在布局时, flow layout往一行中加item, 直到一行显示不下, 才开始第二行. 如果一行刚好可以显示整数个item没有额外的空间, 那么此时item的间距为最小间距. 若果还有额外的空间, 那么layout对象会将item的间距均匀地调大以刚好适配屏幕. 如图3-3所展示调整itme的间距来避免最后留下大块空间
-
对于行间距, flow layout使用同样原理来管理. 而且当item的size不同时, 导致行间距也不同. 行间距的实际距离和相邻两行中最大的两个item有关, 如图3-4所示
flow layout提供两个属性
minimumLineSpacing
和minimumInterItemSpacing
来设置静态的行间距和item间距, 如果想动态提供行间距和item间距可以使用委托方法collectionView:layout:minimumLineSpacingForSectionAtIndex:
和collectionView:layout:minimumInteritemSpacingForSectionAtIndex:
来提供动态的间距.
使用Section Insets来调节section的margin
section inset是调节cells使用空间的一种手段. 你可以在section的左右, header/footer上下之间插入间距达到调节cell布局的空间大小. 如图3-5所示, 展示section inset的作用.
什么时候使用子类化的Flow Layout
在有的情况下, 使用UICollectionViewFlowLayout
满足不了需求, 需要继承UICollectionViewFlowLayout
做一些定制化的操作才行. 下面列举了使用子类化UICollectionViewFlowLayout
的几种情况:
-
给你的布局添加新的补充视图(supplementary)和装饰视图(decoration)
标准的flow layout只提供了header, footer没有decoration view, 为了支持更多的supplementary和decoration view你需要重写下面的一些方法:
-
layoutAttributesForElementsInRect:
(required) -
layoutAttributesForItemAtIndexPath:
(required) -
layoutAttributesForSupplementaryViewOfKind:atIndexPath:
(为了支持新的supplementary视图) -
layoutAttributesForDecorationViewOfKind:atIndexPath:
(为了支持新的decoration视图)
在layoutAttributesForElementsInRect:
方法中, 需要调用父类方法来获得cell的layout attribute对象, 然后再添加supplementary或者decoration的layout attribute对象. 再使用其他方法来提供其他attribute. 关于如何创建layout attribute请看Creating Layout Attributes
-
-
给flow layout返回的layout attribute对象做点调整
重写
layoutAttributesForElementsInRect:
方法以及其他返回layout attribute对象的方法. 在这些方法中你应该调用super来获取attribute对象, 在方法中修改调用super获取layout attribute对象, 然后返回. -
给cell和其他view替换新的layout attribute对象
- 通过集成
UICollectionViewLayoutAttributes
类, 自定义layout attribute对象, 你可以往layout attribute中添加新的特性(属性). - 然后在
UICollectionViewFlowLayout
的子类重写layoutAttributesClass
方法返回你自定义的UICollectionViewLayoutAttributes
的子类. - 你还应该重写
layoutAttributesForElementsInRect:
,layoutAttributesForItemAtIndexPath:
方法, 在这些方法中你要给新的layout attribute对象中属性赋值.
- 通过集成
-
插入/删除的item, 确定item的initial/final位置
默认情况下, 当你插入/删除cell时, 只有一个fade动画, 如果你想要自定义动画, 可以重写下面的方法:
initialLayoutAttributesForAppearingItemAtIndexPath:
initialLayoutAttributesForAppearingSupplementaryElementOfKind:atIndexPath:
initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath:
finalLayoutAttributesForDisappearingItemAtIndexPath:
finalLayoutAttributesForDisappearingSupplementaryElementOfKind:atIndexPath:
-
finalLayoutAttributesForDisappearingDecorationElementOfKind:atIndexPath:
在cell被插入/删除前, 你可以通过上面的方法来设置特定的layout attribute对象, 然后flow layout会使用这些attribute来进行动画. 如果你重写了这些方法, Apple推荐你也重写prepareForCollectionViewUpdates:
和finalizeCollectionViewUpdates
这两个方法. 你可以使用者两个方法来跟踪当前周期内那个item被插入/删除. 想知道关于删除/插入的工作原理, 请看Making Insertion and Deletion Animations More Interesting
上面的几种情况和操作也适用于创建自定义的layout, 不过在自定义layout之前考虑一下是否可以使用子类化的flow layout, 如果可以的话, 建议优先使用flow layout的子类, 因为flow layout是经过苹果精心设计的, 效果更好. 事实上, 需要用到自定义layout的情况很少, 因为使用flow layout或继承flow layout可以满足大多数需求. 只有少数情况下才需要自定义layout, 比如, 你的布局不是基于线性的, 或者你的内容不局限在屏幕两个方向上滚动, 你布局中的item经常移动, 在这些情况下用自定义layout更好点.