IOS 自定义push和pop动画
2016-04-06 15:22
399 查看
转载大神 http://www.cocoachina.com/ios/20150401/11459.html 自iOS7之后,引进了新的API来构造UIViewController之间的转场动画,经过几天的研究,终于做出了一个小Damo,来粗浅谈谈。 这几个API如下: <1.>UIViewControllerAnimatedTransitioning 动画协议 <2>.UIViewControllerInteractiveTransitioning 交互协议 <3>.UIViewControllerContextTransitioning 上下文协议 <4>.UIPercentDrivenInteractiveTransition 遵守 <2>协议的一个官方类 之所以官方给出的API是协议而不是类别,给出的说法是为了灵活性,你可以在ViewController里面直接写,也可以直接另写一个类封装起来。 进入正文: 1、 这个类负责动画,继承自NSObject,遵守UIViewControllerAnimatedTransitioning协议,记得导入UIKit,如下 @interface PopAnimation : NSObject <UIViewControllerAnimatedTransitioning> @end 在.m文件中实现协议其中的两个方法: (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { //这个方法返回动画执行的时间 return 0.25;
}
/** transitionContext你可以看作是一个工具,用来获取一系列动画执行相关的对象,并且通知系统动画是否完成等功能。 */ (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { /** 获取动画来自的那个控制器 */ UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; /** 获取转场到的那个控制器 */ UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; /** 转场动画是两个控制器视图时间的动画,需要一个containerView来作为一个“舞台”,让动画执行。 */ UIView *containerView = [transitionContext containerView]; [containerView insertSubview:toViewController.view belowSubview:fromViewController.view]; NSTimeInterval duration = [self transitionDuration:transitionContext]; // /** // * 执行动画,我们让fromVC的视图移动到屏幕最右侧 // */ // [UIView animateWithDuration:duration animations:^{ // fromViewController.view.transform = CGAffineTransformMakeTranslation([UIScreen mainScreen].bounds.size.width, 0); }completion:^(BOOL finished) { /** // * 当你的动画执行完成,这个方法必须要调用,否则系统会认为你的其余任何操作都在动画执行过程中。 // */ // [transitionContext completeTransition:!transitionContext.transitionWasCancelled]; // }]; _transitionContext = transitionContext; // ----------------pop动画一-------------------------// [UIView beginAnimations:@"View Flip" context:nil]; [UIView setAnimationDuration:duration]; [UIView setAnimationDelegate:self]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:containerView cache:YES]; [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:)]; [UIView commitAnimations];//提交UIView动画 [containerView exchangeSubviewAtIndex:0 withSubviewAtIndex:1]; // //----------------pop动画二-------------------------// // // CATransition *tr = [CATransition animation]; // tr.type = @"cube"; // tr.subtype = @"fromLeft"; // tr.duration = duration; // tr.removedOnCompletion = NO; // tr.fillMode = kCAFillModeForwards; // tr.delegate = self; // [containerView.layer addAnimation:tr forKey:nil]; // [containerView exchangeSubviewAtIndex:0 withSubviewAtIndex:1]; } *在pop过程中可使用动画,例如 _transitionContext = transitionContext; // ----------pop动画一-------------------------// [UIView beginAnimations:@"View Flip" context:nil]; [UIView setAnimationDuration:duration]; [UIView setAnimationDelegate:self]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:containerView cache:YES]; [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:)]; [UIView commitAnimations];//提交UIView动画 [containerView exchangeSubviewAtIndex:0 withSubviewAtIndex:1]; Push基类.m 第一个方法返回的是动画时间,不做多言。 第二个方法是动画的具体执行,方法的参数transitionContext遵守了UIViewControllerContextTransitioning协议,所以它包含了许多关于专场所需要的内容,包括转入ViewController和转出Viewcontroller,还有动画容器View--containerView等。 我们点进去UIViewControllerContextTransitioning协议,可以找到许多的属性和方法,这些方法中最重要的几个方法和意义如下: (UIView*)containerView; //获取容器View (void)completeTransition:(BOOL)didComplete;//通过此参数获知动画是否结束 (UIViewController*)viewControllerForKey:(NSString*)key; //获取转入、转出VC (CGRect)initialFrameForViewController:(UIViewController*)vc //获取动画前VC的frame (CGRect)finalFrameForViewController:(UIViewController*)vc; //获取动画后VC的frame (push的基类也和pop一样,只是具体动画效果代码的不同) 另外,我们需要返回一个遵守了UIViewControllerInteractiveTransitioning协议的对象(提示一下,这两个协议容易混淆,要注意区分,一个是负责动画,一个是负责交互过程),苹果已经有一个类专门处理这个功能,它叫UIPercentDrivenInteractiveTransition,当然你也可以自定义一个这样的类。我们可以这样理解它的作用:前面在方法1中返回的动画,会在执行的过程中被系统分解以用于用户交互,这个交互过程的动画完成度就由它来调控。下面我们来看一下如何使用它。(为了让控制器视图拖动,我们给控制器的视图加了一个拖动手势,在拖动方法里我们对这个对象进行操作) 定义一个类,NavigationInteractiveTransition ,实现 .h中 @class UIViewController, UIPercentDrivenInteractiveTransition; @interface NavigationInteractiveTransition : NSObject <UINavigationControllerDelegate> (instancetype)initWithViewController:(UIViewController *)vc; (void)handleControllerPop:(UIPanGestureRecognizer *)recognizer; (UIPercentDrivenInteractiveTransition *)interactivePopTransition; @end .m中 设置导航控制器的delegate为这个自定义的类 @interface NavigationInteractiveTransition () @property (nonatomic, weak) UINavigationController *vc; @property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactivePopTransition; @end @implementation NavigationInteractiveTransition (instancetype)initWithViewController:(UIViewController *)vc { self = [super init]; if (self) { self.vc = (UINavigationController *)vc; self.vc.delegate = self; } return self; } 添加手势代理 /* 我们把用户的每次Pan手势操作作为一次pop动画的执行 */ (void)handleControllerPop:(UIPanGestureRecognizer *)recognizer { /* interactivePopTransition就是我们说的方法2返回的对象,我们需要更新它的进度来控制Pop动画的流程,我们用手指在视图中的位置与视图宽度比例作为它的进度。 */ CGFloat progress = [recognizer translationInView:recognizer.view].x / recognizer.view.bounds.size.width; /** 稳定进度区间,让它在0.0(未完成)~1.0(已完成)之间 */ progress = MIN(1.0, MAX(0.0, progress)); if (recognizer.state == UIGestureRecognizerStateBegan) { /** 手势开始,新建一个监控对象 */ self.interactivePopTransition = [[UIPercentDrivenInteractiveTransition alloc] init]; /** 告诉控制器开始执行pop的动画 */ [self.vc popViewControllerAnimated:YES]; } else if (recognizer.state == UIGestureRecognizerStateChanged) { /** 更新手势的完成进度 */ [self.interactivePopTransition updateInteractiveTransition:progress]; } else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) { /** 手势结束时如果进度大于一半,那么就完成pop操作,否则重新来过。 */ if (progress > 0.3) { [UIView animateWithDuration:0.3 animations:^{ [self.interactivePopTransition finishInteractiveTransition]; }completion:^(BOOL finished) { self.interactivePopTransition = nil; }]; } else { [UIView animateWithDuration:0.3 animations:^{ [self.interactivePopTransition cancelInteractiveTransition]; }completion:^(BOOL finished) { self.interactivePopTransition = nil; }]; } } } 然后初始化pop动画类 (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { /** 方法1中判断如果当前执行的是Pop操作,就返回我们自定义的Pop动画对象。 */ if (operation == UINavigationControllerOperationPop) return [[PopAnimation alloc] init]; return nil; } 最后,自定义一个集成UINavigationcontroller的类来使用 #import "NavigationInteractiveTransition.h" @interface Nav () <UIGestureRecognizerDelegate> @property (nonatomic, weak) UIPanGestureRecognizer *popRecognizer; /** 方案一不需要的变量 */ @property (nonatomic, strong) NavigationInteractiveTransition *navT; @end @implementation Nav (void)viewDidLoad { [super viewDidLoad]; UIGestureRecognizer *gesture = self.interactivePopGestureRecognizer; gesture.enabled = NO; UIView *gestureView = gesture.view; UIPanGestureRecognizer *popRecognizer = [[UIPanGestureRecognizer alloc] init]; popRecognizer.delegate = self; popRecognizer.maximumNumberOfTouches = 1; [gestureView addGestureRecognizer:popRecognizer]; _navT = [[NavigationInteractiveTransition alloc] initWithViewController:self]; [popRecognizer addTarget:_navT action:@selector(handleControllerPop:)]; } (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { /** 这里有两个条件不允许手势执行,1、当前控制器为根控制器;2、如果这个push、pop动画正在执行(私有属性) */ return self.viewControllers.count != 1 && ![[self valueForKey:@"_isTransitioning"] boolValue]; } 接下来就可以使用了,push和pop的方法一样,只是在实例化的时候注意区分 (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { /** 方法1中判断如果当前执行的是Pop操作,就返回我们自定义的Pop动画对象。 */ if (operation == UINavigationControllerOperationPop) return [[PopAnimation alloc] init]; else if (operation == UINavigationControllerOperationPush) return [[PushAnimation alloc] init]; return nil; } 常见问题: 经常会遇到,横向翻页的scrollview和滑动手势冲突,那么 1.首先自定义一个scrollView,比如:CustomScrollView,遵守协议,然后在实现文件中写如下代码: -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
// 首先判断otherGestureRecognizer是不是系统pop手势 if ([otherGestureRecognizer.view isKindOfClass:NSClassFromString(@"UILayoutContainerView")]) { // 再判断系统手势的state是began还是fail,同时判断scrollView的位置是不是正好在最左边 if (otherGestureRecognizer.state == UIGestureRecognizerStateBegan && self.contentOffset.x == 0) { return YES; } } return NO; } 2.那个横向滚动的scrollView继承这个自定义scrollView,也就是CustomScrollView
相关文章推荐
- ios CocoaPods安装和使用
- 相机的授权 同样适用于扫一扫前的授权判断
- iOS 常用工具 加密规则
- iOS主要设备支持的视频格式
- iOS 定位
- iOS-字符串转化成NSDate类型 计算与当前时间的相差 月数 天数
- iOS 本地缓存 归档数据模型化
- iOS-获取的NSDate date时间与实际相差8个小时解决方案
- ios-复制字符串到剪贴板
- iOS-调用系统的短信和发送邮件功能,实现短信分享邮件分享
- 快速理解GCD
- iOS-iphone自定义状态栏
- ios-获取系统相簿里边的所有照片
- iOS-自定义修改拍照界面retake和use按钮
- iOS 8 AutoLayout与Size Class
- iOS开发入门的一些困惑
- 【ios开发学习】Cocoa 新的依赖管理工具:Carthage
- iOS图片旋转问题
- iOS 画平滑曲线的方法及取音频数据的方法
- iOS 数据保存几种方式总结