从一个看似简单的卡片动画说起
一天,产品经理过来找我,要我实现卡片的动画,就是很多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
总结
这个任务实现了一个多礼拜,走了好多弯路.虽然令人抓狂了点,不过确实学到不少,对每个控件的属性有了更深的了解.
静下心来总结下,发现了交流和思考的重要性.
- 一个任务布置下来,不要想当然地去做,要多和产品经理沟通,先了解那么做的意义和目的.
- 写代码的时候要多思考,先想想会遇到的坑,有没有更好的方法.因为一个功能会有好几种实现途径,做之前要多想,可以避免很多弯路.
posted on 2016-04-11 17:16 steven_fukua 阅读(1156) 评论(0) 编辑 收藏 举报