iOS開發筆記 - 仿京東的加入購物車動畫

EliNussbaum 7年前發布 | 15K 次閱讀 iOS開發 移動開發

請叫我死肥宅

之前APP里的加入購物車動畫是最簡單的UIView動畫(一句代碼那種),這幾天正好有時間所以就跟產品那邊確認優化了一下。雖然產品嘴上說讓我自由發揮,但我相信沒處理好肯定會讓我改,改到產品那邊滿意為止,所以我研究了一下京東的加入購物車動畫。

先看看京東的購物車動畫是怎樣的:

京東的加入購物車動畫.gif

再看看我模仿出來的效果:

我為了突出效果把動畫寫得夸張了點,實際項目中不會這么張狂。

先分析一下整個動畫的過程

當用戶點擊加入購物車按鈕時,一張商品圖片從“加入購物車按鈕”中心飛到了“購物車”按鈕中心。其中:

  • 飛行的路徑是拋物線的
  • 飛行過程中圖片越來越小
  • 飛行結束后商品數量label顫抖了兩下

如何定義這個動畫?

  1. 這個動畫是購物車相關的,所以它的類名應該是 ShoppingCartTool 或者 ShoppingCartManagement 之類的。
  2. 這個動畫效果至少需要3個參數:商品圖片、起點和終點。
  3. 我們需要在動畫結束時進行相應處理,所以還需要一個動畫結束時回調的block。
  4. 類方法比對象方法使用更加方便。

基于這四點,方法定義如下:

#import <Foundation/Foundation.h>

import <UIKit/UIKit.h>

@interface ShoppingCartTool : NSObject

/** 加入購物車的動畫效果

@param goodsImage 商品圖片 @param startPoint 動畫起點 @param endPoint 動畫終點 @param completion 動畫執行完成后的回調 */

  • (void)addToShoppingCartWithGoodsImage:(UIImage *)goodsImage
                           startPoint:(CGPoint)startPoint
                             endPoint:(CGPoint)endPoint
                           completion:(void (^)(BOOL finished))completion;

@end</code></pre>

動畫實現詳細講解

先把完整代碼貼出來:

+ (void)addToShoppingCartWithGoodsImage:(UIImage *)goodsImage startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint completion:(void (^)(BOOL))completion{

//------- 創建shapeLayer -------//
CAShapeLayer *animationLayer = [[CAShapeLayer alloc] init];
animationLayer.frame = CGRectMake(startPoint.x - 20, startPoint.y - 20, 40, 40);
animationLayer.contents = (id)goodsImage.CGImage;

// 獲取window的最頂層視圖控制器
UIViewController *rootVC = [[UIApplication sharedApplication].delegate window].rootViewController;
UIViewController *parentVC = rootVC;
while ((parentVC = rootVC.presentedViewController) != nil ) {
    rootVC = parentVC;
}
while ([rootVC isKindOfClass:[UINavigationController class]]) {
    rootVC = [(UINavigationController *)rootVC topViewController];
}

// 添加layer到頂層視圖控制器上
[rootVC.view.layer addSublayer:animationLayer];


//------- 創建移動軌跡 -------//
UIBezierPath *movePath = [UIBezierPath bezierPath];
[movePath moveToPoint:startPoint];
[movePath addQuadCurveToPoint:endPoint controlPoint:CGPointMake(200,100)];
// 軌跡動畫
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
CGFloat durationTime = 1; // 動畫時間1秒
pathAnimation.duration = durationTime;
pathAnimation.removedOnCompletion = NO;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.path = movePath.CGPath;


//------- 創建縮小動畫 -------//
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
scaleAnimation.fromValue = [NSNumber numberWithFloat:1.0];
scaleAnimation.toValue = [NSNumber numberWithFloat:0.5];
scaleAnimation.duration = 1.0;
scaleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
scaleAnimation.removedOnCompletion = NO;
scaleAnimation.fillMode = kCAFillModeForwards;


// 添加軌跡動畫
[animationLayer addAnimation:pathAnimation forKey:nil];
// 添加縮小動畫
[animationLayer addAnimation:scaleAnimation forKey:nil];


//------- 動畫結束后執行 -------//
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(durationTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [animationLayer removeFromSuperlayer];
    completion(YES);
});

}</code></pre>

看到這種拋物線的動畫我就條件反射的想到 CAShapeLayer+UIBezierPath

展示:由layer決定

layer可以裝圖片

animationLayer.contents = (id)goodsImage.CGImage;

軌跡:由貝塞爾曲線決定

貝塞爾曲線決定了移動軌跡

pathAnimation.path = movePath.CGPath;

動畫:由animation決定

動畫有很多,按需添加

// 添加軌跡動畫
[animationLayer addAnimation:pathAnimation forKey:nil];
// 添加縮小動畫
[animationLayer addAnimation:scaleAnimation forKey:nil];

難點

顫抖效果如何實現?

快速縮放兩次不就是顫抖效果了嗎?:flushed:

/* 加入購物車按鈕點擊 /

  • (void)addButtonClicked:(UIButton *)sender { [ShoppingCartTool addToShoppingCartWithGoodsImage:[UIImage imageNamed:@"heheda"] startPoint:self.addButton.center endPoint:self.shoppingCartButton.center completion:^(BOOL finished) {

      NSLog(@"動畫結束了");
    
      //------- 顫抖吧 -------//
      CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
      scaleAnimation.fromValue = [NSNumber numberWithFloat:1.0];
      scaleAnimation.toValue = [NSNumber numberWithFloat:0.7];
      scaleAnimation.duration = 0.1;
      scaleAnimation.repeatCount = 2; // 顫抖兩次
      scaleAnimation.autoreverses = YES;
      scaleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
      [self.goodsNumLabel.layer addAnimation:scaleAnimation forKey:nil];
    

    }]; }</code></pre>

    就這樣成功顫抖了。

    細節:

    為什么我不直接將動畫layer加到window上?

    如果直接加在window上,不管是keyWindow還是AppDelegate的window,當動畫進行中的時候切換視圖控制器,視圖控制器切換了,但是動畫并不會跟著切換。來張動圖你就明白了:

    動畫進行中切換頁面.gif

    這顯然不是我們想要的結果,所以我把動畫layer添加到的最頂層視圖控制器上。

    精髓

    通過延遲加載來和動畫結束時間相對應:

    //------- 動畫結束后執行 -------//
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(durationTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      [animationLayer removeFromSuperlayer];
      completion(YES);
    });

    總結:

    封裝小功能時不僅僅要完成功能,細節是不能忽視的。

    補充說明:

    實際開發中很可能需要將frame坐標轉換為屏幕坐標,這個百度一下就可以找到答案。

     

 本文由用戶 EliNussbaum 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!