程序员人生 网站导航

iOS开发――仿淘宝添加到购物车的动画效果实现

栏目:综合技术时间:2015-01-09 08:51:20

这篇博文实在不知道该起甚么名字才能概况我的意思。。。挫语文水平尴尬

类似于淘宝1样,我们在写1些购物、订餐之类的app的时候,在用户选择购买或加入购物车时可以添加1个商品飞到购物车中的动画效果,以下图所示:



实现这个效果还是不算难的,但触及的问题比较多,还是挺有学习价值的。主要面对的问题有以下几点

1、cell中有button,如何取得该button,即如何知道用户点击的是哪个button。

2、坐标系的转换,这里频繁使用坐标系转换,主要缘由是这里需要触及3个视图――cell、tableView、view

3、Bezier曲线的利用。

下面我们逐一来解决这些问题。

正好这学期图形学刚刚结课,虽然没有甚么关联,不过也算温习了- -。


1、获得cell中的button

这个问题也是个老问题了,方法也非常多,比较常见的是自定义cell,然后将button作为cell的property,这样我们可以在创建cell的时候为button设tag值,根据indexPath来设便可,通过tag来辨别。这样在很多情况下也能解决问题。不过这次我们用的其实不是这类方法。

分析:每一个cell的button有自己的处理逻辑,比如,当点击收藏按钮时要将选中的FoodModel保存起来,要改变button的标题……,从MVC的原则和职责单1化的原则来看,这些写在cell以外的地方都是不适合的,而上面的动画很明显是在控制器层级的动画,也就是动画代码不能写在cell中,而是在某某Controller中的。如果只是设tag在控制器中处理是不能实现这个需求的。

既然都要处理,那就将处理逻辑分开便可。说到底这还是代理模式的利用,是类与类之间的通讯问题,用协议、块、通知都可以。具体来讲就是当点击按钮时,在cell中处理自己的逻辑,然后把其他任务交给其他类。这里我用的是通知的方法。

固然,再说第2个问题之前先顺带1提,坐标系转换,很明显是需要坐标的,我们在控制器中生成动画的时候,是需要知道点击的那个cell的某1特定位置(以后会作为动画的出发点)的坐标,所以在发送通知的时候要自带上userInfo便于在控制器中取出来。

附上这部份相干代码:

