iOS7 开始苹果推出了自定义转场的 API 。从此,任何可以用 CoreAnimation 实现的动画,都可以出现在两个 ViewController 的切换之间。并且实现方式高度解耦,这也意味着在保证代码干净的同时想要替换其他动画方案时只需简单改一个类名就可以了,真正体会了一把高颜值代码带来的愉悦感。
其实网上关于自定义转场动画的教程很多,这里我是希望同学们能易懂,易上手。
转场分两种Push和Modal,所以自定义转场动画也就肯定分两种,今天我们讲的是Push
自定义转场动画Push
想在Push的时候实现自定义转场动画首先要遵守一个协议UINavigationControllerDelegate
苹果在 UINavigationControllerDelegate 中给出了几个协议方法,通过返回类型就可以很清楚地知道各自的具体作用。
//用来自定义转场动画
- (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);
在第一个方法里只要返回一个准守UIViewControllerInteractiveTransitioning协议的对象,并在里面实现动画即可
创建继承自 NSObject 并且声明 UIViewControllerAnimatedTransitioning 的的动画类。
重载 UIViewControllerAnimatedTransitioning 中的协议方法。
//返回动画时间
- (NSTimeInterval)transitionDuration:(nullable id )transitionContext;
//将动画的代码写到里面即可
- (void)animateTransition:(id )transitionContext;
首先我自定义一个名为CustomAnimateTransitionPush的类继承于NSObject准守了UIViewControllerAnimatedTransitioning协议
self.transitionContext=transitionContext;
// 获取动画的源控制器和目标控制器
ViewController * fromVC = (ViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
PushViewController *toVC = (PushViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *contView = [transitionContext containerView];
UIButton *button = fromVC.button;
UIBezierPath *maskStartBP = [UIBezierPath bezierPathWithOvalInRect:button.frame];
// 都添加到container中。注意顺序
[contView addSubview:fromVC.view];
[contView addSubview:toVC.view];
//*******************************下面代码就是自定义动画了大家把想要实现的动画写在下面即可**********************//
//创建两个圆形的 UIBezierPath 实例;一个是 button 的 size ,另外一个则拥有足够覆盖屏幕的半径。最终的动画则是在这两个贝塞尔路径之间进行的
CGPoint finalPoint;
//判断触发点在那个象限
if(button.frame.origin.x > (toVC.view.bounds.size.width / 2)){
if (button.frame.origin.y < (toVC.view.bounds.size.height / 2)) {
//第一象限
finalPoint = CGPointMake(button.center.x - 0, button.center.y - CGRectGetMaxY(toVC.view.bounds)+30);
}else{
//第四象限
finalPoint = CGPointMake(button.center.x - 0, button.center.y - 0);
}
}else{
if (button.frame.origin.y < (toVC.view.bounds.size.height / 2)) {
//第二象限
finalPoint = CGPointMake(button.center.x - CGRectGetMaxX(toVC.view.bounds), button.center.y - CGRectGetMaxY(toVC.view.bounds)+30);
}else{
//第三象限
finalPoint = CGPointMake(button.center.x - CGRectGetMaxX(toVC.view.bounds), button.center.y - 0);
}
}
CGFloat radius = sqrt((finalPoint.x * finalPoint.x) + (finalPoint.y * finalPoint.y));
UIBezierPath *maskFinalBP = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, -radius, -radius)];
//创建一个 CAShapeLayer 来负责展示圆形遮盖
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = maskFinalBP.CGPath; //将它的 path 指定为最终的 path 来避免在动画完成后会回弹
toVC.view.layer.mask = maskLayer;
CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
maskLayerAnimation.fromValue = (__bridge id)(maskStartBP.CGPath);
maskLayerAnimation.toValue = (__bridge id)((maskFinalBP.CGPath));
maskLayerAnimation.duration = [self transitionDuration:transitionContext];
maskLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
maskLayerAnimation.delegate = self;
[maskLayer addAnimation:maskLayerAnimation forKey:@"path"];
//*******************************上面代码就是自定义动画了大家把想要实现的动画写在上面即可**********************//
在控制器里面用来自定义转场动画的方法里返回刚才自定义的动画类
-(id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if(operation==UINavigationControllerOperationPush)
{
//自定义的动画类
CustomAnimateTransitionPush *animateTransitionPush=[CustomAnimateTransitionPush new];
return animateTransitionPush;
}
else
{
return nil;
}
}
到此为止自定义转场动画就完成了
pop的动画只是把push动画反过来做一遍这里就不细讲了,有疑问的可以去看代码
上面说到这个方法是为这个动画添加用户交互的所以我们要在pop时实现滑动返回
最简单的方式应该就是利用UIKit提供的UIPercentDrivenInteractiveTransition类了,这个类已经实现了UIViewControllerInteractiveTransitioning协议,同学men可以通过这个类的对象指定转场动画的完成百分比。
//为这个动画添加用户交互
- (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];
第二步 通过用户滑动的变化确定动画执行的比例
- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer {
/*调用UIPercentDrivenInteractiveTransition的updateInteractiveTransition:方法可以控制转场动画进行到哪了,
当用户的下拉手势完成时,调用finishInteractiveTransition或者cancelInteractiveTransition,UIKit会自动执行剩下的一半动画,
或者让动画回到最开始的状态。*/
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.navigationController popViewControllerAnimated:YES];
}else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){
[ self.interactiveTransition updateInteractiveTransition:per];
}else if (gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled){
if (per > 0.5) {
[ self.interactiveTransition finishInteractiveTransition];
}else{
[ self.interactiveTransition cancelInteractiveTransition];
}
self.interactiveTransition = nil;
}
}
}
第三步 在为动画添加用户交互的代理方法里返回UIPercentDrivenInteractiveTransition的实例
- (id )navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id ) animationController {
return self.interactiveTransition;
}
如果感觉这篇文章对您有所帮助,顺手点个喜欢,谢谢啦
代码放在了GitHub上大家可以下载。