美文网首页ios开发整理O~1iOS Developer
从零开始UICollectionView(4)--横向动画效果布

从零开始UICollectionView(4)--横向动画效果布

作者: BradleyJohnson | 来源:发表于2016-11-03 11:59 被阅读1757次

    前言

    上一章节我讲完了纵向瀑布流的布局,有朋友私信我问横向怎么做,其实横向就是你在X轴扩充内容,原本是动态计算高度的,现在变成动态计算宽度,原本是记录列长度的数组,现在用来记录行长度。基本的原理都差不多,大家可以多多自己摸索一下。

    横向瀑布流
    那么本章节,我们的主要重心就放在布局的变式上面,通过各种有趣的布局方式,来获得我们想要的UI效果。
    本章节一共进行如下几个功能的书写:
    1.page悬停。
    2.横向布局及动画效果。
    3.增、删Item及其动画效果。

    预备工作:

    我们首先要写好最基本的UICollectionView的构建代码,这些都在本系列的第一章节有,就不再赘述。然后我们需要写的是横向布局的布局代码,还记得代码的核心写在哪个方法吗?

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

    基本布局的核心代码:

    -(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;
    }
    

    至此我们的动画效果如下:

    效果

    我的动画效果比较简陋,纯粹是为了告诉大家怎么走通这个流程而进行的,大家可以根据自己的需要完成一些炫酷的动效。


    下节预告:我会完成一些由上述章节内容组合成的有趣的UICollectionView布局,到时作为UICollectionView系列的终结篇,将奉上所有Demo。谢谢各位。


    注:若你觉得这文章确实帮到你了,或是支持一下原创技术文章,请为我点个赞。大爷若是还能打赏打赏,那就更好不过了。

    相关文章

      网友评论

      • f477f089aa7c:请问,前言里面的效果怎么做的,按照文章的内容改,没改出来~~:pensive:
      • 山野朗朗:列间距,怎么调的
      • 梓华:支持下。增加和删除时的处理逻辑 给了我提示。
        参考那个objcio里的有个文章。拿增加来说。initialLayoutAttributesForAppearingItemAtIndexPath里面的逻辑都有处理deleteIndexPaths和insertIndexPaths。。
      • C丶丶H:谢谢分享,请问下这种情况下如何动态设置圆角?
        C丶丶H:@七秒记忆的鱼儿 好的,谢谢你
        七秒记忆的鱼儿:layer能帮助到你
      • Mr卿:顶顶顶
        BradleyJohnson:@薇薇卿 谢谢 :smile:

      本文标题:从零开始UICollectionView(4)--横向动画效果布

      本文链接:https://www.haomeiwen.com/subject/yovbuttx.html