1.小记
- 关于自定义转场动画,只要你理清他的"套路",你就可以随心所欲地自定义了.
- 大体思路就是:遵守对应的代理协议,然后设置对应的代理,实现代理方法,这个代理方法要返回的值就是你要实现的动画.(如果返回nil,就是默认效果)
- 以UITabBarController为例的简单转场动画demo地址 gitHub地址
2.基本介绍
在此介绍一下基本知识:
1.在哪里写我们自定义的动画.
苹果给我们提供了UIViewControllerAnimatedTransitioning协议,这个协议提供了我们需要的接口,遵守这个协议的对象实现动画基本内容.
让我们跳转进去看看都有什么:
@protocol UIViewControllerAnimatedTransitioning <NSObject>
// This is used for percent driven interactive transitions, as well as for
// container controllers that have companion animations that might need to
// synchronize with the main animation.
// 这个接口返回的值为动画时长
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
// This method can only be a nop if the transition is interactive and not a percentDriven interactive transition.
// 这个接口返回的值为具体动画内容,也就是说,自定义的动画操作都通过这个接口来实现
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
@optional
/// A conforming object implements this method if the transition it creates can
/// be interrupted. For example, it could return an instance of a
/// UIViewPropertyAnimator. It is expected that this method will return the same
/// instance for the life of a transition.
- (id <UIViewImplicitlyAnimating>) interruptibleAnimatorForTransition:(id <UIViewControllerContextTransitioning>)transitionContext NS_AVAILABLE_IOS(10_0);
// This is a convenience and if implemented will be invoked by the system when the transition context's completeTransition: method is invoked.
- (void)animationEnded:(BOOL) transitionCompleted;
@end
1.通过注释的解释,我们能够知道,遵守UIViewControllerAnimatedTransitioning协议的对象就可以实现我们自定义的动画.
2.通常我们会自定义NSObject的子类,遵守UIViewControllerAnimatedTransitioning协议,然后实现协议方法来自定义转场动画.
3.这个子类的对象就是我们的"自定义动画".如果把自定义转场动画比作为做菜的话,那么现在我们准备的就是食材.
- 在这里要对一些概念进行下解释,避免在自定义动画时蒙圈
1.From和To
在自定义转场动画的代码中,经常会出现fromViewController和toViewController。如果错误的理解它们的含义会导致动画逻辑完全错误。
fromViewController表示当前视图容器,toViewController表示要跳转到的视图容器。如果是从A视图控制器present到B,则A是from,B是to。从B视图控制器dismiss到A时,B变成了from,A是to。
2.Presented和Presenting
这也是一组相对的概念,它容易与fromView和toView混淆。简单来说,它不受present或dismiss的影响,如果是从A视图控制器present到B,那么A总是B的presentingViewController, B总是A的presentedViewController。
2.在哪里用我们自定义的动画.
这里要介绍三个协议: 注意每个协议方法的返回值,都是遵守UIViewControllerAnimatedTransitioning的对象
1.协议一: UIViewControllerTransitioningDelegate
// 实现present/dismiss动画的接口.
// 令我们需要自定义动画的控制器遵守UIViewControllerTransitioningDelegate协议,并设置代理,实现协议方法,返回遵守UIViewControllerAnimatedTransitioning协议的类对象即可
// 在这里需要清楚一点,假设由控制器A present 到B, A遵守UIViewControllerTransitioningDelegate协议,则设置B.transitioningDelegate = A,并设置B.modalPresentationStyle = UIModalPresentationCustom(或UIModalPresentationFullScreen);
// 一定要设置modalPresentationStyle,不然还是默认的转场动画.
// present动画
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
// dismiss动画
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;
# modalPresentationStyl
// 这是一个枚举类型,表示present时动画的类型。
// 其中可以自定义动画效果的只有两种:FullScreen和Custom,两者的区别在于FullScreen会移除fromView,而Custom不会。
2.协议二:UINavigationControllerDelegate
// 实现push/pop动画的接口
// 这里同样是要遵守协议,设置代理,实现协议方法.
// 注意这里设置的是navigationController.delegate, self.navigationController.delegate = self.
// 我在其他的博客中看到: (注意: 这里的 self.navigationController.delegate = self 最好写在当前控制器的viewDidAppear方法中, 不然会导致在此push时无动画效果),为什么会失效我还不清楚,希望读者能够找到并分享一下~
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
3.协议三:UITabBarControllerDelegate
// 实现tabBarController切换子控制器的动画
// 还是老套路,遵守协议,设置代理,实现协议方法
// 只是这里要设置tabBarController的代理,我的做法就是在UITabBarController的viewDidLoad方法里设置代理: self.delegate = self;
- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
animationControllerForTransitionFromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
- 小结: 从上面三个协议的返回值能够看出,返回的东西就是我们2.1自定义遵守UIViewControllerAnimatedTransitioning协议的类的对象.
3.转场动画的思路(纯个人理解,起个抛砖引玉作用~)
- 步骤一: 明确做哪种转场动画(做哪种菜,是鲁菜,还是川菜?)
自定义present/dismiss动画要遵守UIViewControllerTransitioningDelegate协议
自定义push/pop动画要遵守UINavigationControllerDelegate协议
自定义tabbarController转场动画要遵守UITabBarControllerDelegate协议
以demo为例:
// DMMainViewController.m文件
@interface DMMainViewController ()<UITabBarControllerDelegate>// 遵守协议
@end
@implementation DMMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = self;// 设置代理
UIViewController *vc = [[UIViewController alloc] init];
vc.view.backgroundColor = [UIColor whiteColor];
[self setChildchildViewController:vc index:0 title:@"我是A"];
[self setChildchildViewController:[[UITableViewController alloc] init] index:1 title:@"我是B"];
[self setChildchildViewController:[[UIViewController alloc] init] index:2 title:@"我是C"];
}
// 动画 实现协议方法
- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
return [[AnimationManager alloc] init];
}
这里其实就是遵守协议,设置代理,实现协议方法.
- 步骤二: 确定做哪样转场动画(如果选择了鲁菜,是德州扒鸡,还是红烧大虾?如果选择了川菜,是四川火锅,还是水煮鱼?)
// AnimationManager.h文件
// 自定义NSObject的子类,遵守UIViewControllerAnimatedTransitioning协议
@interface AnimationManager : NSObject<UIViewControllerAnimatedTransitioning>
@property (nonatomic, assign) KAnimationType type;
- (instancetype)initWithType:(KAnimationType)type;
@end
// AnimationManager.m文件
#import "AnimationManager.h"
#import "DMNavigationViewController.h"
@interface AnimationManager ()
@end
@implementation AnimationManager
// 这个是动画时长
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return 0.5;
}
// 具体动画,在这里可以根据你的想象去实现你要的动画效果了
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
// 获取fromVc和toVc
DMNavigationViewController *fromVc = (DMNavigationViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
DMNavigationViewController *toVc = (DMNavigationViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *fromV = fromVc.view;
UIView *toV = toVc.view;
// 转场环境
UIView *containView = [transitionContext containerView];
containView.backgroundColor = [UIColor whiteColor];
// 判断滑动方向
if (toVc.index > fromVc.index) {
toV.frame = CGRectMake([UIScreen mainScreen].bounds.size.width, 0, containView.frame.size.width, containView.frame.size.height);
[containView addSubview:toV];
// 动画
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromV.transform = CGAffineTransformTranslate(fromV.transform, -[UIScreen mainScreen].bounds.size.width,0);// containView.frame.size.height
toV.transform = CGAffineTransformTranslate(toV.transform, -[UIScreen mainScreen].bounds.size.width, 0);
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}else if (toVc.index < fromVc.index) {
toV.frame = CGRectMake(- [UIScreen mainScreen].bounds.size.width, 0, containView.frame.size.width, containView.frame.size.height);
[containView addSubview:toV];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromV.transform = CGAffineTransformTranslate(fromV.transform, [UIScreen mainScreen].bounds.size.width,0);
toV.transform = CGAffineTransformTranslate(toV.transform, [UIScreen mainScreen].bounds.size.width, 0);
} completion:^(BOOL finished) {
[fromV removeFromSuperview];
[transitionContext completeTransition:YES];
}];
}
}
@end
# 这里面就涉及到前面讲的 1.From和To的关系,2.Presented和Presenting的关系.在2.1的底部有介绍
小结:
所谓的自定义转场动画,就是把系统默认的换成我们自己写的而已,关键就是在这些协议里.理清控制器与协议的关系.
简单的画了一个结构图
附: 以UITabBarController为例的简单转场动画demo地址 gitHub地址