iOS開發――仿淘寶添加到購物車的動畫效果實現
來源:程序員人生 發布時間:2015-01-09 08:51:20 閱讀次數:7899次
這篇博文實在不知道該起甚么名字才能概況我的意思。。。挫語文水平
類似于淘寶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"];
}
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