上一章节我讲完了纵向瀑布流的布局,有朋友私信我问横向怎么作,其实横向就是你在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 來源:简书 著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。