無意間看到一個彩色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]; }]; }