- (IBAction)tapLikedButton:(UIButton *)sender { //处理自己的逻辑 //if the food has been chosen,then remove it if ([self.likedFoods containsObject:self.foodModel.foodName]) { [self.likedFoods removeObject:self.foodModel.foodName]; [self.foodLikedButton setTitle:@"收藏" forState:UIControlStateNormal]; } else { //like the food and change the title of btn [self.likedFoods addObject:self.foodModel.foodName]; [self.foodLikedButton setTitle:@"取消收藏" forState:UIControlStateNormal]; //将动画交给其他类去处理 [[NSNotificationCenter defaultCenter] postNotificationName:LIKE_FOOD_NOTIFICATION object:nil userInfo:@{@"position" : [NSValue valueWithCGPoint:[self convertPoint:self.foodNameLabel.center toView:self.superview]]}]; } //save the foods NSString *filePath = [self filePath]; [self.likedFoods writeToFile:filePath atomically:YES]; }

2、坐标系的转换

其实在上面的代码中已用到了,还是,做1下分析:这里我们要将1个位置坐标传出去,但是传甚么位置呢?如果是Lable的位置简答的传出去,那末很明显会出现1个问题:不管你点击那个cell的按钮,动画都是从同1个出发点动身的,而且绝对不会是任何正确的出发点。由于每一个cell中Label的位置都是1样的,而我们实际需要的是这个坐标相对TableView的位置,也就是说它在父视图中的位置,所以这里要将该点坐标转换。

一样,上面gif图片中,可以看到,我们要触及的视图有,最右下角有1组图片和按钮,表示购物车,在tableView中有我们之前传过来的坐标,而我们希望让动画产生在view层级上,所以这里需要两次坐标转换,把右下角的控件集合中的按钮坐标(购物车是个按钮)和tableView中的传过来的出发点坐标都转换到self.view中,具体做法是

CGPoint endpoint = [self.view convertPoint:btnCenter fromView:carBG];

CGPoint startPoint = [self.view convertPoint:lbCenter fromView:self.tableView];
附:关于坐标转换,网上也有很多资料,本人之前的博客中也有提及:iOS开发――仿新版iBOOks书本打开与关闭动画 

有了起止点以后,剩下的就是最关键的问题――bezier曲线的使用了。

3、Bezier曲线

关于Bezier曲线,iOS已为我们封装好了生成操作,我们只需要提供控制点便可。为了更好地理解Bezier曲线,为了以后能更好的利用Bezier曲线来创造好看的效果,我们应当学习其原理与生成机制,这里只做简单1提,以后再专门学习记录。。

由于我们想产生1种类抛物线的动画,所以这里我们需要2阶Bezier曲线便可,所以要提供3个控制点,起始点和终止点都已有了,关键就是中间的控制点。在计图实验中生成Bezier时,我们用的1种思路是以直代曲,用大量短线段来表示1条曲线,每个n阶Bezier曲线(n+1个点)在生成时,总能在n个线段中依照1个比例各找出1个点,而这n个点又能生成1个n⑴阶Bezier,我们的Bezier曲线上的点就是当只有1条线段以后依照那个比例找出的那个点。

无图无真相,盗图可耻,我干脆摆上1个链接好了偷笑

Beizer曲线上点的肯定

原理是这样,我们用起来只要略微了解1点,就知道我们缺少的那个控制点就是在起止点之间,但是纵坐标要比这两点“高”很多的1个点。所以可以通过下面的公式得出1个控制点

float x = sx + (ex - sx) / 3; float y = sy + (ey - sy) * 0.5 - 400;

由于该控制点的存在,我们的曲线会从起始点向上抛起然后再落到终点处。这里x、y的算法其实不是固定的,可以自由更改,只要符合上面上的条件并且自己觉得好看就好。

利用这3个控制点就可以生成1个2阶Bezier曲线,将其作为动画的path属性便可。

4、其他方面

UIView的动画是作用在layer层级的,所以我们可以生成1个CALayer,在这个layer上添加上自己的图片,然后将动画利用到这个layer中便可。


附该部份代码:

- (void)showLikedFoodsAnimation:(NSNotification *)notification { //get the location of label in table view NSValue *value = notification.userInfo[@"position"]; CGPoint lbCenter = value.CGPointValue; //the image which will play the animation soon UIImageView *imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"cm_center_discount"]]; imageView.contentMode = UIViewContentModeScaleToFill; imageView.frame = CGRectMake(0, 0, 20, 20); imageView.hidden = YES; imageView.center = lbCenter; //the container of image view CALayer *layer = [[CALayer alloc]init]; layer.contents = imageView.layer.contents; layer.frame = imageView.frame; layer.opacity = 1; [self.view.layer addSublayer:layer]; CGPoint btnCenter = carButton.center; //动画 终点 都以sel.view为参考系 CGPoint endpoint = [self.view convertPoint:btnCenter fromView:carBG]; UIBezierPath *path = [UIBezierPath bezierPath]; //动画出发点 CGPoint startPoint = [self.view convertPoint:lbCenter fromView:self.tableView]; [path moveToPoint:startPoint]; //贝塞尔曲线控制点 float sx = startPoint.x; float sy = startPoint.y; float ex = endpoint.x; float ey = endpoint.y; float x = sx + (ex - sx) / 3; float y = sy + (ey - sy) * 0.5 - 400; CGPoint centerPoint=CGPointMake(x, y); [path addQuadCurveToPoint:endpoint controlPoint:centerPoint]; //key frame animation to show the bezier path animation CAKeyframeAnimation *animation=[CAKeyframeAnimation animationWithKeyPath:@"position"]; animation.path = path.CGPath; animation.removedOnCompletion = NO; animation.fillMode = kCAFillModeForwards; animation.duration = 0.8; animation.delegate = self; animation.autoreverses = NO; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; [layer addAnimation:animation forKey:@"buy"]; }




------分隔线----------------------------
------分隔线----------------------------

最新技术推荐