UICollectionView
首先从collectionView说起,collectionView由三个部分构成:
- Cells
- Supplementary Views 追加视图 (类似Header或者Footer)
- Decoration Views 装饰视图 (用作背景展示)
一方面,collectionView和tableview一样,由提供数据的UICollectionViewDataSource以及处理用户交互的UICollectionViewDelegate支持。另一方面,对于cell的样式和组织方式,由于collectionView比tableView要复杂得多,collectionView使用一个类UICollectionViewLayout对布局和行为进行描述。UICollectionViewLayout也是collectionView的精髓所在。
UICollectionViewLayoutAttributes
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的行为十分类似,当UICollectionView在获取布局时将针对每一个indexPath的部件(包括cell,追加视图和装饰视图),向其上的UICollectionViewLayout实例询问该部件的布局信息(在这个层面上说的话,实现一个UICollectionViewLayout的时候,其实很像是zap一个delegate,之后的例子中会很明显地看出),这个布局信息,就以UICollectionViewLayoutAttributes的实例的方式给出。
UICollectionViewLayout
UICollectionViewLayout的功能为向UICollectionView提供布局信息,不仅包括cell的布局信息,也包括追加视图和装饰视图的布局信息。实现一个自定义layout的常规做法是继承UICollectionViewLayout类,然后重载下列方法:
-(CGSize)collectionViewContentSize
返回collectionView的内容的尺寸
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
返回rect中的所有的元素的布局属性
返回的是包含UICollectionViewLayoutAttributes的NSArray
UICollectionViewLayoutAttributes可以是cell,追加视图或装饰视图的信息,通过不同的UICollectionViewLayoutAttributes初始化方法可以得到不同类型的UICollectionViewLayoutAttributes:
layoutAttributesForCellWithIndexPath:
layoutAttributesForSupplementaryViewOfKind:withIndexPath:
layoutAttributesForDecorationViewOfKind:withIndexPath:
-(UICollectionViewLayoutAttributes _)layoutAttributesForItemAtIndexPath:(NSIndexPath _)indexPath
返回对应于indexPath的位置的cell的布局属性
-(UICollectionViewLayoutAttributes _)layoutAttributesForSupplementaryViewOfKind:(NSString _)kind atIndexPath:(NSIndexPath *)indexPath
返回对应于indexPath的位置的追加视图的布局属性,如果没有追加视图可不重载
-(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString_)decorationViewKind atIndexPath:(NSIndexPath _)indexPath
返回对应于indexPath的位置的装饰视图的布局属性,如果没有装饰视图可不重载
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
当边界发生改变时,是否应该刷新布局。如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息。
另外需要了解的是,在初始化一个UICollectionViewLayout实例后,会有一系列准备方法被自动调用,以保证layout实例的正确。
首先,-(void)prepareLayout将被调用,默认下该方法什么没做,但是在自己的子类实现中,一般在该方法中设定一些必要的layout的结构和初始需要的参数等。
之后,-(CGSize) collectionViewContentSize将被调用,以确定collection应该占据的尺寸。注意这里的尺寸不是指可视部分的尺寸,而应该是所有内容所占的尺寸。collectionView的本质是一个scrollView,因此需要这个尺寸来配置滚动行为。
接下来-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect被调用,这个没什么值得多说的。初始的layout的外观将由该方法返回的UICollectionViewLayoutAttributes来决定。
另外,在需要更新layout时,需要给当前layout发送 -invalidateLayout,该消息会立即返回,并且预约在下一个loop的时候刷新当前layout,这一点和UIView的setNeedsLayout方法十分类似。在-invalidateLayout后的下一个collectionView的刷新loop中,又会从prepareLayout开始,依次再调用-collectionViewContentSize和-layoutAttributesForElementsInRect来生成更新后的布局。
通过自定义UICollectionViewLayout实现的效果:
部分代码:
**
* 第一个方法
*
* @return 可见区域的内容尺寸
*/
-(CGSize)collectionViewContentSize{
return [super collectionViewContentSize];
}
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
CGRect visibleRect = CGRectMake(self.collectionView.contentOffset.x, 0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *visibleItemArray = [super layoutAttributesForElementsInRect:visibleRect];
for (UICollectionViewLayoutAttributes *attributes in visibleItemArray) {
CGFloat leftMargin = attributes.center.x - self.collectionView.contentOffset.x;
CGFloat halfCenterX = self.collectionView.frame.size.width / 2;
CGFloat absOffset = fabs(halfCenterX - leftMargin);
CGFloat scale = 1 - absOffset / halfCenterX;
attributes.transform3D = CATransform3DMakeScale(1 + scale * XFMinZoomScale, 1 + scale * XFMinZoomScale, 1);
if (self.needAlpha) {
if (scale < 0.6) {
attributes.alpha = 0.6;
}else if (scale > 0.99){
attributes.alpha = 1.0;
}else{
attributes.alpha = scale;
}
}
}
NSArray *attributesArr = [[NSArray alloc]initWithArray:visibleItemArray copyItems:YES];
return attributesArr;
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
CGPoint pInView = [self.collectionView.superview convertPoint:self.collectionView.center toView:self.collectionView];
NSIndexPath *indexPathNow = [self.collectionView indexPathForItemAtPoint:pInView];
if (indexPathNow.row == 0) {
if (newBounds.origin.x < SCREEN_WIDTH / 2) {
if (_index != indexPathNow.row) {
_index = 0;
if (self.delegate && [self.delegate respondsToSelector:@selector(collectionViewScrollToIndex:)]) {
[self.delegate collectionViewScrollToIndex:_index];
}
}
}
}else{
if (_index != indexPathNow.row) {
_index = indexPathNow.row;
if (self.delegate && [self.delegate respondsToSelector:@selector(collectionViewScrollToIndex:)]) {
[self.delegate collectionViewScrollToIndex:_index];
}
}
}
[super shouldInvalidateLayoutForBoundsChange:newBounds];
return YES;
}
-(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{
CGFloat minOffset = CGFLOAT_MAX;
CGFloat horizontalCenter = proposedContentOffset.x + self.collectionView.bounds.size.width / 2;
CGRect visibleRec = CGRectMake(proposedContentOffset.x, 0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *visibleAttributes = [super layoutAttributesForElementsInRect:visibleRec];
for (UICollectionViewLayoutAttributes *atts in visibleAttributes)
{
CGFloat itemCenterX = atts.center.x;
if (fabs(itemCenterX - horizontalCenter) <= fabs(minOffset)) {
minOffset = itemCenterX - horizontalCenter;
}
}
CGFloat centerOffsetX = proposedContentOffset.x + minOffset;
if (centerOffsetX < 0) {
centerOffsetX = 0;
}
if (centerOffsetX > self.collectionView.contentSize.width -(self.sectionInset.left + self.sectionInset.right + self.itemSize.width)) {
centerOffsetX = floor(centerOffsetX);
}
return CGPointMake(centerOffsetX, proposedContentOffset.y);
}
demo下载:download.csdn.net/detail/qq_3…
github下载地址:github.com/goingmyway1…
理论部分参考:onevcat.com/2012/08/adv… 王巍 先生