結(jié)論: 在自定義控件中如下重寫(xiě)onInterceptTouchEvent
就告訴所有父View:不要攔截事件淹仑,讓我消費(fèi)!棠耕!
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.onInterceptTouchEvent(ev);
}
這是一個(gè)從源碼角度分析滑動(dòng)沖突的原因
以及在源碼中理解為何能解決滑動(dòng)沖突
這是MainActivity主界面的布局內(nèi)容:
xml:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.solory.learnview.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp" />
<ImageView
android:id="@+id/imageView"
android:layout_width="105dp"
android:layout_height="86dp"
android:layout_margin="8dp"
app:srcCompat="@mipmap/ic_launcher_round" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/article_1"
android:textSize="36sp" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@color/colorPrimary">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/article_2" />
</ScrollView>
</LinearLayout>
</ScrollView>
MainActivity不用動(dòng)闰渔。
外面的ScrollView正常滑動(dòng)炮姨,但是里面的那個(gè)ScrollView動(dòng)不了。
直接給出解決方案再看如何解決:
新建一個(gè)類繼承ScrollView
public class MyScrollView extends ScrollView {
public MyScrollView(Context context) {
this(context,null);
}
public MyScrollView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//關(guān)鍵點(diǎn)在這
getParent().requestDisallowInterceptTouchEvent(true);
return super.onInterceptTouchEvent(ev);
}
}
buildProject,然后在xml中將里面的ScrollView修改成這個(gè)MyScrollView米丘。
...
<com.solory.learnview.MyScrollView
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@color/colorPrimary">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/article_2" />
</com.solory.learnview.MyScrollView>
...
跑起來(lái):問(wèn)題解決剑令。
那么現(xiàn)在研究為什么糊啡,為什么在重寫(xiě)的onInterceptTouchEvent(MotionEvent ev)
中神奇的一句代碼
getParent().requestDisallowInterceptTouchEvent(true);
就把問(wèn)題解決了拄查?
好了。跟著我的思路來(lái)棚蓄。
-
先看ScollView源碼中的onInterceptTouchEvent:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { /* * 這個(gè)方法決定了我們是否要攔截這個(gè)事件堕扶。 * 如果我們返回true, onMotionEvent方法將被調(diào)用, * 我們將在那執(zhí)行實(shí)際的滾動(dòng)操作梭依。 */ /* * 最常見(jiàn)的情況:用戶在拖拽中稍算。 * 他在動(dòng)他的手指。我們想要截取這個(gè) * 事件. */ final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; } if (super.onInterceptTouchEvent(ev)) { return true; } /* * 如果我們不能滾動(dòng)役拴,不要試圖截取觸摸糊探。 */ if (getScrollY() == 0 && !canScrollVertically(1)) { return false; } ... ... ...
清晰明了,干凈簡(jiǎn)單,后面還有一大段代碼河闰,就不放了科平。這里一進(jìn)來(lái)就是一個(gè)判斷,如果進(jìn)來(lái)的是ACTION_MOVE, 那么直接返回true姜性,直接攔截瞪慧,那么后面就沒(méi)他的子View什么事了(不懂的話去看一下ViewGroup的dispatchTouchEvent方法),event被傳入他自己的onTouchEvent中去進(jìn)行滾動(dòng)操作了部念。
那么我們一開(kāi)始內(nèi)部的ScrollView滑動(dòng)沒(méi)有響應(yīng)的原因就是弃酌,那時(shí)候手指是在滑動(dòng)的,一直不斷傳入ACTION_MOVE, 所以event一直被外部的ScrollView在如上的操作中攔截了儡炼。
意思就是只要你手指在ScrollView上滑動(dòng)妓湘,ScrollView內(nèi)部的子View就永遠(yuǎn)接收不到任何事件,就是永遠(yuǎn)無(wú)響應(yīng)乌询。
沖突的原因明白了榜贴,現(xiàn)在看如何解決的
-
回頭看MyScrollView是如何解決的:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { getParent().requestDisallowInterceptTouchEvent(true); return super.onInterceptTouchEvent(ev); }
意思就是取得父類,然后請(qǐng)求父類不攔截TouchEvent的意思楣责。
首先getParent就是返回父類竣灌,在這里是返回的那個(gè)LinearLayout聂沙,然后點(diǎn)
requestDisallowInterceptTouchEvent
進(jìn)去看,發(fā)現(xiàn)是一個(gè)叫做ViewParent的接口中的抽象方法初嘹,
注釋的英文:當(dāng)一個(gè)子View不想要他的父View和它的祖先View們攔截觸摸事件的時(shí)候及汉。調(diào)用該方法 他的父View應(yīng)該將該方法接著向上傳遞給每一個(gè)祖先View們。
-
抽象方法的話屯烦,看一下是誰(shuí)實(shí)現(xiàn)了坷随,因?yàn)槔^承的ScrollView,所以先看對(duì)應(yīng)的ScrollView中的實(shí)現(xiàn)
//-----ScrollView中 @Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (disallowIntercept) { //我也不知道這個(gè)方法干嘛的驻龟,反正不影響整體思路温眉,先跳過(guò)。 recycleVelocityTracker(); } //無(wú)論如何翁狐,都會(huì)執(zhí)行父類的該方法类溢。 super.requestDisallowInterceptTouchEvent(disallowIntercept); }
-
那么我們查看父類中的實(shí)現(xiàn),ViewGroup中:
//-----ViewGroup中 @Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) { // We're already in this state, assume our ancestors are too return; } if (disallowIntercept) { mGroupFlags |= FLAG_DISALLOW_INTERCEPT; } else { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // Pass it up to our parent if (mParent != null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); } }
意思就是將自己的FLAG更改露懒,變成disallowIntercept闯冷,并且遞歸,只要有父View懈词, 就把父View的FLAG同樣設(shè)置蛇耀。
大意為,設(shè)置了這個(gè)方法坎弯,MyScrollView就通過(guò)遞歸纺涤,告訴了他的父View和向上的所有祖先View:統(tǒng)統(tǒng)不要攔截事件!交給我來(lái)抠忘!
-
那這個(gè)FLAG是在哪里發(fā)揮作用撩炊?當(dāng)然是在ViewGroup的
dispatchTouchEvent(MotionEvent event)
內(nèi)部,并且用一個(gè)if條件先于onInterceptMotionEvent(MotionEvent event)
來(lái)判斷
圖片為證:
摸了摸老夫的胡須褐桌,嗯...衰抑,說(shuō)的真好啊~
但是!
//-----MyScrollView中
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.onInterceptTouchEvent(ev);
}
這段代碼的getParent().requestDisallowInterceptTouchEvent(true);
能執(zhí)行到的前提是MyScrollView能執(zhí)行onInterceptTouchEvent
荧嵌,也就是能執(zhí)行dispatchTouchEvent
呛踊,可是事件早都被外層的ScrollView攔截了,你還怎么獲取父類然后請(qǐng)求不要攔截TouchEvent啦撮?
當(dāng)時(shí)我在這里思考了蠻久的谭网, 那么我們返回到ScrollView的onInterceptTouchEvent
里去看吧
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
}
if (super.onInterceptTouchEvent(ev)) {
return true;
}
if (getScrollY() == 0 && !canScrollVertically(1)) {
return false;
}
...
...
...
他只攔截ACTION_MOVE,不攔截ACTION_DOWN赃春,所以當(dāng)ACTION_DOWN的那一次事件還是可以傳到下面的子View去的愉择,而利用這一點(diǎn),MyScrollView利用第一次觸碰那唯一的一次event,將他FLAG給改了锥涕,事件就可以順利地傳遞到MyScrollView了~
總結(jié):
- 在自定義控件中重寫(xiě)
onInterceptTouchEvent
就告訴所有父View:不要攔截事件衷戈,讓我消費(fèi)!层坠!@Override public boolean onInterceptTouchEvent(MotionEvent ev) { getParent().requestDisallowInterceptTouchEvent(true); return super.onInterceptTouchEvent(ev); }