抛出问题
常用的viewController
(VC
)切换方式有以下三种:
- 导航切换
- 模态切换
- Tab切换
在VC
切换的时候,为了使过度更自然,系统提供了切换动画,例如Push
切换会有animated
属性。系统提供的动画效果,并不总是能满足我们需求,如果想要自定义切换动画该怎么处理?
问题猜测
VC
切换的时候,设置动画效果,默认执行的是系统动画,所以需要告诉程序在什么情况下执行自定义动画,而不是执行系统动画,自定义动画的触发条件是什么?
确定了自定义动画的触发条件,我们就需要执行自定义动画效果,如何写自定义动画效果?
- 自定义动画的触发条件是什么?
- 如何执行自定义动画?
API分析
上面绕了一圈,绕出两个问题,先看第一个,自定义动画的触发条件是什么?
- UIViewControllerTransitioningDelegate
- UINavigationControllerDelegate
- UITabBarControllerDelegate
在切换的时候,系统会向实现了这些接口的对象询问是否使用自定义切换效果。比如自定义Present
动画,VC
遵守了UIViewControllerTransitioningDelegate
协议并实现了协议方法,就会执行自定义动画效果,自定义Push
动画效果,遵守UINavigationControllerDelegate
协议并实现协议相关方法,就会执行Push的自定义动画。
找到了自定义动画的触发条件,该如何执行执行自定义动画效果呢?
接下来隆重登场的是 UIViewControllerAnimatedTransitioning
,该协议有两个方法:
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
第一个方法是动画执行周期,第二个方法是执行动画的具体内容,我们可以看一下方法二中的transitionContext
,使用该属性可以获取执行动画的相关视图:
-(UIViewController *)viewControllerForKey:(NSString *)key; 获取VC
-(UIView *)containerView; 执行动画的容器
-(CGRect)initialFrameForViewController:(UIViewController *)vc; VC初始位置
-(CGRect)finalFrameForViewController:(UIViewController *)vc; VC切换后位置
-(void)completeTransition:(BOOL)didComplete;
说的比较晦涩,图片为简化流程,图一为整体思路,图二为具体动画过程。
1.png 2.png
手势交互
在使用导航Pop上一级界面的时候,可以在屏幕左侧边缘手势滑动返回。自定义动画如何进行手势操作?
系统已经为我们提供了相关的方法,UIPercentDrivenInteractiveTransition :
-(void)updateInteractiveTransition:(CGFloat)percentComplete 更新百分比
-(void)cancelInteractiveTransition
–(void)finishInteractiveTransition
为VC
添加手势,使用updateInteractiveTransition
控制进度。
应用场景
两个场景,场景一模拟push和pop动画,场景二模拟presen和dismiss动画。不使用系统动画,使用自定义实现下图动画效果和手势操作。
以导航的Push为例,讲解一个例子:
首先有一个从A界面跳转到B界面的Push动画,然后有一个从B界面返回A界面的Pop动画,以及返回的手势操作,系统默认的是左侧滑动返回,我们将手势改为手势操作区域为全屏。
首先写一个继承
NSObject
,遵守UIViewControllerAnimatedTransitioning
协议的对象PushPopAnimation
。设置动画执行时间为0.3
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return 0.3f;
}```
Push动画和Pop动画:
`viewControllerForKey`可以获取相关`VC`,以及`VC`上的子视图。
`containerView`是执行动画容器,执行动画操作要在容器内进行。
有了动画容器以及动画对象,下一步就是执行动画效果,Push和Pop主要用到的主要是位移操作。
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
BOOL isPushing = (_aniStyle == AnimationPushStyle);
if (isPushing) {
[self animationPush:transitionContext];
} else {
[self animationPop:transitionContext];
}
}
-(void)animationPush:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containView = [transitionContext containerView];
[containView addSubview:toVC.view];
CGRect visibleFrame = containView.frame;
CGRect rightFrame = CGRectMake(visibleFrame.size.width, visibleFrame.origin.y, visibleFrame.size.width, visibleFrame.size.height);
CGRect leftFrame = CGRectMake(-60, visibleFrame.origin.y, visibleFrame.size.width, visibleFrame.size.height);
toVC.view.frame = rightFrame;
fromVC.view.frame = visibleFrame;
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
toVC.view.frame = visibleFrame;
fromVC.view.frame = leftFrame;
} completion:^(BOOL finished) {
if ([transitionContext transitionWasCancelled]) {
[toVC.view removeFromSuperview];
}
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
-(void)animationPop:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containView = [transitionContext containerView];
[containView addSubview:toVC.view];
[containView bringSubviewToFront:fromVC.view];
CGRect visibleFrame = containView.frame;
CGRect rightFrame = CGRectMake(visibleFrame.size.width, visibleFrame.origin.y, visibleFrame.size.width, visibleFrame.size.height);
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
toVC.view.frame = visibleFrame;
fromVC.view.frame = rightFrame;
} completion:^(BOOL finished) {
if ([transitionContext transitionWasCancelled]) {
[toVC.view removeFromSuperview];
}
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
添加手势支持继承`UIPercentDrivenInteractiveTransition`
-
(instancetype)initWithGestrueForController:(UIViewController *)controller {
self = [super init];
if (self) {
_fullBackVC = controller;
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handleGesture:)];
[_fullBackVC.view addGestureRecognizer:gesture];
}
return self;
} -
(void)handleGesture:(UIPanGestureRecognizer *)gesture {
CGPoint translation = [gesture translationInView:gesture.view.superview];
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
self.interacting = YES;
[_fullBackVC.navigationController popViewControllerAnimated:YES];
break;
case UIGestureRecognizerStateChanged: {
CGFloat dis = translation.x / [UIScreen mainScreen].bounds.size.width;
dis = fminf(fmaxf(dis, 0.0), 1.0);
_isComplete = dis >= 0.5;
[self updateInteractiveTransition:dis];
break;
}
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateEnded:
self.interacting = NO;
if (!self.isComplete || gesture.state == UIGestureRecognizerStateCancelled) {
[self cancelInteractiveTransition];
} else {
[self finishInteractiveTransition];
}
break;
default:
break;
}
}
#### Demo地址
[Demo地址](https://github.com/royblog/YGTransitionAnimation)
网友评论