在一次App版本迭代中,🏹🐔🦁提出引导用户以后去点击某个按钮的动画:进入的时候,从A界面
点击按钮的位置,B界面
从小到大放大最终充满屏幕显示;离开的时候,A界面
从大到小缩小,最终消失在A界面
按钮的中心。
如果B界面
比较简单可以考虑把A界面
放在B界面
上,使用UIView动画来实现。但是这个B界面
是一个控制器,内含各种操作,所以更好的方法是使用控制器转场动画
。
转场动画协议
转场动画首先需要实现动画协议UIViewControllerAnimatedTransitioning
中的两个方法transitionDuration:
(动画时长)和animateTransition:
(具体动画内容)
为了方便阅读和二次使用,选择新建一个继承NSObject的类,导入UIKit,然后遵守动画协议UIViewControllerAnimatedTransitioning
,然后实现两个必须实现的协议方法:动画时长协议方法transitionDuration:
和 具体动画效果协议方法animateTransition:
1 | // TransitionAnimation.h |
1 | // TransitionAnimation.m |
转场动画协议方法的实现
动画具体实现主要在于动画原型是如何变化的,比如在引言中🏹🐔🦁描述的转场动画效果。在iOS中推出控制器是使用NavgationController和Modal(Present)两种方法,但是动画的具体实现是一样的,只是最后调用转场动画的方式不同。为了区分弹出方式,所以随意想了个动画来实现。
完善TransitionAnimation
由于要实现多个转场动画,为了方便后续扩展,那就定了一个枚举来区分,在转场动画协议方法transitionDuration:
中根据枚举值跳到不同的转场动画实现中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// TransitionAnimation.h
typedef NS_ENUM(NSUInteger, AnimationType) {
AnimationTypeModalPresent = 0, //Modal弹出动画
AnimationTypeModalDismiss, //Modal消失动画
AnimationTypeNavPush, //Nav Push动画
AnimationTypeNavPop //Nav Pop动画
};
@interface TransitionAnimation : NSObject<UIViewControllerAnimatedTransitioning>
/**
* 初始化方法过场动画
*/
+ (instancetype)animationWithAnimationType:(AnimationType)animationType;
@end
1 | // TransitionAnimation.m |
具体动画实现
🏹🐔🦁提出的转场动画
🏹🐔🦁提出的转场动画效果:
进入部分动画
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31- (void)presentAnimationTransition:(id)transitionContext{
/**
* 使用 transitionContext(转场上下文)获取相关界面
* viewForKey:方法 可以直接获取View但需要iOS8以上
* UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
* UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
* 为兼容iOS7,使用viewControllerForKey:方法 先获取控制器再获取对应控制器的view
*/
UIView *containerView = [transitionContext containerView];
UIView *fromView = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view;
UIView *toView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;
// 将新的界面加到容器上
[containerView addSubview:toView];
// 设置动画内容
toView.frame = [UIScreen mainScreen].bounds;
toView.transform = CGAffineTransformMakeScale(0.025, 0.0125);
toView.center = (CGPoint){ScreenWidth-30, 45};
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toView.center = (CGPoint){ScreenWidth/2.0f, ScreenHeight/2.0f};
toView.transform = CGAffineTransformMakeScale(1.0, 1.0);
} completion:^(BOOL finished) {
//完成的时候一定要调用让过程动画完成,否则过场动画无法完成(新界面出来了但,用户无法继续操作)
BOOL success = ![transitionContext transitionWasCancelled];
[transitionContext completeTransition:success];
}];
}离开部分动画
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22- (void)dismissAnimationTransition:(id)transitionContext{
UIView *containerView = [transitionContext containerView];
UIView *fromView = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view;
UIView *toView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;
// 先把原来的视图添加回去
[containerView insertSubview:toView atIndex:0];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromView.center = (CGPoint){ScreenWidth-30,45};
fromView.transform = CGAffineTransformMakeScale(0.025, 0.0125);
} completion:^(BOOL finished) {
BOOL success = ![transitionContext transitionWasCancelled];
// 注意要把视图移除
[fromView removeFromSuperview];
[transitionContext completeTransition:success];
}];
}
随意想的动画
为了区分跳转方式,随意做了一个不一样的动画用来给NavgationController推出界面转场动画使用,大致效果:
进入部分动画代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22- (void)pushAnimationTransition:(id)transitionContext{
UIView *containerView = [transitionContext containerView];
UIView *toView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;
[containerView addSubview:toView];
toView.frame = [UIScreen mainScreen].bounds;
toView.transform = CGAffineTransformMakeScale(1, CGFLOAT_MIN);
toView.alpha = 0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toView.transform = CGAffineTransformMakeScale(1.0, 1.0);
toView.alpha = 1;
} completion:^(BOOL finished) {
BOOL success = ![transitionContext transitionWasCancelled];
[transitionContext completeTransition:success];
}];
}离开部分动画代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23- (void)popAnimationTransition:(id)transitionContext{
UIView *containerView = [transitionContext containerView];
UIView *fromView = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view;
UIView *toView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;
[containerView insertSubview:toView atIndex:0];
[UIView animateWithDuration:[self transitionDuration:transitionContext]*2/3 animations:^{
fromView.transform = CGAffineTransformMakeScale(0.09, 0.16);
} completion:^(BOOL finished) {
[UIView animateWithDuration:[self transitionDuration:transitionContext]/3 animations:^{
fromView.center = (CGPoint){fromView.center.x, ScreenHeight+fromView.frame.size.height/2.0f};
} completion:^(BOOL finished) {
BOOL success = ![transitionContext transitionWasCancelled];
[fromView removeFromSuperview];
[transitionContext completeTransition:success];
}];
}];
}
应用动画效果
Modal方式弹出控制器
- 遵守协议
控制器A需要遵守UIViewControllerTransitioningDelegate
协议 - 设置代理对象
设置控制器B对象的transitioningDelegate
为控制器A - 实现协议方法
在控制器A中实现animationControllerForPresentedController: presentingController:sourceController:
(Modal弹出动画协议方法) 和animationControllerForDismissedController:
(Modal消失动画协议方法),在相应的方法中返回遵守并实现UIViewControllerAnimatedTransitioning协议方法的对象
Nav方式推出控制器
- 遵守协议
控制器A需要遵守UINavigationControllerDelegate
协议 - 设置代理对象
在控制器A中将navigationController的delegate设定为控制器A - 实现协议方法
实现协议方法navigationController: animationControllerForOperation: fromViewController:toViewController:
,在该方法中区分用户操作的是Push还是Pop从而返回相应的遵守并实现UIViewControllerAnimatedTransitioning协议方法的对象