从一个看似简单的卡片动画说起
来源:互联网 发布:炫踪网络李化亮 编辑:程序博客网 时间:2024/05/16 05:46
一天,产品经理过来找我,要我实现卡片的动画,就是很多view叠在一起,可以上拉让view移走,下拉让view出现.看起来很简单的动画,没有多做深入的思考,直接开工了,然后......一个礼拜的恐怖生涯来临了
添加手势实现
我觉得这个动画很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上的控件比较多,不能适配每个机型,能不能不要把长度写死,可以下滑不就行了.你逗我?不过难不倒我,给每个view继承UIScrollView不就行了.
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由浅入深
*实现思路 @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/card
总结
这个任务实现了一个多礼拜,走了好多弯路.虽然令人抓狂了点,不过确实学到不少,对每个控件的属性有了更深的了解.
静下心来总结下,发现了交流和思考的重要性.
- 一个任务布置下来,不要想当然地去做,要多和产品经理沟通,先了解那么做的意义和目的.
- 写代码的时候要多思考,先想想会遇到的坑,有没有更好的方法.因为一个功能会有好几种实现途径,做之前要多想,可以避免很多弯路.
- 从一个看似简单的卡片动画说起
- 视错觉结合UI:从一个看似简单的自定义控件说起
- 一个看似简单的数组地址问题
- 一个看似简单的数字交换问题
- 看似简单的scanf
- 看似简单的TextView
- 从一个Python程序的性能说起
- Linux的起源:从一个故事说起
- 从头学Qt Quick(2)-- QML语法从一个简单的例子说起
- Windows编程入门——从一个简单的窗口说起
- 从一个“Bug”说起
- 从一个台湾人说起
- 从一个请求说起
- 看似简单的小问题
- 看似简单的if-else
- 看似简单的if语句
- 看似简单的小麻烦
- Ajax个人开发心得(一)先从一个最简单的ajax功能模块说起,Ajax技术其实很简单
- 使用SecureCRT进行端口转发
- zoj2326
- 神奇的把一个文件夹变身成网站初稿python程序
- QGroundControl Developers Guide——Mission
- STL 笔记: deque vector string
- 从一个看似简单的卡片动画说起
- 使用Servlet3.0上传文件
- 视音频基础知识
- JRebel最新破解版及使用方法
- es ik
- Openstack: Single node Installation and External Network Accessing Configuration Guide
- 数据结构和算法03-线性表02
- OJ——1.10编程基础之简单排序
- python扩展实现方法--python与c混和编程