最近專案改版裡,產品設計重新設計了tabbar動畫,旨在提升app的逼格。。。 設計圖是借鑑淘寶的tabBar:
找資料查詢下,還沒有相關開源的程式碼,好吧,那就自己開幹吧。
先拆解功能點:
- 自定義tabBar,高度56
- tab顯示:首頁tab在選中時是大logo 無文字,未選中時是圖片文字 ;其他tab未選中和選中都是圖片文字
- tab切換:tab之間相互點選切換選中時的縮小放大的動畫
- 當首頁滑動到一定距離時,首頁tab的大logo和小火箭執行切換動畫:
手勢上滑 - logo向下切換到小火箭; 手勢下滑 - 小火箭向上切換到logo;
開始各個擊破吧
1. 自定義tabBar,高度56
刪除系統tabBar,建立自定義tabBar,
@implementation WMTabBar
// 刪除系統tabbar的UITabBarButton
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView *view in self.subviews) {
if ([view isKindOfClass:NSClassFromString(@"UITabBarButton")]) {
[view removeFromSuperview];
}
}
}
// 根據配置資訊建立自定義tabBar
+ (instancetype)tabBarWithTitleArray:(NSArray *)titleArray imageArray:(NSArray *)imageArray selectedImageArray:(NSArray *)selectedImageArray {
WMTabBar *tabBar = [[WMTabBar alloc] init];
tabBar.titleArray = titleArray;
tabBar.imageArray = imageArray;
tabBar.selectedImageArray = selectedImageArray;
[tabBar setupUI];
return tabBar;
}
- (void)setupUI {
self.lastSelectIndex = 100;//預設為100
self.backgroundColor = [UIColor whiteColor];
for (int i = 0; i < self.titleArray.count; i++) {
CGFloat itemWidth = (kScreen_width/self.titleArray.count);
CGRect frame = CGRectMake(i*itemWidth, 0, itemWidth, 56);
WMTabBarItem *tabBarItem = [[WMTabBarItem alloc] initWithFrame:frame index:i];
tabBarItem.tag = i ;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tabBarItemClickAction:)];
[tabBarItem addGestureRecognizer:tap];
[self addSubview:tabBarItem];
[self.itemArray addObject:tabBarItem];
}
// 記錄選中index, 在其setter方法裡重寫邏輯
self.selectedIndex = 0;
}
<!-- 選中index 重寫setter方法,在裡面做tabBar的item的內容動態配置 -->
- (void)setSelectedIndex:(NSInteger )selectedIndex {
_selectedIndex = selectedIndex;
[self.itemArray enumerateObjectsUsingBlock:^(WMTabBarItem *tabBarItem, NSUInteger idx, BOOL * _Nonnull stop) {
// 當遍歷的idx=selectedIndex時,記錄選中狀態
BOOL selected = (idx == selectedIndex);
// 配置tabBarItem的內容資訊
[tabBarItem configTitle:self.titleArray[idx] normalImage:self.imageArray[idx] selectedImage:self.selectedImageArray[idx] index:idx selected:selected lastSelectIndex:self.lastSelectIndex];
// 當遍歷到最後一個時,賦值lastSelectIndex
if (idx == (self.itemArray.count-1)) {
self.lastSelectIndex = selectedIndex;
}
}];
}
複製程式碼
在WMTabBar裡仿照系統tabBar的點選方法,新增兩個wmTabBar代理方法,以滿足專案裡tabBar點選選中或點選tabBar時根據登入狀態進行是否選中等操作:
@protocol WMTabBarDelegate <NSObject>
/** 選中tabbar */
- (void)wmtabBar:(WMTabBar *)wmTabBar selectedWMTabBarItemAtIndex:(NSInteger)index;
/** 是否可選tabbar */
- (BOOL)wmtabBar:(WMTabBar *)wmTabBar shouldSelectedWMTabBarItemAtIndex:(NSInteger)index;
@end
複製程式碼
在WMTabBarController裡設定tabBar
// 重寫tabbar的frame,改變tabbar高度
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGRect frame = self.tabBar.frame;
if (frame.size.height != kTABBARHEIGHT) {
frame.size.height = kTABBARHEIGHT;
frame.origin.y = self.view.frame.size.height - frame.size.height;
self.tabBar.frame = frame;
}
}
//新增子模組
- (void)creatTabBarWithChildVCArray:(NSArray *)childVCArray
titleArray:(NSArray *)titleArray
imageArray:(NSArray *)imageArray
selectedImageArray:(NSArray *)selectedImageArray {
for (UIViewController *viewController in childVCArray) {
MPNavigationController *navigationController = [[MPNavigationController alloc] initWithRootViewController:viewController];
[self.controllersArray addObject:navigationController];
}
self.wmTabBar = [WMTabBar tabBarWithTitleArray:titleArray
imageArray:imageArray
selectedImageArray:selectedImageArray];
self.wmTabBar.tabBarDelegate = self;
[self setValue:self.wmTabBar forKeyPath:@"tabBar"];
self.viewControllers = self.controllersArray;
}
複製程式碼
2. tab顯示:首頁tab在選中時是大logo 無文字,未選中時是圖片文字 ;其他tab未選中和選中都是圖片文字
在tabBarItem裡建立顯示UI
@implementation WMTabBarItem
- (instancetype)initWithFrame:(CGRect)frame index:(NSInteger )index {
self = [super initWithFrame:frame];
if (self) {
[self addSubview:self.titleLabel];
[self addSubview:self.imageView];
[self addSubview:self.homeTabSelectedBgView];
self.imageView.frame = CGRectMake(self.bounds.size.width/2-14, 7, 28, 28);
self.titleLabel.frame = CGRectMake(0, CGRectGetMaxY(self.imageView.frame)+2, self.bounds.size.width, 14);
self.homeTabSelectedBgView.frame = CGRectMake(self.bounds.size.width/2-21, 7, 42, 42);
if (index == 0) {
// 當為首頁tab時,加上以下內容
[self addSubview:self.homeTabSelectedBgView];
// /** 第一種方案 */
// [self.homeTabSelectedBgView addSubview:self.homeTabAnimateImageView];
// self.homeTabAnimateImageView.frame = CGRectMake(0, 0, 32, 32);
// self.homeTabAnimateImageView.center = CGPointMake(self.homeTabSelectedBgView.frame.size.width/2, self.homeTabSelectedBgView.frame.size.height/2);
/** 第二種方案 */
[self.homeTabSelectedBgView addSubview:self.collectionView];
self.homeTabSelectedBgView.frame = CGRectMake(self.bounds.size.width/2-21, 7, 42, 42);
self.collectionView.frame = CGRectMake(0, 0, 42, 42);
self.collectionView.center = CGPointMake(self.homeTabSelectedBgView.frame.size.width/2, self.homeTabSelectedBgView.frame.size.height/2);
[self.collectionView registerClass:[WMTabBarItemCell class]
forCellWithReuseIdentifier:NSStringFromClass([WMTabBarItemCell class])];
}
}
return self;
}
複製程式碼
在tab點選事件裡處理:
// 當點選tab的item時,執行
- (void)configTitle:(NSString *)title normalImage:(NSString *)normalImage selectedImage:(NSString *)selectedImage index:(NSInteger)index selected:(BOOL)selected lastSelectIndex:(NSInteger )lastSelectIndex {
self.titleLabel.text = title;
// 當index == 0, 即首頁tab
if (index == 0) {
if (selected) {
[self.homeTabSelectedBgView setImage:[UIImage imageNamed:@"tabbar_home_selecetedBg"]];
self.homeTabSelectedBgView.hidden = NO;
YES;self.imageView.hidden = self.titleLabel.hidden = YES;
// /** 第一種方案 */
// [self.homeTabAnimateImageView setImage:[UIImage imageNamed:@"tabbar_home_selecetedLogo"]];
// /** 第二種方案 預設顯示第一個cell 所以不用在這裡設定圖片 */
// 如果本次點選和上次是同一個tab 都是第0個,則執行push動畫,否則執行放大縮小動畫
if (lastSelectIndex == index) {
if (self.flag) {
// 如果已經是火箭狀態,則點選切換logo,且發通知 讓首頁滑到頂部
[self pushHomeTabAnimationDown];
[[NSNotificationCenter defaultCenter] postNotificationName:@"kPushDownAnimationScrollTopNotification" object:nil];
}
}else {
[self animationWithHomeTab];
}
}else {
[self.imageView setImage:[UIImage imageNamed:normalImage]];
self.homeTabSelectedBgView.hidden = YES;
self.imageView.hidden = self.titleLabel.hidden = NO;
}
}else {
// 其他tab
self.homeTabSelectedBgView.hidden = YES;
self.imageView.hidden = self.titleLabel.hidden = NO;
if (selected) {
[self.imageView setImage:[UIImage imageNamed:selectedImage]];
self.titleLabel.textColor = [self colorFromHexRGB:@"18A2FF"];
// 如果本次點選和上次是同一個tab 則無反應,否則執行放大縮小動畫
if (lastSelectIndex != index) {
[self animationWithNormalTab];
}
}else {
[self.imageView setImage:[UIImage imageNamed:normalImage]];
self.titleLabel.textColor = [self colorFromHexRGB:@"575D66"];
}
}
}
複製程式碼
就是在tab點選事件裡做處理:
- 當index==0 即首頁tab時,根據是否是seleted狀態 來判斷顯示是大logo控制元件還是正常文字圖片控制元件。
- 其他tab時,隱藏大logo控制元件,只顯示圖片文字控制元件,再根據是否是seleted狀態 來判斷選中和未選中時的圖片及文字顏色配置。
3.tab切換:tab之間相互點選切換選中時的縮小放大的動畫
tab在相互點選切換選中時的縮小放大動畫,通過CABasicAnimation來實現:
/**
* tab之間切換動畫
*/
// 首頁tab縮小放大動畫
- (void)animationForHomeTab {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.duration = 0.2f;
animation.fromValue = [NSNumber numberWithFloat:0.5f];
animation.toValue = [NSNumber numberWithFloat:1.f];
[self.homeTabSelectedBgView.layer addAnimation:animation forKey:nil];
}
// 其他tab縮小放大動畫
- (void)animationForNormalTab {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.duration = 0.25f;
animation.fromValue = [NSNumber numberWithFloat:0.5f];
animation.toValue = [NSNumber numberWithFloat:1.f];
[self.imageView.layer addAnimation:animation forKey:nil];
[self.titleLabel.layer addAnimation:animation forKey:nil];
}
複製程式碼
效果如下:
4.當首頁滑動到一定距離時,首頁tab的大logo和小火箭執行切換動畫
觀察設計圖,可以得出根據首頁的滑動偏移量及滑動手勢,來確定滑動動畫方案:
在一次滑動行為中:當偏移量>閾值,且手勢上滑 - logo向下切換到小火箭;當偏移量<閾值,且手勢下滑 - 小火箭向上切換到logo;
首頁裡滑動代理實現:
// tabBar動畫 - 判斷滑動手勢,再根據手勢,偏移量 判斷動畫型別
- (void)tabBarAnimateWithScrollView:(UIScrollView *)scrollView {
CGFloat currentPostionOffsetY = scrollView.contentOffset.y;
if (currentPostionOffsetY > self.lastPositionOffestY) {
NSLog(@"手勢上滑");
// tabBar動畫
if (self.tabAnimateOnceScrollFlag) {
// 在一次滑動中,且currentPostionOffsetY>456,執行logo切換火箭?動畫
if ((currentPostionOffsetY > 456.f)) {
NSLog(@"執行-切換火箭");
[[AppLoginHandle sharedInstance].tabBarController pushHomeTabBarAnimationType:anmationDirectionUp];
self.tabAnimateOnceScrollFlag = NO;
}
}
}else {
NSLog(@"手勢下滑");
// tabBar動畫
if (self.tabAnimateOnceScrollFlag) {
// 在一次滑動中,下滑手勢, 且currentPostionOffsetY<456,執行火箭?切換logo動畫
if ((currentPostionOffsetY < 456.f)) {
NSLog(@"觸發-切換logo");
[[AppLoginHandle sharedInstance].tabBarController pushHomeTabBarAnimationType:anmationDirectionDown];
self.tabAnimateOnceScrollFlag = NO;
}
}
}
}
// 當開始滾動檢視時,執行該方法。一次有效滑動(開始滑動,滑動一小段距離,只要手指不鬆開,只算一次滑動),只執行一次。
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
if ([scrollView isEqual:self.collectionView]) {
self.tabAnimateOnceScrollFlag = YES;
self.lastPositionOffestY = scrollView.contentOffset.y;
}
}
複製程式碼
WMTabBar裡暴露出外部呼叫切換動畫的方法如下:
// logo和火箭切換動畫的列舉anmationDirection
typedef NS_ENUM(NSUInteger, anmationDirection) {
anmationDirectionUp,//push動畫,火箭頭出來,logo下去
anmationDirectionDown,//push動畫,火箭頭下去,logo出來
};
@class WMTabBar;
@protocol WMTabBarDelegate <NSObject>
/** 選中tabbar */
- (void)wmtabBar:(WMTabBar *)wmTabBar didSelectWMTabBarItemAtIndex:(NSInteger)index;
/** 是否可選tabbar */
- (BOOL)wmtabBar:(WMTabBar *)wmTabBar shouldSelectWMTabBarItemAtIndex:(NSInteger)index;
@end
@interface WMTabBar : UITabBar
@property (nonatomic, weak) id <WMTabBarDelegate> tabBarDelegate;
@property (nonatomic, assign) anmationDirection anmationDirection;
/** 例項化 */
+ (instancetype)tabBarWithTitleArray:(NSArray *)titleArray imageArray:(NSArray *)imageArray selectedImageArray:(NSArray *)selectedImageArray;
// 外部指定跳轉到某個tab時呼叫
- (void)selectedTabbarAtIndex:(NSNumber *)index;
// 暴露外部的切換動畫logo和火箭的方法
- (void)pushHomeTabBarAnimationType:(anmationDirection )anmationDirection;
@end
複製程式碼
WMTabBarItem裡實現首頁tab的小火箭和logo之間切換動畫
這裡寫了兩種方法,第一種是最開始嘗試的,通過CATransition的push動畫,來達到一個imageView控制元件的兩個圖片切換,如下:
- 第一種方案 push動畫方案:
// push動畫,火箭頭出來
-(void)pushHomeTabAnimationUp {
self.flag = YES;
// /** 第一種方案 */
// [self.homeTabAnimateImageView setImage:[UIImage imageNamed:@"tabbar_home_selecetedPush"]];
// CATransition *animation = [CATransition animation];
// animation.type = kCATransitionPush;//設定動畫的型別
// animation.subtype = kCATransitionFromTop; //設定動畫的方向
// animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// animation.duration = 0.25f;
// [self.homeTabAnimateImageView.layer addAnimation:animation forKey:@"pushAnimation"];
}
// push動畫,火箭頭落下
-(void)pushHomeTabAnimationDown {
self.flag = NO;
// /** 第一種方案 */
// [self.homeTabAnimateImageView setImage:[UIImage imageNamed:@"tabbar_home_selecetedLogo"]];
// CATransition *animation = [CATransition animation];
// animation.type = kCATransitionPush;//設定動畫的型別
// animation.subtype = kCATransitionFromBottom; //設定動畫的方向
// animation.duration = 0.25f;
// [self.homeTabAnimateImageView.layer addAnimation:animation forKey:@"pushAnimation"];
}
複製程式碼
效果如下:
雖然能實現功能,但在切換過程中,會有殘留效果,這顯示是不太美好的,再想想有沒有更好的實現方法。
然後就想到了collectionView了。
- 第二種方案 collection滑動item方案:
把collectionView設定成首頁tab大小,通過讓collection的cell的scrollToItem方法, 即滑動到第n個item來達到切換動畫效果,如下:
// push動畫,火箭頭出來
-(void)pushHomeTabAnimationUp {
self.flag = YES;
/** 第二種方案 */
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
}
// push動畫,火箭頭落下
-(void)pushHomeTabAnimationDown {
self.flag = NO;
/** 第二種方案 */
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
}
複製程式碼
漂亮!
到此,新版tabBar實現就完成了!