前言
最近新做的一个项目,里面出现了页面 Push
之后的新页面导航栏颜色改变的情况.但是仅仅用系统提供的 api, Push
时候的动画特别难看.最终用 透明的导航栏背景
+假背景视图
的方式稍微做出了一些改善.效果如图:
实际就是把
NavigationBar
的背景颜色设置为透明,然后在控制器的主视图上添加一个跟导航栏一样大小的 view
来当做背景.
设置 navigationBar 背景为透明
// 设置 navigationBar 背景为透明
[self.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
// 隐藏 navigationBar 底部分割线
self.navigationBar.shadowImage = [UIImage new];
添加背景视图,来营造一个错觉,来让用户认为 navigationBar 就是背景视图的颜色.
CGSize screenSize = [UIScreen mainScreen].bounds.size;
self.navBackView_zhk = [[UIView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, 64)];
[self.view addSubview:_navBackView_zhk];
本来感觉已经挺满意了,但是后来发现支付宝里面的页面过渡,导航栏背景颜色的渐变过渡更加的流畅.
于是决定研究一下.
全屏滑动返回手势
既然决定做导航栏背景过渡渐变效果,为了方便测试和突显这个流畅的效果.就顺便把全屏滑动返回手势
也顺便做了吧.
先放最终效果图:
1.添加需要用到的属性
@interface BaseViewController () <UINavigationControllerDelegate>
// 导航栏背景视图
@property (nonatomic, strong) UIView *navBackView_zhk;
// 交互动画控制对象(主要用于全屏手势返回过程动画的控制)
@property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactiveTransition_zhk;
@end
2.添加拖动手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_zhk_popGestureAction:)];
[self.view addGestureRecognizer:pan];
3.手势响应方法
当手势发生时候调用[self.navigationController popViewControllerAnimated:YES]
进行Pop
.
手势拖动过程中不断调用[_interactiveTransition_zhk updateInteractiveTransition:progress]
来更新过渡动画的进度.
当手势取消或者结束的时候,判断progress
,如果>0.5
则直接完成过渡动画,否则取消过渡动画.
- (void)_zhk_popGestureAction:(UIPanGestureRecognizer *)pan {
// 计算动画进度百分比
CGFloat offset = [pan translationInView:self.view].x;
CGFloat progress = offset / [UIScreen mainScreen].bounds.size.width;
if (pan.state == UIGestureRecognizerStateBegan) {
self.interactiveTransition_zhk = [[UIPercentDrivenInteractiveTransition alloc] init];
// pop
[self.navigationController popViewControllerAnimated:YES];
}else if (pan.state == UIGestureRecognizerStateChanged) {
// 更新动画进度
[_interactiveTransition_zhk updateInteractiveTransition:progress];
}else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled) {
// 大于 50% 完成过渡动画, 否则取消
if (progress > 0.5) {
[_interactiveTransition_zhk finishInteractiveTransition];
}else {
[_interactiveTransition_zhk cancelInteractiveTransition];
}
self.interactiveTransition_zhk = nil;
}
}
4.实现UINavigationControllerDelegate
代理方法
传递交互动画控制对象_interactiveTransition_zhk
和动画对象NavBackAnimate
#pragma mark - UINavigationController delegate
// 返回交互过渡动画控制对象
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController {
return _interactiveTransition_zhk;
}
// 返回过渡动画对象
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC {
return [NavBackAnimate animationWithOperation:operation];
}
动画对象实现
动画对象需要接受UIViewControllerAnimatedTransitioning
协议
@interface NavBackAnimate : NSObject <UIViewControllerAnimatedTransitioning>
+ (instancetype)animationWithOperation:(UINavigationControllerOperation)operation;
@end
UIViewControllerAnimatedTransitioning
协议:
// 返回动画完成需要的时间
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
// 过渡动画的定义都在这里
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
// 如果过渡动画是可以被中断的,则可以实现这个方法( iOS 10 之后)
- (id <UIViewImplicitlyAnimating>) interruptibleAnimatorForTransition:(id <UIViewControllerContextTransitioning>)transitionContext NS_AVAILABLE_IOS(10_0);
// 过渡动画结束时候调用
- (void)animationEnded:(BOOL) transitionCompleted;
最主要的是- (void)animateTransition:(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 {
BaseViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
BaseViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// nav 背景是否需要过渡动画(颜色不同则需要,否则不需要)
BOOL navBackNeedTransition = ![fromVC.navBackColor_zhk isEqual:toVC.navBackColor_zhk];
// 获取呈现过渡动画的视图(容器)
UIView *containerView = [transitionContext containerView];
CGSize screenSize = [UIScreen mainScreen].bounds.size;
//
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, screenSize.height)];
// Push 时候的动画设置(动画开始状态设置)
if (_operation == UINavigationControllerOperationPush) {
[containerView addSubview:imageView];
[containerView addSubview:toVC.view];
imageView.image = [self snapshot:fromVC.view];
imageView.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);
toVC.view.frame = CGRectMake(screenSize.width, 0, screenSize.width, screenSize.height);
// 设置阴影
toVC.view.layer.shadowColor = [UIColor grayColor].CGColor;
toVC.view.layer.shadowOffset = CGSizeMake(-3, 0);
toVC.view.layer.shadowOpacity = .5;
// Pop 时候过渡动画的设置(动画开始状态设置)
}else if (_operation == UINavigationControllerOperationPop) {
[containerView addSubview:toVC.view];
[containerView addSubview:imageView];
imageView.image = [self snapshot:fromVC.view];
imageView.frame = fromVC.view.bounds;
toVC.view.frame = CGRectMake(-screenSize.width / 3, 0, screenSize.width, screenSize.height);
imageView.layer.shadowColor = [UIColor grayColor].CGColor;
imageView.layer.shadowOffset = CGSizeMake(-3, 0);
imageView.layer.shadowOpacity = .5;
}
// navigationBar 底部假背景视图
// 背景的渐变过渡将会通过 backView 背景的渐变来呈现出 navigationBar 背景渐变的错觉
UIView *backView = nil;
if (navBackNeedTransition) {
backView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, 64)];
backView.backgroundColor = fromVC.navBackColor_zhk;
[containerView addSubview:backView];
}
[UIView animateWithDuration:Duration animations:^{
if (_operation == UINavigationControllerOperationPush) {
// Push 过渡动画结束时候状态设置
imageView.frame = CGRectMake(-screenSize.width / 3, 0, screenSize.width, screenSize.height);
toVC.view.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);
}else if (_operation == UINavigationControllerOperationPop) {
// Pop 过渡动画结束时候状态设置
imageView.frame = CGRectMake(screenSize.width, 0, screenSize.width, screenSize.height);
toVC.view.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);
}
// backView 目标背景色
backView.backgroundColor = toVC.navBackColor_zhk;
} completion:^(BOOL finished) {
// 动画结束时候调用
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
// imageView 如果不移除, 后面页面的交互将被遮挡
[imageView removeFromSuperview];
[backView removeFromSuperview];
}];
}
Push
和 Pop
的过渡动画实际上都是通过 toVC.view
和 从 fromVC.view
获取到的 image
也就是 imageView
这个容器进行一些列改变或者动作来呈现出页面切换这个动态.
最后放上 GitHub 地址,希望能帮到有需求的小伙伴.