在實(shí)現(xiàn)上一篇介紹的自定義滑動(dòng)關(guān)聯(lián)菜單控件BFScrollMenu時(shí),關(guān)于滑動(dòng)方向判斷的邏輯其實(shí)一開始是準(zhǔn)備用手勢(shì)操作來實(shí)現(xiàn)的习柠,結(jié)果發(fā)現(xiàn)在ScrollView中處理手勢(shì)的邏輯比較困難能真,在寫的時(shí)候還沒有仔細(xì)研究過ScrollView的滑動(dòng)原理稿湿,只是知道需要自定義一個(gè)ScrollView才能實(shí)現(xiàn)辫愉,但是我的本意是不希望用戶還要顯示指定一個(gè)自定義的ScrollView而是直接用category進(jìn)行無縫的對(duì)接顽决,所以就改用didScroll delegate 用offset來計(jì)算滑動(dòng)邏輯了窍仰,效果可以接受汉规。
回過頭來花了些時(shí)間仔細(xì)研究了下UIScrollView的滑動(dòng)處理邏輯,這樣就可以采用Customer ScrollView來實(shí)現(xiàn)同樣的BFScrollMenu邏輯了驹吮。
-
UIScrollView的滑動(dòng)處理原理
網(wǎng)上相關(guān)的文章有很多针史,但是我感覺沒有一個(gè)能清晰的解釋清楚。這里我用流程圖的方式碟狞,把我自己經(jīng)過試驗(yàn)后的結(jié)論和理解和大家分享一下啄枕,希望能幫助到你。如果有不正確的歡迎指正族沃。
首先我們來看Apple的官方代碼文檔里的注釋:
Scrolling with no scroll bars is a bit complex. on touch down, we don't know if the user will want to scroll or track a subview like a control. on touch down, we start a timer and also look at any movement. if the time elapses without sufficient change in position, we start sending events to the hit view in the content subview. if the user then drags far enough, we switch back to dragging and cancel any tracking in the subview. the methods below are called by the scroll view and give subclasses override points to add in custom behaviour. you can remove the delay in delivery of touchesBegan:withEvent: to subviews by setting delaysContentTouches to NO.
然后再往下看2個(gè)可以set的property和2個(gè)可以重載的方法:
// default is YES. if NO, we immediately call -touchesShouldBegin:withEvent:inContentView:.
// this has no effect on presses
@property(nonatomic) BOOL delaysContentTouches;
// default is YES. if NO, then once we start tracking, we don't try to drag if the touch moves.
// this has no effect on presses
@property(nonatomic) BOOL canCancelContentTouches;
// override points for subclasses to control delivery of touch events to subviews of the scroll view
// called before touches are delivered to a subview of the scroll view.
// if it returns NO the touches will not be delivered to the subview
// this has no effect on presses
// default returns YES
- (BOOL)touchesShouldBegin:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event inContentView:(UIView *)view;
// called before scrolling begins if touches have already been delivered to a subview of the scroll view.
// if it returns NO the touches will continue to be delivered to the subview and scrolling will not occur
// not called if canCancelContentTouches is NO. default returns YES if view isn't a UIControl.
// this has no effect on presses
- (BOOL)touchesShouldCancelInContentView:(UIView *)view;
有了以上這些频祝,已經(jīng)基本能夠理解ScrollView對(duì)手勢(shì)操作的處理原理,這里來統(tǒng)一歸納一下脆淹。我們直接上圖最清晰:
解釋下上圖:
- Apple使用了一個(gè)延遲機(jī)制來判斷在一個(gè)ScrollView內(nèi)是否產(chǎn)生有效的滑動(dòng)手勢(shì)
常空,而這個(gè)延遲機(jī)制由開關(guān)delaysContentTouches
來控制,默認(rèn)為YES - 如果延遲打開且檢測(cè)到有效Scroll盖溺,則將會(huì)直接發(fā)送滑動(dòng)操作到ScrollView窟绷,并停止向SubView發(fā)送任何tracking;
- 如果延遲未打開或者打開但是沒有檢測(cè)到有效的動(dòng)作咐柜,則會(huì)看
touchesShouldBegin:withEvent:inContentView:
的返回:NO則立即返回給ScrollView,否則會(huì)將touch事件發(fā)送給SubView攘残。默認(rèn)為YES拙友。
當(dāng)touch事件已經(jīng)發(fā)送給SubView之后,如果用戶繼續(xù)產(chǎn)生touch事件(做出Scroll動(dòng)作)歼郭,則: - 檢查
canCancelContentTouches
開關(guān)(默認(rèn)為YES)遗契。如果為N,則將所有后續(xù)事件發(fā)送到SubView病曾,否則: - 檢查
touchesShouldCancelInContentView
的返回值牍蜂,如果為N漾根,則將所有事件發(fā)送到SubView,否則返回到ScrollView鲫竞。默認(rèn)情況下辐怕,如果SubView不是UIControl的一種,則返回YES从绘。
-
一個(gè)簡(jiǎn)單的總結(jié):
針對(duì)1寄疏,2: 默認(rèn)情況下,只要用戶迅速做出滑動(dòng)手勢(shì)僵井,都將觸發(fā)ScrollView滑動(dòng)陕截;
針對(duì)3:默認(rèn)情況下,點(diǎn)擊操作都可以傳入到SubView
針對(duì)4,5:默認(rèn)情況下批什,SubView中的Button, UISlider, UISwitch等等都可以直接響應(yīng)你的Touch农曲,滑動(dòng)事件;但是UIView之類則無法響應(yīng)復(fù)雜事件(Multi-Touch)
-
想要讓SubView響應(yīng)手勢(shì)操作 驻债?
最簡(jiǎn)單的:
- 關(guān)閉
delaysContentTouches
- 關(guān)閉
canCancelContentTouches
如果想要再多一些自定義乳规,比如有些地方響應(yīng),有些地方不響應(yīng)却汉,則可以:
在touchesShouldBegin:withEvent:inContentView:
中設(shè)定響應(yīng)條件驯妄,或者:
打開canCancelContentTouches
,在touchesShouldCancelInContentView:
中設(shè)定響應(yīng)條件合砂。
-
Sample Demo
說這么多還是沒懂青扔?再來一發(fā)Demo,直接看代碼最清晰:
這個(gè)Demo中翩伪,首先自定義一個(gè)ScrollView叫做“MyScollView”微猖,在MyScollView上,有2個(gè)SubView:綠色的greenView和黃色的yellowView缘屹,兩個(gè)View都添加的左右滑動(dòng)的手勢(shì)操作凛剥,但是在touchesShouldBegin:withEvent:inContentView:
方法中,當(dāng)檢測(cè)到當(dāng)前view為greenView時(shí)轻姿,返回NO犁珠。
另外,有2個(gè)Switch開關(guān)互亮,分別操作delaysContentTouches
和 canCancelContentTouches
犁享。
我們可以看一下效果:
1) 初始狀態(tài)下,所有開關(guān)打開豹休,UIButton正常工作炊昆,yellowView不能響應(yīng)手勢(shì);
a) 觸動(dòng)yellowView屏幕后馬上滑動(dòng),則UISilder不能正常工作凤巨,ScrollView滑動(dòng)视乐;
b) 觸動(dòng)yellowView屏幕后等待一小會(huì)再滑動(dòng),則UISilder正常工作敢茁;
2) 關(guān)閉canCancelContentTouches
:
a) 觸動(dòng)yellowView屏幕后馬上滑動(dòng)佑淀,則ScrollView滑動(dòng);
b) 觸動(dòng)yellowView屏幕后等待一小會(huì)再滑動(dòng)卷要,則UISilder渣聚, yellowView響應(yīng)手勢(shì);
3)關(guān)閉delaysContentTouches
僧叉,無論怎樣奕枝,UISilder,yellowView都會(huì)響應(yīng)手勢(shì)瓶堕;
4)以上任何情況隘道,greenView始終不會(huì)響應(yīng)手勢(shì)
希望你喜歡,歡迎大家討論郎笆。
2016.6.14 完稿于南京