下面給大家說一個將敬業(yè)福加入購物車的方法
在項目過程中所涉及到的一個需求,效果和天貓京東等的那種控制器下沉,然后具體商品型號類型等的展示view彈出,加入購物車時圖片的動畫效果差不多,經(jīng)過一些研究查閱之后做了一個非常標準的實現(xiàn),并且在此基礎(chǔ)上做了稍稍一些擴展.大體效果如下:



實現(xiàn)方式是通過 UIImageView + Category 的方式實現(xiàn)的,用分類的方式來實現(xiàn)不會對原有類產(chǎn)生任何影響.
動畫過程是以UIBezierPath, CABasicAnimation及其子類CAKeyframeAnimation等來實現(xiàn)的,并且通過運行時 runtime 的特性為分類增加了一些屬性,更加方便我們?nèi)フ{(diào)用設(shè)置.大體想法是通過貝塞爾路徑畫出圖片所要做的位移路徑,并且在圖片做位移動畫的過程中,通過CABasicAnimation核心動畫的控制,讓圖片的旋轉(zhuǎn),縮放,拋物線,移動速率等的動畫同時發(fā)生.
已經(jīng)寫過一篇文章講述了控制器的3D下沉上升效果,這里就不多說了.主要分析下圖片動畫的過程.整個圖片的位移路徑首先是通過貝塞爾路徑來控制的,先說一下簡單的一個中心控制點的路徑,大多數(shù)電商APP圖片的移動路徑也都是這種的:
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint startPoint = [self convertPoint:self.center toView:nil];
CGPoint endPoint = [[[self endPointAndHadianHeightByTransformEndType:transformType] firstObject] CGPointValue];
簡單方式: 為路徑設(shè)置一個中心控制點(拋物線單頂點):######
CGFloat radianHeight = [[[self endPointAndHadianHeightByTransformEndType:transformType] lastObject] floatValue];
float sx = startPoint.x, sy = startPoint.y, ex = endPoint.x, ey = endPoint.y;
float x = sx + (ex - sx)/3, y = sy + (ey - sy)*0.5 - radianHeight;
CGPoint centerPoint=CGPointMake(x,y);
然后將起始點和中心點都加入貝塞爾路徑中,同時調(diào)用自定義的CAAnimation核心動畫方法:
[path moveToPoint:startPoint];
[path addQuadCurveToPoint:endPoint controlPoint:centerPoint];
[self transformWithBezierPath:path duration:duration];
兩個控制點的方式: 為路徑設(shè)置兩個控制點(一上一下兩個頂點):######
CGFloat radianHeight = [[[self endPointAndHadianHeightByTransformEndType:transformType] lastObject] floatValue];
CGPoint controlPoint1 = [[[self controlPointsByParabolaType:parabolaType startPoint:startPoint endPoint:endPoint radianHeight:radianHeight] firstObject] CGPointValue];
CGPoint controlPoint2 = [[[self controlPointsByParabolaType:parabolaType startPoint:startPoint endPoint:endPoint radianHeight:radianHeight] lastObject] CGPointValue];
[path moveToPoint:startPoint];
[path addCurveToPoint:endPoint controlPoint1:controlPoint1 controlPoint2:controlPoint2];
自定義的CAAnimation核心動畫方法- (void)transformWithBezierPath:(UIBezierPath *)path duration:(NSTimeInterval)duration;內(nèi),
首先要確定一件事:該部分執(zhí)行動畫的圖層全部都以 keyWindow 為參考系進行的,并且要為圖層新建一個layer對象作為執(zhí)行動畫的圖層,讓圖片在新的layer上進行位移,不然會直接作用于原圖片.
然后在此基礎(chǔ)上實現(xiàn)三個動畫:圖層的拋物線,圖層的旋轉(zhuǎn)和圖層的尺寸縮放:
- (void)transformWithBezierPath:(UIBezierPath *)path duration:(NSTimeInterval)duration{
//該部分動畫的imageview 全部都以 keyWindow 為參考系進行
UIWindow *window = [UIApplication sharedApplication].keyWindow;
CGRect rect = [self convertRect:self.bounds toView:window];
CALayer *layer = [[CALayer alloc] init];
layer.contents = self.layer.contents;
layer.frame = rect;
layer.opacity = 1;
[window.layer addSublayer:layer];
//拋物線
CAKeyframeAnimation *parabolaPathAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
parabolaPathAnimation.path = path.CGPath; //pao
parabolaPathAnimation.autoreverses = NO; //自動復(fù)位為NO
parabolaPathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
parabolaPathAnimation.duration = duration;
parabolaPathAnimation.fillMode = kCAFillModeForwards; //動畫狀態(tài)是否保持
parabolaPathAnimation.removedOnCompletion = NO; //完成后移除
//旋轉(zhuǎn)
CABasicAnimation* rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 8];
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
rotationAnimation.cumulative = YES;
rotationAnimation.duration = duration;
rotationAnimation.fillMode = kCAFillModeForwards;
rotationAnimation.removedOnCompletion = NO;
//尺寸縮放
CAKeyframeAnimation *transformAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
CATransform3D scale1 = CATransform3DMakeScale(1.0, 1.0, 1),
scale2 = CATransform3DMakeScale(0.65, 0.65, 1),
scale3 = CATransform3DMakeScale(0.2, 0.2, 1),
scale4 = CATransform3DMakeScale(.0, .0, 1);
NSArray *frameValues = [NSArray arrayWithObjects:
[NSValue valueWithCATransform3D:scale1],
[NSValue valueWithCATransform3D:scale2],
[NSValue valueWithCATransform3D:scale3],
[NSValue valueWithCATransform3D:scale4], nil];
[transformAnimation setValues:frameValues];
//兩種速率控制方式均可
NSArray *frameTimes = [NSArray arrayWithObjects:@0,@0.5,@0.8,@1,nil];
[transformAnimation setKeyTimes:frameTimes];
// transformAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
transformAnimation.duration = duration;
transformAnimation.fillMode = kCAFillModeForwards;
transformAnimation.removedOnCompletion = NO;
[layer addAnimation:parabolaPathAnimation forKey:@"parabolaPathAnimation"];
[layer addAnimation:transformAnimation forKey:@"transformAnimation"];
[layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
}
分類里具體提供了三個方法供大家來調(diào)用:
/**
* 圖片做貝塞爾路徑的形變位移便捷方法
*
* @param transformType 位移的結(jié)束點類型
*/
- (void)animationWithBezierPathTransformEndType:(TransformEndType)transformType duration:(NSTimeInterval)duration;
/**
* 圖片做多控制點貝塞爾路徑的形變位移標準方法
*
* @param parabolaType 貝塞爾拋物線類型
* @param view 目標view
*/
- (void)animationWithBezierPathTransformParabolaType:(ParabolaType)parabolaType toView:(UIView *)view duration:(NSTimeInterval)duration;
/**
* 圖片做多控制點貝塞爾路徑的形變位移擴展方法
*
* @param transformType 位移的結(jié)束點類型
* @param parabolaType 貝塞爾拋物線類型
*/
- (void)animationWithTwoControlPointsBezierPathTransformEndType:(TransformEndType)transformType parabolaType:(ParabolaType)parabolaType duration:(NSTimeInterval)duration;
便捷方法只需要傳入動畫時間duration以及
/** 位移的結(jié)束點類型 */
typedef NS_ENUM(NSInteger, TransformEndType) {
/** 結(jié)束點 : 導(dǎo)航條的右上角 */
TypeNavRightItemPoint = 0,
/** 結(jié)束點 : Tabbar的第3個(從0開始) */
TypeTabBarIndex3Point = 1,
/** 結(jié)束點 : 與選中圖片對稱 */
TypeSymmetricWithImage = 2,
/** 結(jié)束點 : 自定義結(jié)束點,需要設(shè)提前置endPoint與radianHeight */
TypeCustomPoint = 3,
};
的枚舉值,通常情況下直接選定比如TypeNavRightItemPoint就好;
標準方法需要傳入我們的位移目標位置出的小視圖,比如導(dǎo)航條的右側(cè)按鈕,tabbar的第幾個按鈕等,因為一旦選用控制器3D下沉效果的話,導(dǎo)航條的右側(cè)按鈕相對于keyWindow的位置是會改變的,如果繼續(xù)選擇第一個方法,最終的位置會出現(xiàn)偏差:

看到?jīng)],敬業(yè)福我們可以直接購買了
換上標準方法就好了,傳入目標view:
// [self.goodImage animationWithBezierPathTransformEndType:TypeNavRightItemPoint duration:duration];
UIView *view = self.currentVc.navigationItem.rightBarButtonItem.customView;
[self.goodImage animationWithBezierPathTransformParabolaType:ParabolaTypeUp toView:view duration:duration];
可以看到不管控制器有沒有下沉,導(dǎo)航條右按鈕相對window的frame跑到哪里,都可以準確地定位到其中:

擴展方法主要是多了一個```/** 貝塞爾拋物線類型 /
typedef NS_ENUM(NSInteger, ParabolaType) {
/* 上頂點 /
ParabolaTypeUp = 0,
/* 先下后上 /
ParabolaTypeDownAndUp = 1,
/* 先上后下 */
ParabolaTypeUpAndDown = 2,
};
[self.goodImage animationWithTwoControlPointsBezierPathTransformEndType:TypeNavRightItemPoint parabolaType:ParabolaTypeDownAndUp duration:duration];
最終效果:

這里也做了一份完整的demo,放在了github上,大家可以去看一下:[傳送門](https://github.com/coderlinxx/XXBezierTransform) ,如果能幫助到您或有興趣,幫忙點個贊就更好了,謝謝?
Ps:gif軟件做出來的圖片動畫效果都不太順暢,dome里做出的效果都是挺順滑的,去下一個看看就好了。另外哪位兄弟給推薦一個比較好的錄制gif圖片的軟件。。