由于這部分的內(nèi)容涉及的底層知識較多,沒有讀過源碼小伙伴相對比較難以理解。許多小伙伴遇到滑動沖突的時候只是去知其然而不是去知其所以然。其實大家沒必要害怕去接觸復雜的內(nèi)容,其實他們也都是由多個細節(jié)方面的特點堆積形成的。就此部分的內(nèi)容談一談我自己的看法,期待各位的不吝賜教。
滑動沖突的產(chǎn)生
那么滑動沖突時如何產(chǎn)生的呢?界面中只要在內(nèi)外倆層同時可以滑動的時候就會產(chǎn)生滑動沖突,導致內(nèi)外倆層只有一層可以滑動。
場景一:外部和內(nèi)部倆層滑動方向不一致

內(nèi)外層的滑動效果可以通過倆種方式來實現(xiàn),ViewPager + Fragment和ScrollView + Fragment。對于前者來說系統(tǒng)在內(nèi)部已經(jīng)解決了滑動沖突,而后者需要手動解決。這就需要小伙伴們了解一些基礎的事件分發(fā)機制的知識。
場景二:外部和內(nèi)部倆層滑動方向一致

實現(xiàn)方式同場景一,不同的是因為內(nèi)外倆層的滑動方向一致,也就是說當手指滑動的時候,系統(tǒng)無法確定用戶是想讓那一層滑動。進而會導致要么只有一層滑動,要么內(nèi)外倆層都可以滑動但是比較卡頓。
場景三:主要是針對場景一和二的嵌套

就是針對場景一和場景二的嵌套。其實也沒有看起來這么復雜,可以理解為幾個沖突的疊加。簡單來說就是將其拆分成多個場景二或者場景一來處理。
滑動沖突的解決思路
對于場景一來說,滑動類型(水平,垂直)可以根據(jù)滑動路徑與水平方向的夾角,或者是水平方向和垂直方向的距離差(dy - dx),或者是水平和垂直方向上的速度差,然后根據(jù)滑動是水平滑動還是豎直滑動進一步取決于誰來攔截當前事件。對于場景二,場景三來說比較特殊,無法根據(jù)場景一的思路來解決。一般是可以在業(yè)務找到一個突破點進行相應的處理。這里將不再贅述。

滑動沖突的解決方式
針對上述的場景一般有倆種方式去解決。分別是外部攔截和內(nèi)部攔截。
外部攔截(父容器優(yōu)先)就是由父容器首先決定是否消耗事件,然后才會傳遞給子元素。相比內(nèi)部攔截更簡單,也符合View的事件分發(fā)機制,是解決滑動沖突的優(yōu)先選擇。通過重寫父容器的onInterceptTouchEvent方法實現(xiàn)。針對不同的滑動沖突,只需要修改父容器需要消耗此事件的條件即可,外部攔截的邏輯框架如下:
public boolean?onInterceptTouchEvent(MotionEvent event) {
boolean intercepted =?false;
int?x = ?(int)event.getX();
int?y = ?(int)event.getY();
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted =?false;
//如果為true的話子元素將接收不到任何事件
if(!scroller.isFinished()) {
scroller.abortAnimation();
intercepted =?true;
}
break;
}
caseMotionEvent.ACTION_MOVE: {
int?tempX = x-lastXIntercept;
int?tempY = y-lastYIntercept;
//是否是水平滑動
if(Math.abs(tempX) >Math.abs(tempY)) {
intercepted =?true;
}else{
intercepted =?false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted =?false;//一般不去攔截UP事件
break;
}
default:
break;
}
lastX = x;
lastY = y;
lastXIntercept = x;
lastYIntercept = y;
returnintercepted;
}
內(nèi)部攔截(子元素優(yōu)先)就是父容器默認不去攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要消耗該事件就直接消耗,否則會通過重寫子元素的dispatchTouchEvent方法將事件傳遞給父容器處理。針對不同的滑動策略只需修改對應的條件即可,內(nèi)部攔截的邏輯框架如下:
public?boolean?dispatchTouchEvent(MotionEvent event){
int x = (int) event.getX();
int?y = (int) event.getY();
switch(event.getAction()) {
case?MotionEvent.ACTION_DOWN: {
horizontalScrollView.requestDisallowInterceptTouchEvent(true);
break;
}
case?MotionEvent.ACTION_MOVE: {
int?tempX = x - lastX;
int?tempY = y - lastY;
if(Math.abs(tempX) > Math.abs(tempY)) {
horizontalScrollView.requestDisallowInterceptTouchEvent(false);
}
break;
}
case?MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
lastX = x;
lastY = y;
return?super.dispatchTouchEvent(event);
}
“MotionEvent.ACTION_DOWN: {// 必須返回false,否則后續(xù)事件無法傳遞到子元素”------why?
這個問題涉及到事件分發(fā)機制的一些知識。我盡量通俗的去分析,方便大家伙兒的理解。你有沒有想過,當你單擊一個按鈕的時候系統(tǒng)是如何確定被單擊的組件的呢?是這樣的,事件一旦被觸發(fā)的話最先傳遞給當前的Activity,由它的dispatchTouchEvent()來完成事件的分發(fā)。具體工作由它內(nèi)部的Window將事件傳遞給Decor View(頂級父容器),再由頂級父容器傳遞給子元素來實現(xiàn)的。對于有使用過標簽優(yōu)化界面的同學一定聽說過最外層的那個神秘的FrameLayout。是的,它就是Decor View
對于單個View來說,由于它沒有子元素無法再向下傳遞,所以只能自己決定是否消耗事件。如果設置了OnTouchListener()的話,onTouch()就會被調(diào)用否則onTouchEvent()被調(diào)用。如果同時提供的話onTouch()會將onTouchEvent()屏蔽掉。如果該組件決定處理事件,則會終止Down事件的分發(fā),并將接下來的所有事件都交給該組件直接進行處理(當然前提是事件可以傳遞給它)。在執(zhí)行UP事件的時候,如果在onTouchEvent()中設置onClickListener的話onClick()也會被觸發(fā)。
對于ViewGroup來說,如果不攔截當前事件的話,就會由子組件繼續(xù)進行事件的分發(fā)。當所有組件都沒有消耗事件的時候也就是說所有的onTouchEvent()方法中都返回false,然后就會觸發(fā)Activity的onTouchEvent()事件來消耗事件。否則,之后全部的事件都由ViewGroup來處理,不會傳遞給子元素。