無意間看到一個彩色TabBar切換的設計圖,感覺很不錯,有空就把他實現了。
環境信息
Mac OS X 10.10.4
Xcode 6.4
iOS 8.4
效果圖:

源碼下載地址:
https://github.com/saitjr/TColorfulTabBar.git
一、實現分析
看到這個彩色切換效果的時候,我第一個反應就是在TabBar上有一個彩色的View,然後每個色塊的顯示都是通過mask來顯示的。最終,我的具體實現也是根據這個思路來的。
1. 設計思想
為了減少侵入性(耦合),我采取的是繼承UITabBar來實現,所以要集成的時候,只需要將系統的TabBar換成我寫的TColorfulTabBar就可以了。
2. 視圖層級圖

二、效果實現
1. 添加彩色視圖colorfulView
TColorfulTabBar.m
- (void)setupColorView {
// 初始化彩色視圖,並將它加在tabbar上
UIView *colorView = [[UIView alloc] initWithFrame:self.bounds];
[self addSubview:colorView];
self.colorfulView = colorView;
// 彩色視圖的五種顏色,這是一個UIColor數組,
NSArray *colors = self.itemColors;
CGFloat itemWidth = self.bounds.size.width / self.itemCount;
for (int i = 0; i < self.itemCount; i ++) {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(itemWidth * i, 0, itemWidth, self.bounds.size.height)];
view.backgroundColor = colors[i];
[self.colorfulView addSubview:view];
}
}2. 添加彩色視圖的遮罩colorfulMaskView
之所以這裡的遮罩使用的是UIView而不是CAShapeLayer或者CALayer,原因如下:
遮罩是矩形,沒必要使用路徑,所以不用CAShapeLayer
通過設計圖可以看到,在位移動畫之前,有一個遮罩單向變寬(向左或向右)的效果。如果使用CALayer,那麼改變bounds的時候,是雙向改變,想要單向就必須加錨點,太麻煩
如果使用UIView,那麼向右變寬只需要x不變,width變長就行;向左變寬只需要x-value,width+value就行,要方便的多
最後一點就是在做動畫的時候,Layer要用到CABaseAnimation,然而UIView使用UIView的Animation就行
在設置遮罩的時候,將colorfulMaskView的layer設置為colorfulView的mask就可以了。
代碼實現如下:
TColorfulTabBar.m
- (void)setupMaskLayer {
// 獲取每個item的寬度
CGFloat itemWidth = self.bounds.size.width / self.itemCount;
// 初始化colorMaskView,並將colorMaskView的layer設置成彩色視圖的遮罩
UIView *colorMaskView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, itemWidth, self.bounds.size.height)];
colorMaskView.backgroundColor = [UIColor blackColor];
self.colorfulMaskView = colorMaskView;
self.colorfulView.layer.mask = self.colorfulMaskView.layer;
}3. 設置代理,獲取當前點擊的下標
UITabBar並沒有獲取點擊下標的接口,但是UITabBarDelegate有,這個協議默認被UITabBarController遵守。為了降低耦合性,采用UITabBar自己獲取下標,所以,需要UITabBar自己實現自己的delegate。
值得注意的是,不能在UITabBar的初始化方法中去設置self.delegate = self,因為即使設置了,也會被UITabBarController覆蓋。最終解決方案是當UITabBar加載到父視圖上是,在修改delegate為self。
- (void)didMoveToSuperview {
[super didMoveToSuperview];
self.delegate = self;
}因為每次的移動位置與方向都和上次的下標與這次的下標有關,所以需要使用到屬性來記錄這兩個下標的值。
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
NSInteger index = [self.items indexOfObject:item];
self.fromeIndex = self.toIndex;
self.toIndex = index;
// 拿到下標以後執行動畫
[self animation];
}4. 移動遮罩,顯示不同的色塊
在做動畫的時候,需要考慮到效果的平滑性。整個動畫有兩個動畫組成,一個是寬度放大的動畫,一個是縮小到原來大小和位移動畫。整個動畫是EaseInOut的效果,所以拆開來看,就應該第一個動畫EaseIn,第二個動畫EaseOut。
一圖勝千言:

- (void)animation {
CGFloat itemWidth = self.bounds.size.width / self.itemCount;
// 為了效果看起來更平滑,所以根據兩次下標的差值來計算需要放大的距離
CGFloat extraWidth = ABS(self.toIndex - self.fromeIndex) * itemWidth / 4;
// 放大的大小
CGRect scaleFrame = CGRectMake(self.colorfulMaskView.x, 0, itemWidth + extraWidth, self.bounds.size.height);
// 最終大的大小與位置
CGRect toFrame = CGRectMake(self.toIndex * itemWidth, 0, itemWidth, self.bounds.size.height);
// 如果是向左移動,那修改x軸的坐標
if (self.fromeIndex > self.toIndex) {
scaleFrame = CGRectMake(self.colorfulMaskView.x - extraWidth, 0, itemWidth + extraWidth, self.bounds.size.height);
}
// 兩個動畫加起來是一個EaseInOut的效果,所以第一個動畫就應該是EaseIn,第二個動畫是EaseOut
// 先進行放大
[UIView animateWithDuration:ANIMATION_DURATION delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.colorfulMaskView.frame = scaleFrame;
} completion:^(BOOL finished) {
// 再進行縮小和位移
[UIView animateWithDuration:ANIMATION_DURATION delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.colorfulMaskView.frame = toFrame;
} completion:NULL];
}];
}