UICollectionView

 

前言

上一章節我講完了縱向瀑布流的佈局,有朋友私信我問橫向怎麼作,其實橫向就是你在X軸擴充內容,本來是動態計算高度的,如今變成動態計算寬度,本來是記錄列長度的數組,如今用來記錄行長度。基本的原理都差很少,你們能夠多多本身摸索一下。數組

橫向瀑布流app

那麼本章節,咱們的主要重心就放在佈局的變式上面,經過各類有趣的佈局方式,來得到咱們想要的UI效果。佈局

本章節一共進行以下幾個功能的書寫:動畫

1.page懸停。spa

2.橫向佈局及動畫效果。code

3.增、刪Item及其動畫效果。orm

預備工做:事件

咱們首先要寫好最基本的UICollectionView的構建代碼,這些都在本系列的第一章節有,就再也不贅述。而後咱們須要寫的是橫向佈局的佈局代碼,還記得代碼的核心寫在哪一個方法嗎?ci

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;

基本佈局的核心代碼:rem

-(void)prepareLayout
{
    [super prepareLayout];
    //爲了橫向佈局,這裏咱們將列數等於數據源個數
    self.columnCount = [self.collectionView numberOfItemsInSection:0];
    self.columnSpace = 20;//列之間的間距加寬10
    self.rowSpace = 10;
    //因爲咱們須要從第0個Item到最後一個,要他們和UICollectionView的中心點重合完成Page懸停效果,因此咱們作適當改動
    self.sectionInsets = UIEdgeInsetsMake(5.0f, self.collectionView.bounds.size.width*0.35, 5.0f, self.collectionView.bounds.size.width*0.35);;

    [self.columnYArray removeAllObjects];
    for (NSInteger index = 0; index < self.columnCount; index++) {
        [self.columnYArray addObject:@(self.sectionInsets.top)];
    }
    //咱們假定數據源只有一組。
    //固然也能夠有多組,這樣的話咱們只要用嵌套循環就能夠遍歷全部的Item了。
    [self.attributesArray removeAllObjects];
    for (NSInteger index = 0; index<[self.collectionView numberOfItemsInSection:0]; index++) {

        UICollectionViewLayoutAttributes * attributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];

        [self.attributesArray addObject:attributes];
    }
}

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes * attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    CGFloat width = self.collectionView.bounds.size.width;
    CGFloat height = self.collectionView.bounds.size.height;

    CGFloat w = width*0.3;
    CGFloat h = height*0.2;

    CGFloat x = self.sectionInsets.left + (self.columnSpace + w)*indexPath.item;
    CGFloat y = height*0.4;

    attributes.frame = CGRectMake(x, y, w, h);

    //這裏咱們增長了contentX來記錄最長X軸距離,砍掉循環查詢
    self.contentX = attributes.frame.origin.x + attributes.frame.size.width;

    return attributes;
}
//因爲咱們變成了橫向佈局,因此咱們須要改變collectionView的滑動範圍
-(CGSize)collectionViewContentSize
{
    return CGSizeMake(self.contentX + self.sectionInsets.right, 0);
}

至此,咱們的基本效果就出來了:

基本效果

1.page懸停。

要完成page懸停,咱們須要計算當前的x軸偏移量與最近的中心點的差值,而後讓UICollectionView加上這段偏移量。

而咱們的UICollectionViewLayout提供了一個方法- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;來告訴咱們當前UICollectionView將要滑到的位置和方向。因而利用這一點,咱們的代碼以下:

-(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
    CGFloat midCenterX = self.collectionView.center.x;
    CGFloat cardWidth = self.collectionView.bounds.size.width*0.3;

    CGFloat realMidX = proposedContentOffset.x + midCenterX;

    //這裏咱們用滑動內容的中心點對每一個完整的Item求餘,得到整數Item之外的偏移量
    CGFloat more = fmodf(realMidX-self.sectionInsets.left, cardWidth+self.columnSpace);
    //上一行獲取的偏移量對Item中心點的間距,也就是咱們的偏移量須要再增長的偏移量。
     //返回這個通過計算的偏移量,系統會幫咱們無痕的完整偏移。
    return CGPointMake(proposedContentOffset.x-(more-cardWidth/2.0), 0);    
}

效果以下(注意看底部的滑動塊):

Page懸停

2.橫向佈局及動畫效果。

在預備工做中咱們完成了橫向佈局的書寫,那麼咱們須要對這種死板的佈局加上一點動畫效果。

經過與中心點距離的比例,咱們改變Item的scale和alpha,來完成一個越靠近中心點,透明度越低越大,反之越高越小的佈局。這裏用到的關鍵就是UICollectionViewLayoutAttributes的transform和alpha屬性。

咱們對核心方法作一點改變:

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes * attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    CGFloat width = self.collectionView.bounds.size.width;
    CGFloat height = self.collectionView.bounds.size.height;

    CGFloat w = width*0.3;
    CGFloat h = height*0.2;

    CGFloat x = self.sectionInsets.left + (self.columnSpace + w)*indexPath.item;
    CGFloat y = height*0.4;

    attributes.frame = CGRectMake(x, y, w, h);

    //這裏咱們增長了contentX來記錄最長X軸距離,砍掉循環查詢
    self.contentX = attributes.frame.origin.x + attributes.frame.size.width;

    //獲取滑動內容實時顯示尺寸的中心點
    CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
    //獲取當前Item的中心距滑動內容實時顯示尺寸的中心點的差值並完成比例計算
    CGFloat delta = ABS(attributes.center.x - centerX);
    CGFloat scale = 1.0 - delta / self.collectionView.frame.size.width;
    //經過比例,來進行2D變形和透明度變化。
    attributes.transform = CGAffineTransformMakeScale(scale, scale);
    attributes.alpha = scale;

    return attributes;
}

