前言

最近根据产品的需求,要求实现tabBar的动画,自己之前做过类似的,纯自定义tabBar来加载想要的动画。这次的需求里还添加了一个plusButton,类似闲鱼的tabBar闲鱼

所以基于CYLTabBarController实现了更加方便定制的tabBar,然后加上自己的动画效果。最后的效果如下(当然,这是参考Demo):

效果图

先简单介绍一下CYLTabBarController的使用

  • 由于需要手动修改一些方法,所以不建议使用cocoapods,而是手动集成
  • 接下来初始化TabBarController,这里参考CYLTabBarController采取Config的方式。
    CYLTabBarControllerConfig.h
    CYLTabBarControllerConfig.m
    这里只显示了部分,详细的请参考Demo。
  • 自定义PlusButton。 需要继承CYLPlusButton,以及添加协议CYLPlusButtonSubclassing。定制想要的样式。
  • AppDelegate中。

(方便大家直接可以看,所以这里放的是截图,代码在Demo里都有)

接下来讲述一下动画的定制。

翻转动画

我们想要实现的效果是水平翻转动画。简单而言,对于实现翻转动画,方式有很多。

使用Pop

Facebook的Pop动画是很强大的,遇到想要实现动画的地方第一反应就是它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POPBasicAnimation *rotateAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerRotationY];
rotateAnimation.fromValue = @(0);
rotateAnimation.toValue = @(M_PI * 2);
rotateAnimation.duration = 0.6; // 0.5 0.3
[view.layer pop_addAnimation:rotateAnimation forKey:@"layerRotateAnimation"];
[rotateAnimation setCompletionBlock:^(POPAnimation *animation, BOOL finished) {
if (finished) {
[UIView animateWithDuration:0.3 animations:^{
// Pop 选择动画有时会发生动画停止的时候位置有稍微偏移的情况,所以最好在动画结束后恢复一下
view.transform = CGAffineTransformIdentity;
} completion:nil];
}

}];

使用CABasicAnimation

系统动画,比较直接,这里同样也可以使用关键帧动画,可以做得更加自然一点

1
2
3
4
5
6
7
8
9
10
11
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];

// 设定动画选项
rotateAnimation.duration = 1.2; // 持续时间
rotateAnimation.repeatCount = 1; // 重复次数

// 设定旋转角度
rotateAnimation.fromValue = @(0); // 起始角度
rotateAnimation.toValue = @(M_PI * 2); // 终止角度

[view.layer addAnimation:rotateAnimation forKey:@"rotateAnimation"];

使用transform动画

1
2
3
4
5
6
7
8
9
[UIView animateWithDuration:0.32 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
view.layer.transform = CATransform3DMakeRotation(M_PI, 0, 1, 0);
} completion:nil];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.70 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0.2 options:UIViewAnimationOptionCurveEaseOut animations:^{
view.layer.transform = CATransform3DMakeRotation(2 * M_PI, 0, 1, 0);
} completion:nil];
});

我采用的是第三种,其他二者都是可以的。之所以写得有点繁琐是为了看起来更加自然一点。

tabBar中添加动画

tabBar的自定义过程中我采取了CYLTabBarController的,另外还可以完全自定义,完全自定义的话,所有的tabBarItem都是自定义的Button,动画直接在event中添加即可,这里就不详述,有兴趣的话可以私下交流一下。

这里来讲述CYLTabBarController的tabBar动画。
在CYLTabBar中的

1
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;

添加位置如下:

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
32
33
34
35
36
37
38
39
40
41
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
BOOL canNotResponseEvent = self.hidden || (self.alpha <= 0.01f) || (self.userInteractionEnabled == NO);
if (canNotResponseEvent) {
return nil;
}
if (!CYLExternPlusButton && ![self pointInside:point withEvent:event]) {
return nil;
}
if (CYLExternPlusButton) {
CGRect plusButtonFrame = self.plusButton.frame;
BOOL isInPlusButtonFrame = CGRectContainsPoint(plusButtonFrame, point);
if (!isInPlusButtonFrame && (point.y < 0) ) {
return nil;
}
if (isInPlusButtonFrame) {
self.times ++;
if (self.times % 2 == 0) {
// 这里添加动画
[self addRotateAnimationOnPlusButton];
}
return CYLExternPlusButton;
}
}
NSArray *tabBarButtons = self.tabBarButtonArray;
if (self.tabBarButtonArray.count == 0) {
tabBarButtons = [self tabBarButtonFromTabBarSubviews:self.subviews];
}
for (NSUInteger index = 0; index < tabBarButtons.count; index++) {
UIView *selectedTabBarButton = tabBarButtons[index];
CGRect selectedTabBarButtonFrame = selectedTabBarButton.frame;
if (CGRectContainsPoint(selectedTabBarButtonFrame, point)) {
self.times ++;
if (self.times % 2 == 1) {
// 这里添加动画
[self addRotateAnimation:selectedTabBarButton];
}
return selectedTabBarButton;
}
}
return nil;
}

动画 上述两个位置加的动画实现一样,区分开来方便分开定制

1
2
3
4
5
6
7
8
9
10
11
12
- (void)addRotateAnimationOnPlusButton {
// 这里动画
[UIView animateWithDuration:0.32 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
_plusButton.layer.transform = CATransform3DMakeRotation(M_PI, 0, 1, 0);
} completion:nil];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.70 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0.2 options:UIViewAnimationOptionCurveEaseOut animations:^{
_plusButton.layer.transform = CATransform3DMakeRotation(2 * M_PI, 0, 1, 0);
} completion:nil];
});
}

由于hit的响应比较频繁,所以采取了折半。

重要提示

在设置tabBar的过程中,如果没有设置背景图片,即为透明的话,动画是正常的。但是如果为了需要设置了背景图片,比如:

1
[[UITabBar appearance] setBackgroundImage:[UIImage imageNamed:@"tab_bar"]];

那么动画会有明显的异常。

很明显的异常。这个问题我想了挺长时间,开始一直以为是我动画方法的问题,所以尝试了很多动画,但都一样。最后和别人讨论了一下问题的原因,弄了个解决方法。
背景与按钮图片处于同一层次,当按钮图片旋转时,转轴就在背景图上,所以会有一部分在背景图之下 所以会有一半被遮住的情况出现。

两个在同一图层,所以动画出现了异常。那么如何解决呢?

  • 可以自定义UIimageView放在tabBar上代替tabBarItem,然后将imageView.layer.zPosition = 最大图片宽度
  • 找到tabbar中放置图片的imageView,调整zPosition

我这里采取了第一种方法。

1
2
3
for (UIView *tabBarButton in self.tabBar.subviews) {
tabBarButton.layer.zPosition = 65.f / 2;//最大图片宽度的一半
}

还有一点,这个设置需要放在TabBarViewController的viewWillAppear方法中

1
2
3
4
5
6
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
for (UIView *tabBarButton in self.tabBar.subviews) {
tabBarButton.layer.zPosition = 65.f / 2;//最大图片宽度的一半
}
}

为了定制方便,建议大家继承CYLTabBarController,使用自己的,添加功能,以及这个设置。

详情参考Demo,希望大家指出我的不足以及改进之处,共同学习进步

附github地址:
https://github.com/hllGitHub/CYLTabBarController
也可以加入群:(139852091)Demo不用写,说就行 一起交流。