上一章節我講完了縱向瀑布流的佈局,有朋友私信我問橫向怎麼作,其實橫向就是你在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 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。