一天,產品經理過來找我,要我實現卡片的動畫,就是不少view疊在一塊兒,能夠上拉讓view移走,下拉讓view出現.看起來很簡單的動畫,沒有多作深刻的思考,直接開工了,而後......一個禮拜的恐怖生涯來臨了html
我以爲這個動畫很easy啊,而後產品經理說了一次性只會疊加幾張卡片,因此不須要考慮卡片的複用,感受容易爆了.只要把把view疊在一塊兒,而後給每一個view添加手勢就ok了.git
//添加每一個view,並給每一個view添加手勢 -(NSMutableArray *)cardViewArray { if (!_cardViewArray) { _cardViewArray = [[NSMutableArray alloc]initWithCapacity:CARD_SUM]; for (NSInteger i = 0; i < CARD_SUM; i++) { PopularCardView *cardView = [[PopularCardView alloc]initWithFrame:appearFrame]; UIPanGestureRecognizer *movePan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(movePan:)]; [cardView addGestureRecognizer:movePan]; [_cardViewArray addObject:cardView]; } } return _cardViewArray; } //手勢狀態 typedef NS_ENUM(NSInteger,ScrollerViewState) { ScrollerViewStateDefault, //默認狀態 ScrollerViewStateHeaderNoticeShow, //頂部view出現 ScrollerViewStateFooterNoticeShow, //尾部view出現 ScrollerViewStateDragUp, //上拉狀態 ScrollerViewStateDragDown, //下拉狀態 }; //手勢操做 -(void)movePan:(UIPanGestureRecognizer *) gesture { CGPoint translationPoint = [gesture translationInView:self]; CGPoint viewOrigin = gesture.view.origin; //只能上下移動 viewOrigin.y += translationPoint.y; [gesture setTranslation:CGPointZero inView:self]; switch (scrollerState) { case ScrollerViewStateDefault: //在default裏作判斷是什麼操做 if(viewOrigin.y < self.origin.y){ //上拉 if (currentIndex >= _cardViewDataArray.count - 1){ scrollerState = ScrollerViewStateFooterNoticeShow; } else{ scrollerState = ScrollerViewStateDragUp; } } else{ //下滑 if (currentIndex == 0){ scrollerState = ScrollerViewStateHeaderNoticeShow; } else{ scrollerState = ScrollerViewStateDragDown; } } break; //具體每一個狀態所作的操做就不寫了,由於最後證實是無用功,挺繁瑣的. case ScrollerViewStateFooterNoticeShow: break; case ScrollerViewStateHeaderNoticeShow: break; case ScrollerViewStateDragUp: break; case ScrollerViewStateDragDown: break; default: break; } }
功能實現好了,興沖沖地交差了,而後測試MM跟我說,要適配!
行,找美工MM要圖.
美工MM說由於每一個view上的控件比較多,不能適配每一個機型,能不能不要把長度寫死,能夠下滑不就好了.
github
你逗我?不過難不倒我,給每一個view繼承UIScrollView不就好了.app
@interface PopularCardView : UIScrollView
想法是美好的,現實是骨幹的.繼承UIScrollView後,手勢失效了.My God,手勢和UIScrollView衝突了.
佈局
根本不須要手勢啊,直接用UIScrollView的代理就好了.改起來也不是很繁瑣.把手勢中的代碼修改修改加到協議裏就好了.測試
-(void)scrollViewDidScroll:(UIScrollView *)scrollView { contentOffsetY = scrollView.contentOffset.y; if (scrollerState == ScrollerViewStateDefault) { if (contentOffsetY > cardViewHeight) { //上拉 scrollerState = (currentIndex >= _cardViewDataArray.count - 1)?ScrollerViewStateFooterNoticeShow:ScrollerViewStateDragUp; } if (contentOffsetY < 0) { scrollerState = (currentIndex == 0)? //下移 ScrollerViewStateHeaderNoticeShow:ScrollerViewStateDragDown; } } } -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ //作相應結束的動畫 }
搞定了,4,5的機型都好了,長吁一口氣.而後測試MM又出現了,說6機型不能滑動.
what happened?
研究了一下,Oh,==原來view的長度和contentSize的長度同樣,是不會調用scrollViewDidScroll的.==
什麼,難道每一個機型要用不一樣實現?實在接受不了.
忽然靈光一閃,把全部的卡片當成一個view,我彷佛實現了整個UIScrollView的功能?要是再加個複用,若是不是重疊的,我彷佛實現了一個UICollectionView?
動畫
UICollectionView是UITableView的增強版,由於UICollectionViewLayout,能夠自定義佈局,功能真是太強大了,它的基礎特性就很少介紹了,還不是很熟悉的小夥伴們能夠看UICollectionView由淺入深ui
*實現思路
@interface CardViewLayout : UICollectionViewLayout
繼承UICollectionViewLayout實現自定義佈局代理
/** * 該方法返回CollectionView的ContentSize的大小 */ -(CGSize)collectionViewContentSize { return CGSizeMake(SCREEN_WIDTH, _itemSize.height*_numberOfCellsInSection+_footerSize.height); }
ContentSize大小就是每一個Card的高度之和加上尾視圖的高度code
/** * 該方法爲每一個Cell綁定一個Layout屬性~ */ - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *array = [NSMutableArray array]; //add cells for (int i = 0; i < _numberOfCellsInSection; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0]; UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; [array addObject:attributes]; } NSIndexPath *headerIndexPath = [NSIndexPath indexPathForItem:0 inSection:0]; UICollectionViewLayoutAttributes *headerAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:headerIndexPath]; [array addObject:headerAttributes]; NSIndexPath *footerIndexPath = [NSIndexPath indexPathForItem:_numberOfCellsInSection - 1 inSection:0]; UICollectionViewLayoutAttributes *footerAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:footerIndexPath]; [array addObject:footerAttributes]; return array; }
給每一個cell和頭視圖尾視圖添加Layout屬性
/** * 爲每一個Cell設置attribute */ - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ //獲取當前Cell的attributes UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; //獲取滑動的位移 CGFloat contentOffsetY = self.collectionView.contentOffset.y; //獲取當前cell的Index NSInteger currentIndex = contentOffsetY/_itemSize.height; //下面的代碼比較繁瑣,我介紹下思路,有興趣的小夥伴們能夠去github上下載交流 ..... return attributes; }
//當邊界發生改變時,是否應該刷新佈局。若是YES則在邊界變化(通常是scroll到其餘地方)時,將從新計算須要的佈局信息。必須設置爲YES - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return YES; } //修正Cell的位置,當cell移動超過必定比例就飛走 - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{ NSInteger currentIndex = proposedContentOffset.y/_itemSize.height; if (proposedContentOffset.y - currentIndex*_itemSize.height > Animation_Scale*_itemSize.height) { proposedContentOffset.y = (currentIndex + 1)*_itemSize.height; } return proposedContentOffset; } /** * 爲每一個Header和footer設置attribute */ - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind withIndexPath:indexPath]; if (elementKind == UICollectionElementKindSectionHeader) { attributes.frame = CGRectMake(0,-_headerSize.height,_headerSize.width,_headerSize.height); }else if (elementKind == UICollectionElementKindSectionFooter) { attributes.frame = CGRectMake(0, self.collectionView.contentSize.height - _headerSize.height,_headerSize.width,_headerSize.height); } return attributes; }
In的動態:每一個動態正好是屏幕大小,評論點贊後會超過屏幕.也就是說每一個動態的長度不同.
我寫了個小Demo.大體實現了In的動畫效果.
探探的卡片好友推薦:實現層疊的卡片動畫,而且可以移動圖片.可是移走的圖片不能在移回來.
探探直接用view層疊,添加手勢完成動畫.我寫了個小Demo,實現了層疊的動畫,可以上拉和下移.由於是用collectionView實現的,和探探的不大同樣.
兩個小Demo的layout簡單封裝了下,能夠直接改改拿去用,主要仍是和小夥伴們一塊兒研究交流啦,看看還有沒有更好的實現方式.
github地址:https://github.com/stevenxiaoyang/card
這個任務實現了一個多禮拜,走了好多彎路.雖然使人抓狂了點,不過確實學到很多,對每一個控件的屬性有了更深的瞭解.
靜下心來總結下,發現了交流和思考的重要性.