UICollectionView笔记-2

UICollectionView的结构回顾

首先回顾一下集合视图的构成,我们能看到的有三个部分:

  • 细胞
  • 补充意见追加视图(类似页眉页脚或者)
  • 装修查看装饰视图(用作背景展示)

而在表面下,由两个方面对UICollectionView进行支持。其中之一和tableView一样,即提供数据的UICollectionViewDataSource以及处理用户交互的UICollectionViewDelegate。另一方面,对于cell的样式和组织方式,由于collectionView比tableView要复杂得多,因此没有按照类似于tableView的style的方式来定义,而是专门使用了一个类来对collectionView的布局和行为进行描述,这就是UICollectionViewLayout。

这次的笔记将把重点放在UICollectionViewLayout上,因为这不仅是collectionView和tableView的最重要求的区别,也是整个UICollectionView的精髓所在。

如果对UICollectionView的基本构成要素和使用方法还不清楚的话,可以移步到我之前的一篇笔记:Session笔记——205引入集合视图中进行一些了解。


UICollectionViewLayoutAttributes

UICollectionViewLayoutAttributes是一个非常重要的类,先来看看属性列表:

  • @属性(非原子)CGRect框架
  • @属性(非原子)CGPoint中心
  • @属性(非原子)CGSize大小
  • @属性(非原子)CATransform3D的Transform3D
  • @属性(非原子)CGFloat的阿尔法
  • @属性(非原子)NSInteger的zIndex的
  • @属性(非原子,吸气= isHidden)布尔隐藏

可以看到,UICollectionViewLayoutAttributes的实例中包含了诸如边框,中心点,大小,形状,透明度,层次关系和是否隐藏等信息。和DataSource的行为十分类似,当UICollectionView在获取布局时将针对每一个indexPath的部件(包括cell,追加视图和装饰视图),向其上的UICollectionViewLayout实例询问该部件的布局信息(在这个层面上说的话,实现一个UICollectionViewLayout的时候,其实很像是zap一个delegate,之后的例子中会很明显地看出),这个布局信息,就以UICollectionViewLayoutAttributes的实例的方式给出。


自定义的UICollectionViewLayout

UICollectionViewLayout的功能为向UICollectionView提供布局信息,不仅包括cell的布局信息,也包括追加视图和装饰视图的布局信息。实现一个自定义layout的常规做法是继承UICollectionViewLayout类,然后重载下列方法:

  • - (CGSize)collectionViewContentSize

    • 返回的CollectionView的内容的尺寸
  • - (NSArray的*)layoutAttributesForElementsInRect:(CGRect)矩形

    • 返回矩形中的所有的元素的布局属性
    • 返回的是包含UICollectionViewLayoutAttributes的NSArray的
    • UICollectionViewLayoutAttributes可以是cell,追加视图或装饰视图的信息,通过不同的UICollectionViewLayoutAttributes初始化方法可以得到不同类型的UICollectionViewLayoutAttributes:

      • layoutAttributesForCellWithIndexPath:
      • layoutAttributesForSupplementaryViewOfKind:withIndexPath:
      • layoutAttributesForDecorationViewOfKind:withIndexPath:
  • - (UICollectionViewLayoutAttributes _)layoutAttributesForItemAtIndexPath:(NSIndexPath _)indexPath

    • 返回对应于indexPath的位置的细胞的布局属性
  • - (UICollectionViewLayoutAttributes _)layoutAttributesForSupplementaryViewOfKind:(的NSString _)一种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来决定。

另外,在需要更新的布局时,需要给当前布局发送-invalidateLayout,该消息会立即返回,并且预约在下一个loop的时候刷新当前layout,这一点和UIView的setNeedsLayout方法十分类似。在-invalidateLayout后的下一个collectionView的刷新loop中,又会从prepareLayout开始,依次再调用-collectionViewContentSize和-layoutAttributesForElementsInRect来生成更新后的布局。


