从一个看似简单的卡片动画说起

来源:互联网 发布:炫踪网络李化亮 编辑:程序博客网 时间:2024/05/16 05:46

一天,产品经理过来找我,要我实现卡片的动画,就是很多view叠在一起,可以上拉让view移走,下拉让view出现.看起来很简单的动画,没有多做深入的思考,直接开工了,然后......一个礼拜的恐怖生涯来临了

添加手势实现

happy我觉得这个动画很easy啊,然后产品经理说了一次性只会叠加几张卡片,所以不需要考虑卡片的复用,感觉容易爆了.只要把把view叠在一起,然后给每个view添加手势就ok了.

//添加每个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上的控件比较多,不能适配每个机型,能不能不要把长度写死,可以下滑不就行了.nothappy你逗我?不过难不倒我,给每个view继承UIScrollView不就行了.

UIScrollView实现

@interface PopularCardView : UIScrollView
想法是美好的,现实是骨干的.继承UIScrollView后,手势失效了.My God,手势和UIScrollView冲突了.
sad根本不需要手势啊,直接用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?
angry

UICollectionView实现

UICollectionView是UITableView的加强版,因为UICollectionViewLayout,可以自定义布局,功能真是太强大了,它的基础特性就不多介绍了,还不是很熟悉的小伙伴们可以看UICollectionView由浅入深

*实现思路
@interface CardViewLayout : UICollectionViewLayout 继承UICollectionViewLayout实现自定义布局

/** * 该方法返回CollectionView的ContentSize的大小 */-(CGSize)collectionViewContentSize {    return CGSizeMake(SCREEN_WIDTH,  _itemSize.height*_numberOfCellsInSection+_footerSize.height);}

ContentSize大小就是每个Card的高度之和加上尾视图的高度 

/** * 该方法为每个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/cardcardAnimation

总结

这个任务实现了一个多礼拜,走了好多弯路.虽然令人抓狂了点,不过确实学到不少,对每个控件的属性有了更深的了解.
静下心来总结下,发现了交流和思考的重要性. 

  • 一个任务布置下来,不要想当然地去做,要多和产品经理沟通,先了解那么做的意义和目的. 
  • 写代码的时候要多思考,先想想会遇到的坑,有没有更好的方法.因为一个功能会有好几种实现途径,做之前要多想,可以避免很多弯路.
0 0
原创粉丝点击