從新運行後,獲得的效果如圖:

橫向動效

3.增、刪Item及其動畫效果。

有時候咱們須要再UI上經過交互的形式添加新數據或者刪除已有的數據,並且須要配備相應的動畫效果,那麼咱們須要用到以下的代碼:

首先咱們須要完成增和刪的操做(這些操做在UICollectionView的點擊事件中完成):

-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    __block ViewController * weakself = self;
    if (indexPath.row%2) {
        //奇數Item的點擊咱們刪除數據源尾部,而且調用CollectionView的deleteItemsAtIndexPaths:方法刪除Item
        [self.cardCollectionView performBatchUpdates:^{
            NSMutableArray * theArray = [NSMutableArray arrayWithArray:self.dataArray];
            BJCardModel * model = [BJCardModel new];
            model.indexStr = [NSString stringWithFormat:@"%ld",theArray.count];
            [theArray addObject:model];
            weakself.dataArray = [NSArray arrayWithArray:theArray];

            NSIndexPath * indexpath = [NSIndexPath indexPathForItem:self.dataArray.count-1 inSection:0];
            [weakself.cardCollectionView insertItemsAtIndexPaths:@[indexpath]];

        } completion:nil];
    }else{
        //偶數Item的點擊咱們在數據源尾部增長新數據,而且調用CollectionView的insertItemsAtIndexPaths:方法新增Item
        [self.cardCollectionView performBatchUpdates:^{
            NSMutableArray * theArray = [NSMutableArray arrayWithArray:self.dataArray];
            [theArray removeLastObject];
            weakself.dataArray = [NSArray arrayWithArray:theArray];

            NSIndexPath * indexpath = [NSIndexPath indexPathForItem:self.dataArray.count inSection:0];
            [weakself.cardCollectionView deleteItemsAtIndexPaths:@[indexpath]];

        } completion:nil];
    }

}

而後咱們再自定義的Layout中,新增兩個數組,來記錄新增和刪除的updateItem,由於在上面咱們能夠發現

[weakself.cardCollectionView insertItemsAtIndexPaths:@[indexpath]];
[weakself.cardCollectionView deleteItemsAtIndexPaths:@[indexpath]];

改變都是以數組的形式進行改變,儘管咱們一次只改變一個。在多數據更改的狀況下,咱們須要用數組來記錄,並在動畫執行時對其完成判斷:

//這個方法在即將發生改變時進行,而且提供了須要改變的Item數組
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
{
    [super prepareForCollectionViewUpdates:updateItems];

    NSLog(@"準備改變");

    UICollectionViewUpdateItem *update = updateItems[0];
    NSLog(@"%ld -- %ld",update.indexPathBeforeUpdate.section,update.indexPathBeforeUpdate.row);
    NSLog(@"%ld -- %ld",update.indexPathAfterUpdate.section,update.indexPathAfterUpdate.row);
    NSLog(@"%ld",update.updateAction);


    self.deleteIndexPaths = [NSMutableArray array];
    self.insertIndexPaths = [NSMutableArray array];

    for (UICollectionViewUpdateItem *update in updateItems)
    {
        if (update.updateAction == UICollectionUpdateActionDelete)
        {
            [self.deleteIndexPaths addObject:update.indexPathBeforeUpdate];
        }
        else if (update.updateAction == UICollectionUpdateActionInsert)
        {
            [self.insertIndexPaths addObject:update.indexPathAfterUpdate];
        }
    }
}

//這個方法在新增時進行,而且提供了須要改變的Item的IndexPath
-(UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
    NSLog(@"插入動畫 : %ld -- %ld ",itemIndexPath.section,itemIndexPath.row);

    UICollectionViewLayoutAttributes * att = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];

    if ([self.insertIndexPaths containsObject:itemIndexPath]) {
        if (!att) {
            att = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
        }

        att.alpha = 0.1f;
    }

    return att;
}

//這個方法在刪除時進行,而且提供了須要改變的Item的IndexPath
-(UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
    NSLog(@"刪除動畫 : %ld -- %ld ",itemIndexPath.section,itemIndexPath.row);

    UICollectionViewLayoutAttributes * att = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];

    if ([self.deleteIndexPaths containsObject:itemIndexPath]) {
        if (!att) {
            att = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
        }

        att.alpha = 1.0f;
        att.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(90));

    }

    return att;
}

//這個方法發生在改變完成時,咱們對數組置nil
- (void)finalizeCollectionViewUpdates
{
    [super finalizeCollectionViewUpdates];

    NSLog(@"完成改變");

    self.deleteIndexPaths = nil;
    self.insertIndexPaths = nil;
}

至此咱們的動畫效果以下:

效果

個人動畫效果比較簡陋,純粹是爲了告訴你們怎麼走通這個流程而進行的,你們能夠根據本身的須要完成一些炫酷的動效。

做者:BradleyJohnson 連接:http://www.jianshu.com/p/d2421b88ee64 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索