演示

说了那么多,其实还是演示最能解决问题。苹果官方给了一个流动的布局和一个圆形布局的例子,都很经典,需要的同学可以从这里下载

LineLayout--对于个别UICollectionViewLayoutAttributes的调整

先看LineLayout,它继承了UICollectionViewFlowLayout这个Apple提供的基本的布局。它主要实现了单行布局,自动对齐到网格以及当前网格cell放大三个特性。如图:

先看LineLayout的初始化方法:

- (ID)的初始化
{
    自= [超级初始化]
    如果(个体经营){
        .itemSize = CGSizeMake(ITEM_SIZE,ITEM_SIZE);
        .scrollDirection = UICollectionViewScrollDirectionHorizo ​​ntal;
        .sectionInset = UIEdgeInsetsMake(2000.02000.0);
        .minimumLineSpacing = 50.0 ;
    }
    返回自我;
}

self.sectionInset = UIEdgeInsetsMake(200,0.0,200,0.0);确定了缩进,此处为上方和下方各缩进200个point。由于cell的size已经定义了为200x200,因此屏幕上在缩进后就只有一排item的空间了。

self.minimumLineSpacing = 50.0; 这个定义了每个项目在水平方向上的最小间距。

UICollectionViewFlowLayout是Apple为我们准备的开袋即食的现成布局,因此之前提到的几个必须重载的方法中需要我们操心的很少,即使完全不重载它们,现在也可以得到一个不错的线状一行的gridview了。而我们的LineLayout通过重载父类方法后,可以实现一些新特性,比如这里的动对齐到网格以及当前网格cell放大。

自动对齐到网格

- (CGPoint)targetContentOffsetForProposedContentOffset :( CGPoint)proposedContentOffset withScrollingVelocity :( CGPoint)速度
{
    // proposedContentOffset是没有对齐到网格时本来应该停下的位置
    CGFloat的的offsetAdjustment = MAXFLOAT;
    CGFloat的的horizo ​​ntalCenter = proposedContentOffset .X +(CGRectGetWidth(个体经营.collectionView .bounds)/ 2.0);
    CGRect targetRect = CGRectMake(proposedContentOffset .x0.0self .collectionView .bounds .size .widthself .collectionView .bounds .size .height);
    NSArray中的*阵列= [超级layoutAttributesForElementsInRect:targetRect]

    //对当前屏幕中的UICollectionViewLayoutAttributes逐个与屏幕中心进行比较,找出最接近中心的一个
    对于(UICollectionViewLayoutAttributes *数组layoutAttributes){
        CGFloat的的itemHorizo ​​ntalCenter = layoutAttributes .center .X ;
        如果(ABS(itemHorizo​​ ntalCenter  -  horizo​​ ntalCenter)LT; ABS(offsetAdjustment)){
            offsetAdjustment = itemHorizo​​ ntalCenter  -  horizo​​ ntalCenter;
        }
    }
    返回CGPointMake(proposedContentOffset .X + offsetAdjustment,proposedContentOffset .Y);
}

当前项目放大

- (NSArray中的*)layoutAttributesForElementsInRect :( CGRect)矩形
{
    NSArray中的*阵列= [超级layoutAttributesForElementsInRect:RECT]
    CGRect的的visibleRect;
    的visibleRect .origin = 自我.collectionView .contentOffset ;
    的visibleRect .size = 自我.collectionView .bounds .size ;

    对于(UICollectionViewLayoutAttributes *数组属性){
        如果(CGRectIntersectsRect(属性.frame,矩形)){
            CGFloat的的距离= CGRectGetMidX(的的visibleRect) -属性.center .X ;
            CGFloat的的normalizedDistance =距离/ ACTIVE_DISTANCE;
            如果(ABS(距离)< ACTIVE_DISTANCE){
                CGFloat的的变焦= 1 + ZOOM_FACTOR *(1   - ABS(normalizedDistance));
                属性.transform3D = CATransform3DMakeScale(变焦,变焦,1.0);
                属性.zIndex = 1 ;
            }
        }
    }
    返回数组;
}

