事件分發(fā)的核心方法
| 組件 | dispatchTouchEvent | onTouchEvent | onInterceptTouchEvent |
|---|---|---|---|
| Activity | 存在 | 存在 | 不存在 |
| ViewGroup | 存在 | 存在 | 存在 |
| View | 存在 | 存在 | 不存在 |
事件分發(fā)流程圖

image.png
源碼分析
1、事件從點(diǎn)擊屏幕時(shí)觸發(fā),由手指點(diǎn)擊屏幕,硬件通知底層,再調(diào)用java層,最后調(diào)用Activity的 public boolean dispatchTouchEvent(MotionEvent ev) 方法
/**
* 當(dāng)一個(gè)點(diǎn)擊操作發(fā)生時(shí),事件傳遞順序 Acivity - > Window ->DecorView DecorView 一般
* 就是當(dāng)前界面的底層容器(即setContentView 所設(shè)置的View的 父容器),一個(gè)點(diǎn)擊操作要是沒有被Activity
* 下的任何View處理,即頂層DecorView的dispatchTouchEvent()方法返回false的話
* 則Activity的onTouchEvent()方法會(huì)調(diào)用
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 如果是down,說明是一個(gè)新的事件
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); // 空方法 添加自己的業(yè)務(wù)
}
//注釋1 調(diào)用Window的superDispatchTouchEvent方法,吧事件從Activity分離到DecorView
if (getWindow().superDispatchTouchEvent(ev)) {
return true; //如果返回true
}
return onTouchEvent(ev);
}
注釋1中調(diào)用Window的superDispatchTouchEvent,也就是調(diào)用PhoneWindow的superDispatchTouchEvent,而PhoneWindow的superDispatchTouchEvent又是調(diào)用DecorView的superDispatchTouchEvent,DecorView本身就是ViewGroup的實(shí)現(xiàn)類,相當(dāng)于調(diào)用了ViewGroup的dispatchTouchEvent
## PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
## DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
滑動(dòng)沖突的解決方法
1、制定合適的滑動(dòng)策略
2、按滑動(dòng)策略分發(fā)事件
制定合適的滑動(dòng)策略
如何判斷水平滑動(dòng)還是豎直滑動(dòng)?1、可以根據(jù)水平方向或者豎直方向的距離差來判斷。2、可以根據(jù)滑動(dòng)路徑和水平方向的夾角來判斷,小于30°則為水平方向,大于60°為豎直方向,30到60之間不處理,或者根據(jù)具體業(yè)務(wù)需求

image.png
按滑動(dòng)策略分發(fā)事件
- 外部攔截法
外部攔截法一般用在容器中,父容器攔截子容器的事件,子容器沒有響應(yīng)。
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false; // 必須false,否則后續(xù)的MOVE和UP不再傳遞給子View
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要當(dāng)前點(diǎn)擊事件) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false; // 必須false,會(huì)影響子View的onClick是否被觸發(fā)
break;
}
return intercepted;
}
- 內(nèi)部攔截法
內(nèi)部攔截法一般在子容器中實(shí)現(xiàn),比如在ScrollView中嵌套的ScrollView,在第二個(gè)ScrollView中實(shí)現(xiàn)是否攔截事件。
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 關(guān)閉父元素的攔截功能,當(dāng)前容器攔截
requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要此類點(diǎn)擊事件) {
// 激活父元素原本的攔截功能 ,父容器攔截
requestDisallowInterceptTouchEvent(false);
break;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(event);
}
判斷滑動(dòng)的方向
/**
* @param startX 按下x 這個(gè)值不能在 onTouchEvent當(dāng)局部變量,因?yàn)榫植孔兞棵看伟聪聏,y都是0
* @param startY 按下y
* @param endX 結(jié)束x
* @param endY 結(jié)束y
* @return top = 1 bottom = 2 left = 3 right = 4
*/
public int getSlideDirection(float startX, float startY, float endX, float endY) {
final int top = 1;
final int bottom = 2;
final int left = 3;
final int right = 4;
float x = Math.abs(endX - startX);
float y = Math.abs(endY - startY);
double z = Math.sqrt(x * x + y * y);
int jiaodu = Math.round((float) (Math.asin(y / z) / Math.PI * 180));//角度
if (endY < startY && jiaodu > 45) {//上
Log.d(TAG, "角度:" + jiaodu + ", 動(dòng)作:上");
return top;
} else if (endY > startY && jiaodu > 45) {//下
Log.d(TAG, "角度:" + jiaodu + ", 動(dòng)作:下");
return bottom;
} else if (endX < startX && jiaodu <= 45) {//左
Log.d(TAG, "角度:" + jiaodu + ", 動(dòng)作:左");
return left;
} else if (endX > startX && jiaodu <= 45) {//右
Log.d(TAG, "角度:" + jiaodu + ", 動(dòng)作:右");
return right;
}
return -1;
}
float X = 0;
float Y = 0;
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: // 按下
X = event.getX();
Y = event.getY();
case MotionEvent.ACTION_UP: // 抬起
float upX = event.getX();
float upY = event.getY();
getSlideDirection(X, Y, upX, upY);
}
return super.onTouchEvent(event);
}