之前在做直播的時候边锁,參照了映客App茅坛,發(fā)現(xiàn)其首頁的效果還挺不錯贡蓖,在網上找了一下相關仿映客App代碼和博客彻桃,大部分都是說如何播放直播流和推流邻眷,對于UI這塊甚少肆饶,所以我自己花了點時間研究了一下映客的首頁UI效果驯镊。
我們來看看最終效果
從效果圖上可以看出,映客首頁主要分兩部分笼蛛,一部分是實現(xiàn)沒有文字而且中間按鈕突出的TabBar
,另一部分是顯示滑動ScrollView
隱藏和顯示NavBar
和TabBar
。我們來慢慢看领追。
一、TabBar實現(xiàn)
首先舔亭,我們看下實現(xiàn)后的效果订雾。
這里我們可以使用系統(tǒng)的TabBar
來實現(xiàn)該效果洼哎。
關于如何設置系統(tǒng)的TabBar
噩峦,這里就不贅述了识补,可以看到我項目的代碼凭涂。我們來看重點部分。
1. 提出問題:
- 如何實現(xiàn)中間的突出按鈕
- 中間突出按鈕超出
TabBar
部分是如何響應點擊的 - 如何實現(xiàn)
TabBar
中Item
圖片居中且不帶文字
(1)中間突出按鈕
要實現(xiàn)中間突出的按鈕白翻,直接使用系統(tǒng)TabBar
還是不行滤馍,需要用一個取巧的方法巢株,通過KVC
的方式(使用KVC
可以修改readonly
屬性阁苞,不過不能濫用哦)使用自定義的TabBar
替換系統(tǒng)UITabBar
悼沿。
//創(chuàng)建自己的tabbar糟趾,然后用KVC將自己的tabbar和系統(tǒng)的tabBar替換下
HKTabBar *tabbar = [[HKTabBar alloc] init];
//KVC實質是修改了系統(tǒng)的_tabBar
[self setValue:tabbar forKeyPath:@"tabBar"];
替換了系統(tǒng)UITabBar
后义郑,就需要實現(xiàn)中間按鈕了非驮。我們在自定義TabBar
中添加UIButton
院尔,作為中間按鈕邀摆。
1.在HKTabBar
的initWithFrame:
方法中,初始化中間按鈕
//設置中間按鈕圖片和尺寸
UIButton *centerBtn = [[UIButton alloc] init];
[centerBtn setBackgroundImage:[UIImage imageNamed:@"tab_launch"] forState:UIControlStateNormal];
[centerBtn setBackgroundImage:[UIImage imageNamed:@"tab_launch"] forState:UIControlStateHighlighted];
//這里button的size是根據(jù)需要設置的中間圖片來的
centerBtn.size = centerBtn.currentBackgroundImage.size;
[centerBtn addTarget:self action:@selector(centerBtnDidClick) forControlEvents:UIControlEventTouchUpInside];
self.centerBtn = centerBtn;
[self addSubview:centerBtn];
2.在layoutSubviews
中設置中間按鈕和其他Item
位置
由于系統(tǒng)Item
是UITabBarButton
類型例获,自定義中間按鈕為UIButton
榨汤,所以可以根據(jù)Item
類型來區(qū)分是自定義按鈕還是系統(tǒng)Item
收壕,再調整每個Item
的位置蜜宪。這里系統(tǒng)UITabBarButton
寬度為TabBar
寬度減去中間按鈕寬度的一半。
//系統(tǒng)自帶的按鈕類型是UITabBarButton澳窑,找出這些類型的按鈕,然后重新排布位置颅湘,空出中間的位置
Class class = NSClassFromString(@"UITabBarButton");
self.centerBtn.centerX = self.centerX;
//調整中間按鈕的中線點Y值
self.centerBtn.centerY = (self.height - (self.centerBtn.height - self.height)) * 0.5;
NSInteger btnIndex = 0;
for (UIView *btn in self.subviews) {//遍歷tabbar的子控件
if ([btn isKindOfClass:class]) {//如果是系統(tǒng)的UITabBarButton,那么就調整子控件位置悲立,空出中間位置
//按鈕寬度為TabBar寬度減去中間按鈕寬度的一半
btn.width = (self.width - self.centerBtn.width) * 0.5;
//中間按鈕前的寬度薪夕,這里就3個按鈕原献,中間按鈕Index為1
if (btnIndex < 1) {
btn.x = btn.width * btnIndex;
} else { //中間按鈕后的寬度
btn.x = btn.width * btnIndex + self.centerBtn.width;
}
btnIndex++;
//如果是索引是0(從0開始的),直接讓索引++倔撞,目的就是讓消息按鈕的位置向右移動鄙陡,空出來中間按鈕的位置
if (btnIndex == 0) {
btnIndex++;
}
}
}
[self bringSubviewToFront:self.centerBtn];
到這里趁矾,中間按鈕就實現(xiàn)好了毫捣,但是如何讓超出TabBar
部分(即紅色框部分)響應點擊事件呢培漏?
(2)超出TabBar部分響應點擊
按照系統(tǒng)默認處理方式,超出TabBar
部分侧甫,是不會響應點擊事件的(不信的可以自己試試哦)。要響應點擊事件冷冗,這里就需要重寫UIView
的 hitTest:
方法(該方法可以決定點擊事件的響應者蒿辙,關于hitTest
說明思灌,可以參見iOS-使用hitTest控制點擊事件的響應對象)了泰偿。
//重寫hitTest方法,去監(jiān)聽中間按鈕的點擊攒发,目的是為了讓凸出的部分點擊也有反應
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
//判斷當前手指是否點擊到中間按鈕上烟阐,如果是蜒茄,則響應按鈕點擊檀葛,其他則系統(tǒng)處理
//首先判斷當前View是否被隱藏了屿聋,隱藏了就不需要處理了
if (self.isHidden == NO) {
//將當前tabbar的觸摸點轉換坐標系润讥,轉換到中間按鈕的身上楚殿,生成一個新的點
CGPoint newP = [self convertPoint:point toView:self.centerBtn];
//判斷如果這個新的點是在中間按鈕身上砌溺,那么處理點擊事件最合適的view就是中間按鈕
if ( [self.centerBtn pointInside:newP withEvent:event]) {
return self.centerBtn;
}
}
return [super hitTest:point withEvent:event];
}
處理完突出部分规伐,就剩下不帶文字的Item
了猖闪。
(3) TabBar中Item圖片居中且不帶文字
有的同學可能就會說了萧朝,要不帶文字,不設置tabBarItem
的title
不就好了竖配。但是title
這個NavBar
的標題也是要用的进胯,所以還是必須要設置。
那要怎么辦呢诸衔?其實很簡單就缆,要實現(xiàn)該效果竭宰,以下代碼就夠了
//設置圖片居中切揭,這里的4.5廓旬,根據(jù)實際中間按鈕圖片大小來決定
Vc.tabBarItem.imageInsets = UIEdgeInsetsMake(4.5, 0, -4.5, 0);
//設置不顯示文字嗤谚,將title的位置設置成無限遠巩步,就看不到了
Vc.tabBarItem.titlePositionAdjustment = UIOffsetMake(0, MAXFLOAT);
到這里终畅,TabBar
的實現(xiàn)就結束了离福,下面我們來看看如何實現(xiàn)隱藏和顯示NavBar
和TabBar
妖爷。
二絮识、隱藏和顯示NavBar和TabBar實現(xiàn)
首先次舌,我們來看看效果
1. 提出問題:
- 如何移動
NavBar
和TabBar
- 如何控制
NavBar
和TabBar
移動距離 - 如何控制使
ScrollView
移動的同時其顯示的區(qū)域正確 - 如何在手指滑動距離較小時,收起或者展開
NavBar
和TabBar
- 如何在
Push
到其他頁面逐沙,再Pop
回來后酱吝,NavBar
和TabBar
顯示正確
首先务热,我們要解決最基本的問題崎岂,如何讓NavBar
和TabBar
移動
(1)移動NavBar和TabBar
移動的話,其實很簡單,只需要改變他們的Y坐標即可濒憋。
//這里的self就是NavBar或者TabBar
CGRect viewFrame = self.frame;
viewFrame.origin.y = newOffsetY;
self.frame = viewFrame;
(2)控制NavBar和TabBar移動距離
移動距離,就要取決于ScrollView
的相對移動距離了条辟,即相對之前contentOffset.y
滑動了多少本姥。
在計算相對移動距離之前婚惫,我們需要獲取上次滑動ScrollView
的contentOffset.y
辰妙,我們可以在- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
中獲取上次滑動ScrollView
的contentOffset.y
,即_previousOffsetY
粗井,
_previousOffsetY = scrollView.contentOffset.y;
之后實現(xiàn)ScrollView
的委托方法- (void)scrollViewDidScroll:(UIScrollView *)scrollView
,在其中監(jiān)聽ScrollView
的移動距離浇衬,從而計算相對移動距離deltaY
CGFloat deltaY = scrollView.contentOffset.y - _previousOffsetY;
在得到相對移動距離后,我們就需要分別控制NavBar
和TabBar
的移動距離了絮姆。
這里蚁阳,我們可以實現(xiàn)一個分類來專門控制他們的移動螺捐。
要注意的是:當相對距離超出應移動范圍時定血,需要對其校正
那么灾票,我們必須先知道NavBar
和TabBar
坐標的上下限铝条,即其展開和收起時的Y
坐標班缰,以下代碼openOffsetY
為展開的Y
坐標埠忘,closeOffsetY
為收起的Y
坐標莹妒。
//NavBar
//kStatusBarHeight為狀態(tài)欄高度
openOffsetY = kStatusBarHeight;
closeOffsetY = -CGRectGetHeight(self.frame;
//TabBar
//kScreenHeight為屏幕寬度,hk_extraDistance為中間按鈕突出的距離
openOffsetY = kScreenHeight - CGRectGetHeight(self.frame);
closeOffsetY = kScreenHeight + self.hk_extraDistance;
以下坐標都代表Y
坐標鉴腻,我們這里只做豎直方向移動
知道可以移動的范圍后爽哎,就可以根據(jù)相對移動距離deltaY
計算移動后的坐標了课锌。
對于NavBar
渺贤,計算后的坐標(即當前坐標減去deltaY
)癣亚,要大于收起的坐標小于展開的坐標述雾。
對于TabBar
,計算后的坐標(即即當前坐標加上deltaY
)唆缴,要大于展開的坐標小于收起的坐標面徽。
這里畫畫圖會好理解一些趟紊。
//NavBar最終要移動的Y坐標
newOffsetY = CGRectGetMinY(self.frame) - deltaY;
newOffsetY = MAX(closeOffsetY, MIN(openOffsetY, newOffsetY));
//TabBar最終要移動的Y坐標
newOffsetY = CGRectGetMinY(self.frame) + deltaY;
newOffsetY = MIN(closeOffsetY, MAX(openOffsetY, newOffsetY));
之后,就只要將NavBar
和TabBar
移動到指定坐標即可
CGRect viewFrame = self.frame;
viewFrame.origin.y = newOffsetY;
self.frame = viewFrame;
我們再來看看ScrollView
是怎么控制移動的铛嘱。
(3)控制使ScrollView移動的同時其顯示的區(qū)域正確
細心的童鞋可能會發(fā)現(xiàn)墨吓,當NavBar
收起或者展開的過程中帖烘,ScrollView
是跟著一起移動的蚓让,即ScrollView
本身并沒滑動窄瘟,而是Y
坐標在改變蹄葱。那如何實現(xiàn)呢图云?
這里竣况,我們可以改變ScrollView
的contentInset
來滿足我們的需求情萤,相對于改變ScrollView
的frame
要方便很多哦筋岛。
我們要根據(jù)NavBar
和TabBar
移動后的坐標睁宰,改變ScrollView
的contentInset
的top
和bottom
孝赫。
top
取NavBar
的MaxY
寒锚,就是當前Y
坐標加上本身的高度刹前。
bottom
取TabBar
突出的距離喇喉,即屏幕高度減去其Y
坐標大于0的部分拣技。
這里要注意:ScrollView
的scrollIndicatorInsets
同時也需要更新,不然Indicator
顯示就有問題了邪驮。
CGFloat navBarMaxY = CGRectGetMaxY(self.navigationController.navigationBar.frame);
CGFloat tabBarMinY = CGRectGetMinY(self.tabBarController.tabBar.frame);
UIEdgeInsets scrollViewInset = self.tableView.contentInset;
scrollViewInset.top = navBarMaxY;
scrollViewInset.bottom = MAX(0, kScreenHeight - tabBarMinY);
self.tableView.contentInset = scrollViewInset;
self.tableView.scrollIndicatorInsets = scrollViewInset;
(4)在手指滑動距離較小時沮榜,收起或者展開NavBar和TabBar
細心的童鞋可能會發(fā)現(xiàn)蟆融,映客在滑動距離比較小的時候型酥,有的時候NavBar
和TabBar
會彈回來萍歉,有的時候會收起枪孩。這個是怎么做的呢蔑舞?
這個就需要在停止滑動的時候處理攻询,我們可以在ScrollView
的委托方法- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
中處理钧栖。
在滑動時,要判斷當前NavBar
或者TabBar
滑動距離啃奴,是否滑到到最大坐標(最大坐標減去最小坐標)的一半依溯。如果沒有滑動黎炉,則收起拜隧,反之展開。這里可能比較繞雀费,我們看看代碼盏袄。
//判斷當前`NavBar`是展開還是收起
- (BOOL)hk_shouldOpen {
CGFloat viewY = CGRectGetMinY(self.frame);
//[self hk_openOffsetY]為展開的Y坐標辕羽,[self hk_closeOffsetY] 為收起的Y坐標
CGFloat viewMinY = [self hk_openOffsetY];
viewMinY = [self hk_closeOffsetY] + ([self hk_openOffsetY] - [self hk_closeOffsetY]) * 0.5;
if (viewY <= viewMinY) {
return NO;
}
return YES;
}
當知道是展開還是收起后绰寞,就可以進行滑動了滤钱。這里我們做一個簡單動畫件缸,使滑動看起來自然一些他炊,這里除了需要改變ScrollView
的contentInset
,還需要改變其contentOffset
舌胶,因為NavBar
和TabBar
移動了幔嫂,ScrollView
也要跟著一起移動履恩。
[UIView animateWithDuration:0.2 animations:^{
CGFloat navBarOffsetY = 0;
if (opening) {
//navBarOffsetY為NavBar從當前位置到展開滑動的距離
navBarOffsetY = [self.navigationController.navigationBar hk_open];
[self.tabBarController.tabBar hk_open];
} else {
//navBarOffsetY為NavBar從當前位置到收起滑動的距離
navBarOffsetY = [self.navigationController.navigationBar hk_close];
[self.tabBarController.tabBar hk_close];
}
//更新TableView的contentInset
[self updateScrollViewInset];
//根據(jù)NavBar的偏移量來滑動TableView
CGPoint contentOffset = self.tableView.contentOffset;
contentOffset.y += navBarOffsetY;
self.tableView.contentOffset = contentOffset;
}];
(5)在Push到其他頁面,再Pop回來后绽昏,NavBar和TabBar顯示正確
在Push
到其他頁面之前全谤,必須把NavBar
和TabBar
都展開认然,不然在收起的狀態(tài)Push
到其他頁面卷员,NavBar
和TabBar
都不見了削饵。
這里就需要在- (void)viewWillDisappear:(BOOL)animated
中將NavBar
和TabBar
都展開葵孤。
還有一個地方需要注意:
當Push
到其他頁面的時候尤仍,如果此時ScrollView
的contentInset
不為(0,0,0,0)
時宰啦,系統(tǒng)會自動將其置為(0,0,0,0)
,這樣在Push
后师抄,還會走到之前頁面ScrollView
的scrollViewDidScroll:
方法中辆布,會導致NavBar
消失锋玲。對于這種情況惭蹂,就不應該讓其繼續(xù)走我們處理展開和收取NavBar
和TabBar
的流程盾碗。
我們可以通過以下代碼控制,當該UIViewController
不是當前顯示的UIViewController
時,就不往下走了。
//在push到其他頁面時候谬盐,還是會走該方法,這個時候不應該繼續(xù)執(zhí)行
if (!(self.isViewLoaded && self.view.window != nil)) {
return;
}
到這里砸烦,映客首頁的效果就實現(xiàn)好了幢痘!
Demo項目
該Demo項目地址:https://github.com/HustHank/YingKeHomeDemo
封裝的滑動隱藏NavBar和TabBar開源控件
項目地址:https://github.com/HustHank/HKScrollingNavAndTabBar
如果覺得該文章對你有用颜说,請幫忙點贊或者Star
我的GitHub項目门粪,謝謝!