從一個看似簡單的卡片動畫提及

一天,產品經理過來找我,要我實現卡片的動畫,就是不少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

UIScrollView實現

@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實現

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;
}
  • 一開始每一個cell的位置是同樣的,這樣就能重疊在一塊兒了.
  • 向下滑動時,indexPath.row比當前cell大的加上contentOffsetY,就會隨着當前cell一塊兒下滑了
  • indexPath.row比當前cell小的就不用管了,因此它就會在它原本在的位置
  • 這樣滑動到了最後一個cell時,其實cell就一個個排列下來了
    可能比較難說,因此你們仍是看代碼來的實在
//當邊界發生改變時,是否應該刷新佈局。若是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

總結

這個任務實現了一個多禮拜,走了好多彎路.雖然使人抓狂了點,不過確實學到很多,對每一個控件的屬性有了更深的瞭解.
靜下心來總結下,發現了交流和思考的重要性.

  • 一個任務佈置下來,不要想固然地去作,要多和產品經理溝通,先了解那麼作的意義和目的.
  • 寫代碼的時候要多思考,先想一想會遇到的坑,有沒有更好的方法.由於一個功能會有好幾種實現途徑,作以前要多想,能夠避免不少彎路.
相關文章
相關標籤/搜索