对于个别UICollectionViewLayoutAttributes进行调整,以达到满足设计需求是UICollectionView使用中的一种思路。在根据位置提供不同layout属性的时候,需要记得让-shouldInvalidateLayoutForBoundsChange:返回YES,这样当边界改变的时候,-invalidateLayout会自动被发送,才能让layout得到刷新。

CircleLayout--完全自定义的布局,添加删除的项目,以及手势识别

CircleLayout的例子稍微复杂一些,cell分布在圆周上,点击cell的话会将其从collectionView中移出,点击空白处会加入一个cell,加入和移出都有动画效果。

这放在以前的话估计够写一阵子了,而得益于UICollectionView,基本只需要100来行代码就可以搞定这一切,非常cheap。通过CircleLayout的实现,可以完整地看到自定义的layout的编写流程,非常具有学习和借鉴的意义。

CircleLayout

首先,布局准备中定义了一些之后计算所需要用到的参数。

  - (无效)prepareLayout
{ //初始化和相似,必须调用超的prepareLayout以保证初始化正确
    [超级prepareLayout]

    CGSize大小= 自我.collectionView .frame .size ;
    _cellCount = [[自我的的CollectionView] numberOfItemsInSection:0 ];
    _center = CGPointMake(大小.WIDTH / 2.0,大小.height / 2.0);
    _radius = MIN(大小.WIDTH,大小.height)/ 2.5 ;
}

其实对于一个size不变的collectionView来说,除了_cellCount之外的中心和半径的定义也可以扔到init里去做,但是显然在prepareLayout里做的话具有更大的灵活性。因为每次重新给出layout时都会调用prepareLayout,这样在以后如果有collectionView大小变化的需求时也可以自动适应变化。

然后,按照UICollectionViewLayout子类的要求,重载了所需要的方法:

//整个的的CollectionView的内容大小就是的的CollectionView的大小(没有滚动)
- (CGSize)collectionViewContentSize
{
    返回[个体经营的的CollectionView] .frame .size ;
}

//通过所在的indexPath确定位置。
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath :( NSIndexPath *)路径
{
    UICollectionViewLayoutAttributes *属性= [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:路径] //生成空白的属性对象,其中只记录了类型是细胞以及对应的位置是indexPath
    //配置属性到圆周上
    属性.size = CGSizeMake(ITEM_SIZE,ITEM_SIZE);
    属性.center = CGPointMake(_center .X + _radius * cosf(2 *路径.item * M_PI / _cellCount)_中心.Y + _radius * SINF(2 *路径.item * M_PI / _cellCount));
    返回属性;
}

//用来在一开始给出一套UICollectionViewLayoutAttributes
- (NSArray中的*)layoutAttributesForElementsInRect :( CGRect)矩形
{
    的NSMutableArray里*属性= [ NSMutableArray里的阵列]
    对(NSInteger的的I = 0 ; I< .cellCount ;我++){
        //这里利用了-layoutAttributesForItemAtIndexPath:来获取属性
        NSIndexPath * indexPath = [ NSIndexPath indexPathForItem:我切入口:0 ];
        [属性ADDOBJECT:[自我layoutAttributesForItemAtIndexPath:indexPath]];
    }    
    返回属性;
}

现在已经得到了一个圈layout。为了实现cell的添加和删除,需要为collectionView加上手势识别,这个很简单,在ViewController中:

UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer页头] initWithTarget:自我行动:@selector(handleTapGesture :)];  
[ 自我.collectionView addGestureRecognizer:tapRecognizer]

