先總結(jié)一下事件分發(fā)機制的流程
- 事件分發(fā)從Action_Down開始购岗,最初由Activity的dispatchTouchEvent()方法接收徐紧,不攔截不中斷的正常分發(fā)流程:Activity的disPatchTouchEvent()方法到PhoneWindow的superDispatchTouchEvent方法沙峻,再到DecorView的superDispatchTouchEvent方法办绝,再到ViewGroup的dispatchTouchEvent方法辜王,在ViewGroup的dispatchTouchEvent方法中判斷是否攔截级乐,若攔截調(diào)用ViewGroup的onTouchEvent方法议薪,該ViewGroup消費掉尤蛮;若不攔截,該ViewGroup遍歷子View根據(jù)點擊的位置等條件判斷是否為接收事件的子View斯议,是产捞,則分發(fā)給該子View的dispatchTouchEvent()方法,然后會調(diào)用View的onTouchEvent方法哼御,在onTouchEvent方法中會判斷該子View是否可點擊坯临,是焊唬,則事件最終傳遞到View的onClick方法消費;否則看靠,事件返回向上傳遞赶促,直到消費或者終止。
- 在dispatchTouchEvent()方法中返回true或者false挟炬,事件不向下傳遞鸥滨,只用調(diào)用super.dispatchTouchEvent方法,事件才會向下傳遞谤祖。
- 在onTouchEvent()方法中返回true爵赵,事件在該方法中消費,不會向下或者向上傳遞泊脐;返回super.onTouchEvent方法空幻,將會調(diào)用View onTouchEvent方法,判斷長按事件和點擊事件的執(zhí)行條件存不存在容客,存在則會在點擊事件中消費秕铛。
- 在onInterceptTouchEvent()方法中返回true表示攔截事件,事件可能會在該ViewGroup中消費掉缩挑;返回false表示事件繼續(xù)往下傳遞税课。
ViewGroup 默認攔截事件嗎?
答:默認不攔截任何事件渔嚷,onInterceptTouchEvent返回的是false谚鄙。
一旦有事件傳遞給view,view的onTouchEvent一定會被調(diào)用嗎芥丧?
答:是的紧阔,因為view 本身沒有onInterceptTouchEvent方法,所以只要事件來到view這里 就一定會走onTouchEvent方法续担。
并且默認都是消耗掉擅耽,返回true的。除非這個view是不可點擊的物遇,所謂不可點擊就是clickable和longgclikable同時為fale
Button的clickable就是true 但是textview是false乖仇。
enable是否影響view的onTouchEvent返回值?
答:不影響询兴,只要clickable和longClickable有一個為真乃沙,那么onTouchEvent就返回true。
requestDisallowInterceptTouchEvent 可以在子元素中干擾父元素的事件分發(fā)嗎诗舰?如果可以警儒,是全部都可以干擾嗎?
答:肯定可以始衅,但是down事件干擾不了冷蚂。
dispatchTouchEvent每次都會被調(diào)用嗎缭保?
答:是的,onInterceptTouchEvent則不會蝙茶。
滑動沖突問題如何解決 思路是什么艺骂?
要解決滑動沖突核心的方法就是2個 外部攔截也就是父親攔截,另外就是內(nèi)部攔截隆夯,也就是子view攔截法钳恕。 學會這2種 基本上所有的滑動沖突都是這2種的變種,而且核心代碼思想都一樣蹄衷。
- 外部攔截法:思路就是重寫父容器的onInterceptTouchEvent即可忧额。子元素一般不需要管±⒖冢可以很容易理解睦番,因為這和android自身的事件處理機制 邏輯是一模一樣的。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
//down事件肯定不能攔截 攔截了后面的就收不到了
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if (你的業(yè)務(wù)需求) {
//如果確定攔截了 就去自己的onTouchEvent里 處理攔截之后的操作和效果 即可了
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
//up事件 我們一般都是返回false的 一般父容器都不會攔截他耍属。 因為up是事件的最后一步托嚣。這里返回true也沒啥意義
//唯一的意義就是因為 父元素 up被攔截。導(dǎo)致子元素 收不到up事件厚骗,那子元素 就肯定沒有onClick事件觸發(fā)了示启,這里的
//小細節(jié) 要想明白
intercepted = false;
break;
default:
break;
}
return intercepted;
}
- 內(nèi)部攔截法:內(nèi)部攔截法稍微復(fù)雜一點,就是事件到來的時候领舰,父容器不管夫嗓,讓子元素自己來決定是否處理。如果消耗了 就最好冲秽,沒消耗 自然就轉(zhuǎn)給父容器處理了舍咖。
子元素代碼:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (如果父容器需要這個點擊事件) {
getParent().requestDisallowInterceptTouchEvent(false);
}//否則的話 就交給自己本身view的onTouchEvent自動處理了
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
父親容器代碼也要修改一下,其實就是保證父親別攔截down:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return false;
}
return true;
}
怎么解決 ScrollView 嵌套 ScrollView 后內(nèi)部 ScrollView 無法滑動的問題劳跃?
答:根本原因就是因為用戶的滑動操作都被外部 ScrollView 攔截并消費了谎仲,導(dǎo)致內(nèi)部 ScrollView 一直無法響應(yīng)滑動事件浙垫。
這里選擇使用內(nèi)部攔截法來解決問題刨仑。首先需要讓外部 ScrollView 攔截 ACTION_DOWN 之外的任何事件
class ExternalScrollView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {
override fun onInterceptTouchEvent(motionEvent: MotionEvent): Boolean {
val intercepted: Boolean
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
intercepted = false
super.onInterceptTouchEvent(motionEvent)
}
else -> {
intercepted = true
}
}
return intercepted
}
}
內(nèi)部 ScrollView 判斷自身是否還處于可滑動狀態(tài),如果滑動到了最頂部還想再往下滑動夹姥,或者是滑動到了最底部還想再往上滑動杉武,那么就將事件都交由外部 ScrollView 處理,其它情況都直接攔截并消費掉事件辙售,這樣內(nèi)部 ScrollView 就可以實現(xiàn)內(nèi)部滑動了
class InsideScrollView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {
private var lastX = 0f
private var lastY = 0f
override fun dispatchTouchEvent(motionEvent: MotionEvent): Boolean {
val x = motionEvent.x
val y = motionEvent.y
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
parent.requestDisallowInterceptTouchEvent(true)
}
MotionEvent.ACTION_MOVE -> {
val deltaX = x - lastX
val deltaY = y - lastY
if (abs(deltaX) < abs(deltaY)) { //上下滑動的操作
if (deltaY > 0) { //向下滑動
if (scrollY == 0) { //滑動到頂部了
parent.requestDisallowInterceptTouchEvent(false)
}
} else { //向上滑動
if (height + scrollY >= computeVerticalScrollRange()) { //滑動到底部了
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
}
MotionEvent.ACTION_UP -> {
}
}
lastX = x
lastY = y
return super.dispatchTouchEvent(motionEvent)
}
}