前言:最近发现一个自定义TabBar的效果很有趣,就研究下实现思路。
演示GIF
从源APP演示的GIF中可以看出点击底部TabBar Item,有两个动画效果:Item闪烁抖动和页面之前的横向切换。后者给人视觉上感觉类似父子控制器点击切换页面的效果。
分析:
虽然我上面用了类似来比喻,但是很显然我们不会用父子控制器的原理来这样实现,举个简单的例子,如果我们这样做了势必要自定义底部TabBar和父子控制器中add的childVc做绑定。这样的确可以去只是会很麻烦或者说没有下面我要分享的方法来的简单。
着手点:应该去TabBarVc的代理方法中去寻找'UITabBarControllerDelegate'
动画一:闪烁抖动
根据GIF的演示上看出是通过点击来产生的,在
UITabBarControllerDelegate
找到didSelectViewController
方法找到获取点击TabBar Item的内部UITabBarButton
给UITabBarButton做核心动画
#pragma mark - 获取UIControl
- (UIControl *)getTabBarButton{
NSMutableArray *tabBarButtons = [[NSMutableArray alloc]initWithCapacity:0];
for (UIView *tabBarButton in self.tabBar.subviews) {
if ([tabBarButton isKindOfClass:NSClassFromString(@"UITabBarButton")]){
[tabBarButtons addObject:tabBarButton];
}
}
UIControl *tabBarButton = [tabBarButtons objectAtIndex:self.selectedIndex];
return tabBarButton;
}
#pragma mark - 点击核心动画
- (void)tabBarButtonClick:(UIControl *)tabBarButton
{
for (UIView *imageView in tabBarButton.subviews) {
if ([imageView isKindOfClass:NSClassFromString(@"UITabBarSwappableImageView")]) {
//需要实现的帧动画,这里根据自己需求改动
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"transform.scale";
animation.values = @[@1.0,@1.1,@0.9,@1.0];
animation.duration = 0.3;
animation.calculationMode = kCAAnimationCubic;
//添加动画
[imageView.layer addAnimation:animation forKey:nil];
}
}
}
在代理方法中调用
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
[self tabBarButtonClick:[self getTabBarButton]]; //点击tabBarItem动画代理
}
动画二:页面切换
切换,准确的说控制器的切换动画。正常可以联想到转场动画-
Transition
点击进入UITabBarControllerDelegate
里查看一下代理方法:发现果然有两个转场方法
!iOS 7.0
- (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController
interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController NS_AVAILABLE_IOS(7_0);
- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
animationControllerForTransitionFromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
区分两个方法的就是是否含交互。直观翻译:
interactionControllerForAnimationController
就是带有交互的。而下面那个方法则不包含。
如上需求中我们只需要下面那个方法即可。方法很好理解,参数1:
tabBarController
参数2和参数3:转场两个控制器 fromVC和toVC
返回转场动画:UIViewControllerAnimatedTransitioning
在代理方法中获取到tabBar下的controllers
自定义切换动画
AnimatedTransitioning
根据TabBar角标判断切换方向,显示动画
自定义切换动画
#import <UIKit/UIKit.h>
@interface DCSlideBarViewAnimation : NSObject <UIViewControllerAnimatedTransitioning>
/**
初始化
@param targetEdge 方向
@return 转场
*/
- (instancetype)dcInitWithSlideTargetEdge:(UIRectEdge)targetEdge;
@end
#import "DCSlideBarViewAnimation.h"
#import <UIKit/UIKit.h>
@interface DCSlideBarViewAnimation()
/**
typedef NS_OPTIONS(NSUInteger, UIRectEdge) {
UIRectEdgeNone = 0,
UIRectEdgeTop = 1 << 0,
UIRectEdgeLeft = 1 << 1,
UIRectEdgeBottom = 1 << 2,
UIRectEdgeRight = 1 << 3,
UIRectEdgeAll = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight
} NS_ENUM_AVAILABLE_IOS(7_0);
*/
@property (nonatomic, assign) UIRectEdge targetEdge;
@end
@implementation DCSlideBarViewAnimation
#pragma mark - 初始化
- (instancetype)dcInitWithSlideTargetEdge:(UIRectEdge)targetEdge
{
if ([self init]) {
_targetEdge = targetEdge;
}
return self;
}
#pragma mark - 事件间隔
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.15;
}
#pragma mark - <UIViewControllerAnimatedTransitioning>
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
UIViewController *fromVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
CGRect fromFrame = [transitionContext initialFrameForViewController:fromVc];
CGRect toFrame = [transitionContext finalFrameForViewController:toVc];
CGVector offset;
if (self.targetEdge == UIRectEdgeLeft){
offset = CGVectorMake(-1.f, 0.f);
}else if (self.targetEdge == UIRectEdgeRight){
offset = CGVectorMake(1.f, 0.f);
}
fromView.frame = fromFrame;
toView.frame = CGRectOffset(toFrame,toFrame.size.width * offset.dx * -1,toFrame.size.height * offset.dy * -1);
[transitionContext.containerView addSubview:toView];
NSTimeInterval transitionDuration = [self transitionDuration:transitionContext]; //获取专场事件
[UIView animateWithDuration:transitionDuration animations:^{
fromView.frame = CGRectOffset(fromFrame,fromFrame.size.width * offset.dx,fromFrame.size.height * offset.dy);
toView.frame = toFrame;
} completion:^(BOOL finished) {
//告诉专场动画结束 !transitionContext.transitionWasCancelled,是为了后续手势交互,避免手势取消时,造成卡顿现象。
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
@end
代理实现
- (id<UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
//页面切换代理
NSArray *tabViewControllers = tabBarController.viewControllers;
if ([tabViewControllers indexOfObject:toVC] > [tabViewControllers indexOfObject:fromVC]) { // left
return [[DCSlideBarViewAnimation alloc] dcInitWithSlideTargetEdge:UIRectEdgeLeft];
}else{ // right
return [[DCSlideBarViewAnimation alloc] dcInitWithSlideTargetEdge:UIRectEdgeRight];
}
}