创建自定义布局
在开始创建自定义布局之前,首先需要考虑是否必要,下列情况需要考虑使用自定义布局:
1:布局你想要的界面,并不像网格或者基于线程的布局效果。或者需要滚动不止一个方向。
2:你想要频繁改变所有cell的位置,这可能比创建自定义布局有更多的工作,主要是修改存在的流动布局(flow layout )
好消息是,从API角度来看,实现自定义布局并不困难,最困难的部分是实现计算来确定布局中items的位置,然后直接为collection view提供信息。
理解核心布局过程
collection view直接与自定义布局对象来管理全部的布局过程。当collection view 确定了什么时候它需要布局信息,collection view将会要求布局对象提供布局信息。例如:collection view会在第一次显示内容或者是重新计算的时候要求布局信息。你也能够通过调用布局对象的invalidateLayout 方法来显示的告诉collection view更新布局。该方法将强制去除当前的布局信息并生成新的布局信息。
在布局的过程中,collection view会调用布局对象(layout object)的具体方法。在这些方法中,你有机会重新计算items的位置来为collection view提供需要的基本信息。其它的方法也有可能被调用,但是这些方法是经常在布局过程中被调用:
1:使用prepareLayout方法来执行预先计算需要提供的布局信息
2:使用collectionViewContentSize方法基于初始化计算来返回整个内容区域的全部大小
3:使用layoutAttributesForElementsInRect:方法在指定的具体区域内为cell和views返回对应的属性
Figure 5-1 illustrates how you can use the preceding methods to generate your layout information.
在prepareLayout方法中你有机会执行任意计算,来确定布局中cells和views的位置。最低限度,你应该在这个方法中计算足够的信息,能够返回内容区域的全部大小,计算完的结果能够在第二步collectionViewContentSize 方法中返回。
CollectionView使用了内容大小(content size)来配置滚动视图.例如:如果你计算的内容大小在水平和垂直两个方向都超过了当前设备屏幕的区域,滚动视图将调节允许同时在两个方向滚动。不像UICollectionViewFlowLayout,默认是不能够调节布局内容的滚动,只能够在一个方向滚动。
基于当前的滚动位置,CollectionView这时会调用你的layoutAttributesForElementsInRect: 方法为具体矩形区域内的cells和views获取属性(attributes),在返回该属性信息之后,核心的布局过程已经有效的完成了。
在布局完成之后,cells和views的属性还是保持一样的,一直到你或者CollectionView使布局无效(invalidates).通过调用布局对象的invalidateLayout方法,这将使布局对象重新开始,并且开始重新调用prepareLayout方法,继续布局过程。CollectionView也能够在滚动期间自动失效你的布局。如果用户滚动它的内容,CollectionView调用了布局对象的shouldInvalidateLayoutForBoundsChange:方法并失效布局如果该方法返回YES。
笔记:注意,调用invalidateLayout方法是并不会立即执行布局更新。该方法仅仅是标记布局与数据不匹配,在需要的时候被更新。在下一次view的更新循环中,CollectionView将会确认是否它的布局需要更新。事实上,你连续多次调用invalidateLayout方法,每次都并没有立即出发更新操作。
有了上面的基本内容之后,如果希望UICollectionView更加复杂,更加灵活的方式对单元格进行布局,可以通过继承与UICollectionViewLayout,实现自己的布局管理,通过这种方式,开发者几乎可以任意控制UICollectionView中单元格的布局.
另外需要了解的是,在初始化一个UICollectionViewLayout实例后,会有一系列准备方法被自动调用,以保证layout实例的正确。
首先,-(void)prepareLayout将被调用,默认下该方法什么没做,但是在自己的子类实现中,一般在该方法中设定一些必要的layout的结构和初始需要的参数等。
之后, - (CGSize) collectionViewContentSize将被调用,以确定collection应该占据的尺寸。注意这里的尺寸不是指可视部分的尺寸,而应该是所有内容所占的尺寸。collectionView的本质是一个scrollView,因此需要这个尺寸来配置滚动行为。
接下来 - (NSArray的*)layoutAttributesForElementsInRect:(CGRect)rect被调用,这个没什么值得多说的。初始的layout的外观将由该方法返回的UICollectionViewLayoutAttributes来决定。
另外,在需要更新的布局时,需要给当前布局发送-invalidateLayout,该消息会立即返回,并且预约在下一个loop的时候刷新当前layout,这一点和UIView的setNeedsLayout方法十分类似。在-invalidateLayout后的下一个collectionView的刷新loop中,又会从prepareLayout开始,依次再调用-collectionViewContentSize和-layoutAttributesForElementsInRect来生成更新后的布局。
还有就是UICollectionViewLayoutAttributes是一个非常重要的类,先来看看property列表:
@property (nonatomic) CGRect frame
@property (nonatomic) CGPoint center
@property (nonatomic) CGSize size
@property (nonatomic) CATransform3D transform3D
@property (nonatomic) CGFloat alpha
@property (nonatomic) NSInteger zIndex
@property (nonatomic, getter=isHidden) BOOL hidden
可以看到,UICollectionViewLayoutAttributes的实例中包含了诸如边框,中心点,大小,形状,透明度,层次关系和是否隐藏等信息。和DataSource的行为十分类似