#import <UIKit/UIKit.h> @interface FirstViewController : UIViewController @property(nonatomic,strong)UIButton *button; @end
#import "FirstViewController.h" #import "SecondViewController.h" #import "PingTransition.h" @interface FirstViewController () <UINavigationControllerDelegate> @end @implementation FirstViewController -(void)viewWillAppear:(BOOL)animated{ //设置代理 self.navigationController.delegate = self; //为了美观,将 NavigationBar 隐藏 self.navigationController.navigationBarHidden = YES; } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor clearColor]; //创建底层图片 UIImageView *image = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"page1"]]; image.frame = [UIScreen mainScreen].bounds; image.userInteractionEnabled = YES; [self.view addSubview:image]; //创建按钮 _button = [UIButton buttonWithType:(UIButtonTypeCustom)]; [_button addTarget:self action:@selector(buttonAction) forControlEvents:(UIControlEventTouchUpInside)]; [_button setTitle:@"" forState:(UIControlStateNormal)]; _button.frame =CGRectMake(self.view.bounds.size.width - 70, 15, 50, 50); [image addSubview:_button]; } //button 的事件 -(void)buttonAction{ SecondViewController *second = [[SecondViewController alloc]init]; [self.navigationController pushViewController:second animated:YES]; } //UINavigationController 的代理方法,实现动画样式的跳转 #pragma mark - UINavigationControllerDelegate - (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{ if (operation == UINavigationControllerOperationPush) { PingTransition *ping = [PingTransition new]; return ping; }else{ return nil; } }
PingTransition
我们就来看看这里面是怎么写的.#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> //#import <pop/POP.h> @interface PingTransition : NSObject<UIViewControllerAnimatedTransitioning> @end
#import "PingTransition.h" #import "SecondViewController.h" #import "FirstViewController.h" @interface PingTransition () @property (nonatomic,strong)id<UIViewControllerContextTransitioning> transitionContext; @end @implementation PingTransition //“TIPS: 这个协议方法返回动画的执行时间。通常把我们把动画时间设计成一个外部接口,因此只要返回这个属性就可以了。” - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext{ return 0.7f; } //“TIPS: 一切的核心。所有 CoreAnimation 的代码就是写在这里了。” - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{ /* “TIPS: transitionContext 是动画执行过程中的上下文。通过 UIViewControllerContextTransitioning 你可以拿到执行动画的容器 containerView 。 所有动画必须发生在这个容器上。除了可以拿到 containerView ,你还可以获取: UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];” */ self.transitionContext = transitionContext; FirstViewController * fromVC = (FirstViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; SecondViewController *toVC = (SecondViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *contView = [transitionContext containerView]; UIButton *button = fromVC.button; UIBezierPath *maskStartBP = [UIBezierPath bezierPathWithOvalInRect:button.frame]; [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"]; /* POP的弹框效果 CGPathRef CAKeyframeAnimation *keyFrame = [CAKeyframeAnimation animationWithKeyPath:@"path"]; keyFrame.values = @[(__bridge id)(maskStartBP.CGPath),(__bridge id)(maskFinalBP.CGPath)]; keyFrame.duration = 100.0f; keyFrame.additive = YES; keyFrame.removedOnCompletion = NO; keyFrame.fillMode = kCAFillModeForwards; [maskLayer addAnimation:keyFrame forKey:nil]; maskLayer.speed = 0.0; POPAnimatableProperty* pop = [POPAnimatableProperty propertyWithName:@"timeOffset" initializer:^(POPMutableAnimatableProperty *prop) { // read value prop.readBlock = ^(CAShapeLayer *obj, CGFloat values[]) { values[0] = obj.timeOffset; }; // write value prop.writeBlock = ^(CAShapeLayer *obj, const CGFloat values[]) { obj.timeOffset = values[0]; }; // dynamics threshold prop.threshold = 0.1; }]; POPSpringAnimation *popSpring = [POPSpringAnimation animation]; popSpring.fromValue = @(0.0); popSpring.toValue = @(100.f); popSpring.springBounciness = 1.0;//弹性 popSpring.springSpeed = 20.0;//速度 popSpring.dynamicsTension = 700;//张力 popSpring.dynamicsFriction = 5; // 摩擦力 popSpring.dynamicsMass = 1; popSpring.property = pop; popSpring.delegate = self; [maskLayer pop_addAnimation:popSpring forKey:nil]; */ // kPOPShapeLayerStrokeStart //创建一个关于 path 的 CABasicAnimation 动画来从 circleMaskPathInitial.CGPath 到 circleMaskPathFinal.CGPath 。同时指定它的 delegate 来在完成动画时做一些清除工作 } //- (void)pop_animationDidStop:(POPAnimation *)anim finished:(BOOL)finished{ // //告诉 iOS 这个 transition 完成 // [self.transitionContext completeTransition:![self. transitionContext transitionWasCancelled]]; // //清除 fromVC 的 mask // [self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view.layer.mask = nil; // [self.transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view.layer.mask = nil; //} //“TIPS: 最后通知上下文动画已完成。” #pragma mark - CABasicAnimation的Delegate - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{ //告诉 iOS 这个 transition 完成 [self.transitionContext completeTransition:![self. transitionContext transitionWasCancelled]]; //清除 fromVC 的 mask [self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view.layer.mask = nil; [self.transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view.layer.mask = nil; } @end
#import <UIKit/UIKit.h> @interface SecondViewController : UIViewController @property(nonatomic,strong)UIButton *button; @end
#import "SecondViewController.h" #import "FirstViewController.h" #import "PingInvertTransition.h" @interface SecondViewController () <UINavigationControllerDelegate>{ //设置并更新一个 iOS 7 新加入的类的对象。 UIPercentDrivenInteractiveTransition。 //这个类的对象会根据我们的手势,来决定我们的自定义过渡的完成度 UIPercentDrivenInteractiveTransition *percentTransition; } @end @implementation SecondViewController -(void)viewWillAppear:(BOOL)animated{ self.navigationController.delegate = self; self.navigationController.navigationBarHidden = YES; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor clearColor]; UIImageView *image = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"page2"]]; image.frame = [UIScreen mainScreen].bounds; image.userInteractionEnabled = YES; [self.view addSubview:image]; //设置action, 并设置改变的方法,因为我们是想从第二个页面回到第一个页面,所以设置 UIRectEdgeLeft UIScreenEdgePanGestureRecognizer *edgeGes = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(edgePan:)]; edgeGes.edges = UIRectEdgeLeft; [self.view addGestureRecognizer:edgeGes]; _button = [UIButton buttonWithType:(UIButtonTypeCustom)]; [_button addTarget:self action:@selector(buttonAction) forControlEvents:(UIControlEventTouchUpInside)]; [_button setTitle:@"" forState:(UIControlStateNormal)]; _button.frame = image.frame; [image addSubview:_button]; } -(void)buttonAction{ [self.navigationController popViewControllerAnimated:YES]; } - (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController { // 返回我们的自定义过渡 return percentTransition; } -(void)edgePan:(UIPanGestureRecognizer *)recognizer{ // 计算用户手指划了多远 CGFloat per = [recognizer translationInView:self.view].x / (self.view.bounds.size.width); per = MIN(1.0,(MAX(0.0, per))); if (recognizer.state == UIGestureRecognizerStateBegan) { // 创建过渡对象,弹出viewController percentTransition = [[UIPercentDrivenInteractiveTransition alloc]init]; [self.navigationController popViewControllerAnimated:YES]; }else if (recognizer.state == UIGestureRecognizerStateChanged){ // 更新 interactive transition 的进度 [percentTransition updateInteractiveTransition:per]; }else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled){ // 完成或者取消过渡 if (per > 0.3) { [percentTransition finishInteractiveTransition]; }else{ [percentTransition cancelInteractiveTransition]; } percentTransition = nil; } } - (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{ if (operation == UINavigationControllerOperationPop) { PingInvertTransition *pingInvert = [PingInvertTransition new]; return pingInvert; }else{ return nil; } }
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface PingInvertTransition : NSObject <UIViewControllerAnimatedTransitioning> @end
#import "PingInvertTransition.h" #import "SecondViewController.h" #import "FirstViewController.h" @interface PingInvertTransition() @property(nonatomic,strong)id<UIViewControllerContextTransitioning>transitionContext; @end @implementation PingInvertTransition //“TIPS: 这个协议方法返回动画的执行时间。通常把我们把动画时间设计成一个外部接口,因此只要返回这个属性就可以了。” - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext{ return 0.7f; } //“TIPS: 一切的核心。所有 CoreAnimation 的代码就是写在这里了。” - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{ /* “TIPS: transitionContext 是动画执行过程中的上下文。通过 UIViewControllerContextTransitioning 你可以拿到执行动画的容器 containerView 。 所有动画必须发生在这个容器上。除了可以拿到 containerView ,你还可以获取: UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];” */ self.transitionContext = transitionContext; SecondViewController *fromVC = (SecondViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; FirstViewController *toVC = (FirstViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *containerView = [transitionContext containerView]; UIButton *button = toVC.button; [containerView addSubview:toVC.view]; [containerView addSubview:fromVC.view]; UIBezierPath *finalPath = [UIBezierPath bezierPathWithOvalInRect:button.frame]; /* “TIPS: 创建两个圆形的 UIBezierPath 实例; 一个是初始位置的最小内接圈。 另一个是拥有足够覆盖屏幕半径的外接圆。 最终的动画则是在这两个贝塞尔路径之间进行的。 通过确定初始点所在的象限位置,从而确定终点位置,从而计算出半径 —— 也就是最小能覆盖整个界面的圆。” */ 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 *startPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, -radius, -radius)]; CAShapeLayer *maskLayer = [CAShapeLayer layer]; maskLayer.path = finalPath.CGPath; fromVC.view.layer.mask = maskLayer; CABasicAnimation *pingAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; pingAnimation.fromValue = (__bridge id)(startPath.CGPath); pingAnimation.toValue = (__bridge id)(finalPath.CGPath); pingAnimation.duration = [self transitionDuration:transitionContext]; pingAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; pingAnimation.delegate = self; [maskLayer addAnimation:pingAnimation forKey:@"pingInvert"]; } //“TIPS: 最后通知上下文动画已完成。” - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{ [self.transitionContext completeTransition:![self.transitionContext transitionWasCancelled]]; [self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view.layer.mask = nil; [self.transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view.layer.mask = nil; }