iOS7 開始蘋果推出了自定義轉(zhuǎn)場的 API 衣赶。從此诊赊,任何可以用 CoreAnimation 實現(xiàn)的動畫,都可以出現(xiàn)在兩個 ViewController 的切換之間府瞄。并且實現(xiàn)方式高度解耦碧磅,這也意味著在保證代碼干凈的同時想要替換其他動畫方案時只需簡單改一個類名就可以了,真正體會了一把高顏值代碼帶來的愉悅感遵馆。
其實網(wǎng)上關(guān)于自定義轉(zhuǎn)場動畫的教程很多鲸郊,這里我是希望同學(xué)們能易懂,易上手货邓。
轉(zhuǎn)場分兩種Push和Modal,所以自定義轉(zhuǎn)場動畫也就肯定分兩種秆撮,今天我們講的是Push
自定義轉(zhuǎn)場動畫Push
首先搭建界面,添加4個按鈕:
- (void)addButton{
self.buttonArr = [NSMutableArray array];
CGFloat margin=50;
CGFloat width=(self.view.frame.size.width-margin*3)/2;
CGFloat height = width;
CGFloat x = 0;
CGFloat y = 0;
//列
NSInteger col = 2;
for (NSInteger i = 0; i < 4; i ++) {
x = margin + (i%col)*(margin+width);
y = margin + (i/col)*(margin+height) + 150;
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(x, y, width, height);
button.layer.cornerRadius = width * 0.5;
[button addTarget:self action:@selector(btnclick:) forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1.0];
button.tag = i+1;
[self.view addSubview:button];
[self.buttonArr addObject:button];
}
}
添加動畫:
- (void)setupButtonAnimation{
[self.buttonArr enumerateObjectsUsingBlock:^(UIButton * _Nonnull button, NSUInteger idx, BOOL * _Nonnull stop) {
// positionAnimation
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
positionAnimation.calculationMode = kCAAnimationPaced;
positionAnimation.fillMode = kCAFillModeForwards;
positionAnimation.repeatCount = MAXFLOAT;
positionAnimation.autoreverses = YES;
positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
positionAnimation.duration = (idx == self.buttonArr.count - 1) ? 4 : 5+idx;
UIBezierPath *positionPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, button.frame.size.width/2-5, button.frame.size.height/2-5)];
positionAnimation.path = positionPath.CGPath;
[button.layer addAnimation:positionAnimation forKey:nil];
// scaleXAniamtion
CAKeyframeAnimation *scaleXAniamtion = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.x"];
scaleXAniamtion.values = @[@1.0,@1.1,@1.0];
scaleXAniamtion.keyTimes = @[@0.0,@0.5,@1.0];
scaleXAniamtion.repeatCount = MAXFLOAT;
scaleXAniamtion.autoreverses = YES;
scaleXAniamtion.duration = 4+idx;
[button.layer addAnimation:scaleXAniamtion forKey:nil];
// scaleYAniamtion
CAKeyframeAnimation *scaleYAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.y"];
scaleYAnimation.values = @[@1,@1.1,@1.0];
scaleYAnimation.keyTimes = @[@0.0,@0.5,@1.0];
scaleYAnimation.autoreverses = YES;
scaleYAnimation.repeatCount = YES;
scaleYAnimation.duration = 4+idx;
[button.layer addAnimation:scaleYAnimation forKey:nil];
}];
}
界面搭建好了:
然后想在Push的時候?qū)崿F(xiàn)自定義轉(zhuǎn)場動畫首先要遵守一個協(xié)議UINavigationControllerDelegate
蘋果在 UINavigationControllerDelegate 中給出了幾個協(xié)議方法逻恐,通過返回類型就可以很清楚地知道各自的具體作用像吻。
//用來自定義轉(zhuǎn)場動畫
- (nullable id )navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC ?NS_AVAILABLE_IOS(7_0);
//為這個動畫添加用戶交互
- (nullable id )navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id ) animationController NS_AVAILABLE_IOS(7_0);
在第一個方法里只要返回一個準(zhǔn)守UIViewControllerInteractiveTransitioning協(xié)議的對象,并在里面實現(xiàn)動畫即可
創(chuàng)建繼承自 NSObject 并且聲明 UIViewControllerAnimatedTransitioning 的的動畫類。
重載 UIViewControllerAnimatedTransitioning 中的協(xié)議方法复隆。
//返回動畫時間
- (NSTimeInterval)transitionDuration:(nullable id )transitionContext;
//將動畫的代碼寫到里面即可
- (void)animateTransition:(id )transitionContext;
首先我自定義一個名為LRTransitionPushController的類繼承于NSObject準(zhǔn)守了UIViewControllerAnimatedTransitioning協(xié)議
- (void)animateTransition:(id)transitionContext{
self.transitionContext = transitionContext;
//獲取源控制器 注意不要寫成 UITransitionContextFromViewKey
LRTransitionPushController *fromVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
//獲取目標(biāo)控制器 注意不要寫成 UITransitionContextToViewKey
LRTransitionPopController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//獲得容器視圖
UIView *containView = [transitionContext containerView];
// 都添加到container中。注意順序 目標(biāo)控制器的view需要后面添加
[containView addSubview:fromVc.view];
[containView addSubview:toVc.view];
UIButton *button = fromVc.button;
//繪制圓形
UIBezierPath *startPath = [UIBezierPath bezierPathWithOvalInRect:button.frame];
//創(chuàng)建兩個圓形的 UIBezierPath 實例姆涩;一個是 button 的 size 挽拂,另外一個則擁有足夠覆蓋屏幕的半徑。最終的動畫則是在這兩個貝塞爾路徑之間進行的
//按鈕中心離屏幕最遠的那個角的點
CGPoint finalPoint;
//判斷觸發(fā)點在那個象限
if(button.frame.origin.x > (toVc.view.bounds.size.width / 2)){
if (button.frame.origin.y < (toVc.view.bounds.size.height / 2)) {
//第一象限
finalPoint = CGPointMake(0, CGRectGetMaxY(toVc.view.frame));
}else{
//第四象限
finalPoint = CGPointMake(0, 0);
}
}else{
if (button.frame.origin.y < (toVc.view.bounds.size.height / 2)) {
//第二象限
finalPoint = CGPointMake(CGRectGetMaxX(toVc.view.frame), CGRectGetMaxY(toVc.view.frame));
}else{
//第三象限
finalPoint = CGPointMake(CGRectGetMaxX(toVc.view.frame), 0);
}
}
CGPoint startPoint = CGPointMake(button.center.x, button.center.y);
//計算向外擴散的半徑 = 按鈕中心離屏幕最遠的那個角距離 - 按鈕半徑
CGFloat radius = sqrt((finalPoint.x-startPoint.x) * (finalPoint.x-startPoint.x) + (finalPoint.y-startPoint.y) * (finalPoint.y-startPoint.y)) - sqrt(button.frame.size.width/2 * button.frame.size.width/2 + button.frame.size.height/2 * button.frame.size.height/2);
UIBezierPath *endPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, -radius, -radius)];
//賦值給toVc視圖layer的mask
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = endPath.CGPath;
toVc.view.layer.mask = maskLayer;
CABasicAnimation *maskAnimation =[CABasicAnimation animationWithKeyPath:@"path"];
maskAnimation.fromValue = (__bridge id)startPath.CGPath;
maskAnimation.toValue = (__bridge id)endPath.CGPath;
maskAnimation.duration = [self transitionDuration:transitionContext];
maskAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
maskAnimation.delegate = self;
[maskLayer addAnimation:maskAnimation forKey:@"path"];
}
在控制器里面用來自定義轉(zhuǎn)場動畫的方法里返回剛才自定義的動畫類
- (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
if (operation == UINavigationControllerOperationPush) {
return [LRTranstionAnimationPush new];
}else{
return nil;
}
}
到此為止自定義轉(zhuǎn)場動畫就完成了
pop的動畫只是把push動畫反過來做一遍這里就不細講了骨饿,有疑問的可以去看代碼
添加滑動返回手勢
上面說到這個方法是為這個動畫添加用戶交互的所以我們要在pop時實現(xiàn)滑動返回
最簡單的方式應(yīng)該就是利用UIKit提供的UIPercentDrivenInteractiveTransition類了亏栈,這個類已經(jīng)實現(xiàn)了UIViewControllerInteractiveTransitioning協(xié)議,同學(xué)men可以通過這個類的對象指定轉(zhuǎn)場動畫的完成百分比宏赘。
//為這個動畫添加用戶交互
- (nullable id )navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id ) animationController NS_AVAILABLE_IOS(7_0);
第一步 添加手勢
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[self.view addGestureRecognizer:gestureRecognizer];
第二步 通過用戶滑動的變化確定動畫執(zhí)行的比例
- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer {
/*調(diào)用UIPercentDrivenInteractiveTransition的updateInteractiveTransition:方法可以控制轉(zhuǎn)場動畫進行到哪了绒北,
當(dāng)用戶的下拉手勢完成時,調(diào)用finishInteractiveTransition或者cancelInteractiveTransition察署,UIKit會自動執(zhí)行剩下的一半動畫闷游,
或者讓動畫回到最開始的狀態(tài)。*/
if ([gestureRecognizer translationInView:self.view].x>=0) {
//手勢滑動的比例
CGFloat per = [gestureRecognizer translationInView:self.view].x / (self.view.bounds.size.width);
per = MIN(1.0,(MAX(0.0, per)));
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
self.interactiveTransition = [UIPercentDrivenInteractiveTransition new];
[self.navigationController popViewControllerAnimated:YES];
} else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){
if([gestureRecognizer translationInView:self.view].x ==0){
[self.interactiveTransition updateInteractiveTransition:0.01];
}else{
[self.interactiveTransition updateInteractiveTransition:per];
}
} else if (gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled){
if([gestureRecognizer translationInView:self.view].x == 0){
[self.interactiveTransition cancelInteractiveTransition];
self.interactiveTransition = nil;
}else if (per > 0.5) {
[ self.interactiveTransition finishInteractiveTransition];
}else{
[ self.interactiveTransition cancelInteractiveTransition];
}
self.interactiveTransition = nil;
}
} else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){
[self.interactiveTransition updateInteractiveTransition:0.01];
[self.interactiveTransition cancelInteractiveTransition];
} else if ((gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled)){
self.interactiveTransition = nil;
}
}
第三步 在為動畫添加用戶交互的代理方法里返回UIPercentDrivenInteractiveTransition的實例
- (id )navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id ) animationController {
return self.interactiveTransition;
}