对应的处理方法handleTapGesture:为

  - (无效)handleTapGesture:(UITapGestureRecognizer *)发送{
    如果(发送.STATE == UIGestureRecognizerStateEnded){
        CGPoint initialPinchPoint = [发送locationInView:.collectionView ]
        NSIndexPath * tappedCellPath = [ self .collectionView indexPathForItemAtPoint:initialPinchPoint] //获取点击处的单元格的indexPath
        如果(tappedCellPath!=无){ //点击处没有电池
            .cellCount = 自我.cellCount   -   1 ;
            [ 自我.collectionView performBatchUpdates:^ {
                [ 自我.collectionView deleteItemsAtIndexPaths:[ NSArray中的arrayWithObject:tappedCellPath]];
            }完成:零]
        } {其他
            .cellCount = 自我.cellCount + 1 ;
            [ 自我.collectionView performBatchUpdates:^ {
                [ self .collectionView insertItemsAtIndexPaths:[ NSArray的arrayWithObject:[ NSIndexPath indexPathForItem:0切入口:0 ]]];
            }完成:零]
        }
    }
}

performBatchUpdates:完成:再次展示了block的强大的一面..这个方法可以用来对collectionView中的元素进行批量的插入,删除,移动等操作,同时将触发collectionView所对应的layout的对应的动画。相应的动画由layout中的下列四个方法来定义:

  • initialLayoutAttributesForAppearingItemAtIndexPath:
  • initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath:
  • finalLayoutAttributesForDisappearingItemAtIndexPath:
  • finalLayoutAttributesForDisappearingDecorationElementOfKind:atIndexPath:

更正:正式版中API发生了变化(而且不止一次变化initialLayoutAttributesForInsertedItemAtIndexPath:在正式版中已经被废除。现在在insert或者delete之前,prepareForCollectionViewUpdates:会被调用,可以使用这个方法来完成添加/删除的布局。关于更多这方面的内容以及新的示例demo,可以参看这篇博文(需要翻墙)。新的示例demo在Github上也有,链接

在CircleLayout中,实现了细胞的动画。

//插入前,电池在圆心位置,全透​​明
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForInsertedItemAtIndexPath :( NSIndexPath *)itemIndexPath
{
    UICollectionViewLayoutAttributes *属性= [自我layoutAttributesForItemAtIndexPath:itemIndexPath]
    属性阿尔法 = 0.0 ;
    属性.center = CGPointMake(_center .X,_center .Y);
    返回属性;
}

//删除时,细胞在圆心位置,全透​​明,且只有原来的1/10大
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDeletedItemAtIndexPath :( NSIndexPath *)itemIndexPath
{
    UICollectionViewLayoutAttributes *属性= [自我layoutAttributesForItemAtIndexPath:itemIndexPath]
    属性阿尔法 = 0.0 ;
    属性.center = CGPointMake(_center .X,_center .Y);
    属性.transform3D = CATransform3DMakeScale(0.10.11.0);
    返回属性;
}

在插入或删除时,将分别以插入前和删除后的attributes和普通状态下的attributes为基准,进行UIView的动画过渡。而这一切并没有很多代码要写,几乎是free的,感谢苹果…


布局之间的切换

有时候可能需要不同的布局,Apple也提供了方便的布局间切换的方法。直接更改collectionView的collectionViewLayout属性可以立即切换布局。而如果通过setCollectionViewLayout:animated:,则可以在切换布局的同时,使用动画来过渡。对于每一个cell,都将有对应的UIView动画进行对应,又是一个接近free的特性。

对于我自己来说,UICollectionView可能是我转向的iOS 6 SDK的最具有吸引力的特性之一,因为UIKit团队的努力和CoreAnimation的成熟,使得创建一个漂亮优雅的UI变的越来越简单了。可以断言说UICollectionView在今后的iOS开发中,一定会成为和UITableView一样的强大和最常用的类之一。在iOS 6还未正式上市前,先对其特性进行一些学习,以期尽快能使用新特性来简化开发流程,可以说是非常值得的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值