本文主要介紹 上下滾動時菜單懸停在頂端,并且可以左右滑動切換的特殊視圖的實(shí)現(xiàn)方式。涉及知識包含事件響應(yīng)鏈助琐,UIScorllView滾動模擬,以及刷新控件的基本原理面氓。
一兵钮、前言
隨著業(yè)務(wù)的發(fā)展,系統(tǒng)提供的常規(guī)視圖已經(jīng)難以滿足需求舌界,偉大的UI設(shè)計師總能想出一些特殊的違反常理的視圖來挑戰(zhàn)程序員的腦細(xì)胞掘譬。這種上下滾動還可左右滑動切換最初也不知是哪家提出來的,但是經(jīng)過這么久發(fā)展呻拌,這種視圖展現(xiàn)方式也被越來越多的App使用葱轩,此類開源框架也有不少,實(shí)現(xiàn)處理方式也很奇妙,但也各有缺點(diǎn)和限制靴拱。
二垃喊、部分框架
2.1 YX_UITableView_IN_UITableView
此類視圖常見的做法便是UIScorllView套UIScorllView,使內(nèi)層的UITableView(TAB欄里面)和外層的UITableView同時響應(yīng)用戶的手勢滑動事件袜炕。當(dāng)用戶從頁面頂端從下往上滑動到TAB欄的過程中本谜,使外層的UITableView跟隨用戶手勢滑動,內(nèi)層的UITableView不跟隨手勢滑動偎窘。當(dāng)用戶繼續(xù)往上滑動的時候乌助,讓外層的UITableView不跟隨手勢滑動,讓內(nèi)層的UITableView跟隨手勢滑動陌知。反之從下往上滑動也一樣他托。
缺點(diǎn):當(dāng)用戶從頁面頂端從下往上滑動到TAB欄的過程中,會停住不會有單獨(dú)scrollview那如絲滑一般的滾動效果纵诞。
大部分類似框架都存在該問題上祈,如 MXSegmentedPager也是如此,本來這樣效果也挺好了浙芙,但是UI說你看看簡書就可以登刺,美妝心得就可以,產(chǎn)品說如果沒有如絲滑一般的滾動不如不上......
2.2 HHHorizontalPagingView
為了滿足UI和產(chǎn)品的需求嗡呼,筆者輾轉(zhuǎn)反側(cè) 夜不能寐終于發(fā)現(xiàn)了曙光纸俭,該作者的思路非常巧妙:
HHHorizontalPagingView 通過重寫 - (UIView *)hitTest:(CGPoint)point
withEvent:(UIEvent *)event方法 將headerView 上的響應(yīng)作用在了
self.currentScrollView (當(dāng)前展現(xiàn)的scrollerView)上,滾動就根據(jù)contentOffset來移動
headerView南窗。點(diǎn)擊就調(diào)用 @property (nonatomic, copy) void
(^clickEventViewsBlock)(UIView *eventView);
eventView 是hitTest方法查找到的view揍很。
缺點(diǎn):1.只要headerView稍微復(fù)雜點(diǎn),點(diǎn)擊事件就非常難以處理万伤。
2.破壞了headerView的事件響應(yīng)鏈窒悔,如果想在headerView上添加輪播圖就無法手勢左右滑動了。
針對以上兩個缺點(diǎn)敌买,缺點(diǎn)2 限于攔截的實(shí)現(xiàn)方法導(dǎo)致系統(tǒng)的手勢處理都沒作用在headerView简珠,已是無法實(shí)現(xiàn)。缺點(diǎn)1在筆者冥思苦想下做出了一種解決方案虹钮。
點(diǎn)擊難以處理主要是聋庵,作者為了實(shí)現(xiàn)該效果,重寫hitTest方法芙粱,導(dǎo)致了headerView響應(yīng)者鏈條的斷裂祭玉,雖然作者提供了一個block回調(diào),但對于點(diǎn)擊處理無疑是反人類春畔。我的想法是在點(diǎn)擊處理時將響應(yīng)者鏈條接起來脱货。關(guān)于響應(yīng)者鏈條可以看看該文章岛都。
Huanhoo 使用@property (nonatomic, copy) void (^clickEventViewsBlock)
(UIView *eventView);來處理點(diǎn)擊事件,而eventView就是 命中測試view 蹭劈, 而我要做的
就是通過這個命中測試view向上查找處理該事件疗绣。
實(shí)現(xiàn)方法:
引入UIView+WhenTappedBlocks這是一個手勢處理的分類,
#pragma mark - 模擬響應(yīng)者鏈條 由被觸發(fā)的View 向它的兄弟控件 父控件 延伸查找響應(yīng)
- (void)viewWasTappedPoint:(CGPoint)point{
[self clickOnThePoint:point];
}
- (BOOL)clickOnThePoint:(CGPoint)point{
if ([self.superview isKindOfClass:[UIWindow class]]) {
return NO;
}
if (self.block) {
self.block();
return YES;
}
__block BOOL click = NO;
// 看兄弟控件
[self.superview.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 轉(zhuǎn)換坐標(biāo)系 看點(diǎn)是否在View上
CGPoint objPoint = [obj convertPoint:point fromView:self];
if (!CGRectContainsPoint(obj.frame, objPoint)) {
// NSLog(@"-----%@",NSStringFromCGPoint(objPoint));
return;
}
if (self.block) {
self.block();
click = YES;
*stop = YES;
}
}];
if (!click) {
return [self.superview clickOnThePoint:point];
}
return click;
}
正常響應(yīng)铺韧,有點(diǎn)擊手勢觸發(fā)方法來執(zhí)行block多矮,非正常點(diǎn)擊 主動調(diào)用
- (void)viewWasTappedPoint:(CGPoint)point;方法就可以接起響應(yīng)者鏈條。
關(guān)于以上的實(shí)現(xiàn)可以看 JYHHHorizontalPagingView 1.1.0版本這是筆者對
HHHorizontalPagingView的一個優(yōu)化處理讓點(diǎn)擊易于處理哈打,但是也僅僅只能接起點(diǎn)擊事件塔逃。在headerView上加輪播圖無法實(shí)現(xiàn),甚至UIButton的長按高亮效果在headerView上也會失效料仗。但是它有絲滑一般的滑動湾盗,headerView和下部的每一個ScrollView滾動效果都是一體的。
三立轧、 JYHHHorizontalPagingView模擬ScrollView的滾動
上面介紹的框架限于實(shí)現(xiàn)方式都各有缺陷格粪,那到底能不能做到完美,答案是肯定的氛改,畢竟美妝心得做到了帐萎,簡書做到了(UIButton 的長按高亮效果猶在說明headerView上的響應(yīng)并沒有被破環(huán))。我的思路是在headerView上添加拖拽手勢改變下方scrollView的contentOffset胜卤,模擬scrollView的減速滑動以及彈簧效果疆导。
3.1模擬彈簧效果
彈簧效果的實(shí)現(xiàn)很簡單使用UIView 動畫即可。
[UIView animateWithDuration:0.35 animations:^{
self.currentScrollView.contentOffset = CGPointMake(contentOffset.x, border);
[self layoutIfNeeded];
}];
3.2模擬減速滑動效果
減速滑動效果確實(shí)不好實(shí)現(xiàn)葛躏,我嘗試過不少方法效果都不太好澈段,后來看到了餓了么一位開發(fā)者的博客,他是通過UIDynamic的物理特性來模擬scrollView滾動舰攒。按照他的方法完美實(shí)現(xiàn)了模擬败富。作者的博客地址目前好像無法進(jìn)去就貼一個推酷的轉(zhuǎn)載用UIKit Dynamics模仿UIScrollView,具體的一些說明作者講的很清晰我就不多說了摩窃,大家可以自己看看兽叮,也可以直接看我的代碼。
四偶芍、擴(kuò)展功能
到目前為止,該類視圖的功能可以說是相當(dāng)完美了德玫,但是需求永遠(yuǎn)是難以滿足的匪蟀,某天產(chǎn)品說現(xiàn)在數(shù)據(jù)沒有更新機(jī)制只能上拉刷新,不能下拉刷新宰僧。what材彪?這么反人類的功能你還要加下拉刷新......
針對此類需求,筆者為此添加了單獨(dú)下拉刷新以及整體下拉刷新,由于篇幅問題段化,筆者就不再多說了嘁捷,感興趣的同學(xué)可以去 github -JYHHHorizontalPagingView看具體介紹說明。
五显熏、結(jié)尾
如果我的文章對你有幫助或者給了你一些啟發(fā)雄嚣,希望你能在github給個小星星,如果你在使用過程中遇到了Bug請留言反饋喘蟆,我會及時解決缓升。歡迎轉(zhuǎn)載(在文章開頭標(biāo)明來源即可)。
六蕴轨、補(bǔ)充
1.很多人反饋有偏移啥的港谊,看下 self.edgesForExtendedLayout = UIRectEdgeNone;
- pod 1.2.1 版本已經(jīng)支持自定義 SegmentView,自定義view只需支持JYSegmentViewProtocol協(xié)議即可。具體可參考默認(rèn)的JYSegmentView 橙弱。
七歧寺、最后再推薦一個(相當(dāng)給力,可定制性強(qiáng))
github -HVScrollView
感謝 S型身材的豬
思路:
首先最底部是一個全屏的scrollView棘脐,這個scrollView的作用是橫向滑動斜筐,scrollView上面添加若干個tableView,然后每個tableView上設(shè)置頂部內(nèi)邊距荆残,頂部由內(nèi)邊距空出來的地方就放headerView和菜單欄奴艾。headerView和菜單欄放在控制器 view上。當(dāng)滑動tableView時内斯,讓headerView的y值隨著 scrollview的偏移量時刻改變蕴潦,刷新也不會有問題。
我看了下代碼俘闯,相當(dāng)給力潭苞,思路實(shí)現(xiàn)都相當(dāng)好值得學(xué)習(xí)。
大家看評論的話真朗,作者有這么一句話:
不知道為什么網(wǎng)上幾個人封裝都去用collectionView搞那么復(fù)雜此疹。
為什么這么復(fù)雜呢?一個功能又是響應(yīng)鏈的打斷遮婶,又是模擬滾動蝗碎,view還一層套一層。針對一種特殊需求的出現(xiàn)旗扑,前期并沒有太多的開源代碼供大家學(xué)習(xí)和研究蹦骑,在時間的緊逼之下,大家都選擇了自己的處理方式臀防,隨著后期的迭代眠菇,為了達(dá)到需要的效果边败,各個流派在原有基礎(chǔ)上做出了相應(yīng)處理。這些方案也許并不是最好的捎废,最簡單的笑窜,但它們都包含了作者的智慧與思想。對于這些作者我們應(yīng)心存感激登疗,因?yàn)樗麄冮_源排截,他們無私的分享了自己的思路,這些如今看來也許并不友好的實(shí)現(xiàn)方式卻是在我們最迷茫最需要時給了我們幫助與啟發(fā)谜叹。也正因?yàn)橛兴麄兊姆桨竵肀WC項(xiàng)目的正常上線匾寝,我們才敢嘗試更簡單更高效的方法。最后感謝開源荷腊。
本文所列舉的一些DEMO都有自己的實(shí)現(xiàn)方式艳悔,大家可研究學(xué)習(xí)一下選擇最適合的。感謝這些開源作者女仰。