在我們 Android 開發中經常會遇到多個View、ViewGroup嵌套的問題,例如:當我們在一個ViewPager里面嵌套Fragment,而又在Fragment再次嵌套一個ViewPager的時候,那么兩個ViewPager就可能發生沖突,這時候就要我們對分發事件進行處理了。
在一次的完整的事件傳遞中,主要包括了三個階段:事件分發、攔截、消費。
觸摸事件的類型
首先要有事件的傳遞,那么先有事件的產生才行。那么事件的產生無疑就是通過手指對屏幕觸摸,在觸摸后,就會產生一系列的觸摸事件,觸摸事件對應的MotionEvent類,其類型主要有以下三種:
MotionEvent.ACTION_DOWN 按下View,是所有事件的開始
MotionEvent.ACTION_MOVE 滑動事件
MotionEvent.ACTION_UP 與down對應,表示抬起
正常情況下,一次手指觸摸屏幕的行為會觸發一系列點擊事件,通常有如下情況:
點擊屏幕后立即松開,事件序列為Down -> Up ,
點擊屏幕滑動滑動一會在松開,事件序列為Down -> Move => .... => Move -> up .
事件分發的三個階段
在了解了觸摸事件的三種主要類型之后,在講解Activity、View、ViewGroup事件分發的具體實現之前,先要講述事件分發分發的三個階段。
分發(Dispatch):
在Android中,所有的觸摸事件都是通過以下方法來分發事件的。
public boolean dispatchTouchEvent (MotionEvent ev)
在這個方法中,根據當前視圖的具體實現邏輯,來決定這個事件是直接消耗還是繼續分發給子視圖。
方法返回true表示事件被當前視圖消耗掉,不再繼續分發事件;方法返回值為 super.dispatchTouchEvent 表示繼續分發這個事件。
如果當前視圖是ViewGroup或者是ViewGroup的子類,則會調用 onInterceptTouchEvent 方法判斷是否攔截該事件。
攔截(Intercept):
public boolean onInterceptTouchEvent (MotionEvent ev)
這個方法只在ViewGroup或者是ViewGroup的子類中存在,View和Activity中不存在。
同理:返回true表示事件被當前視圖消耗掉,不再繼續分發事件給子視圖,同時交由自身的onTouchEvent方法進行消費;返回 false 或者返回 super.onInterceptTouchEvent 表示繼續分發這個事件。
消費(Consume):
public boolean onTouchEvent (MotionEvent ev)
事件的消費對應著 onTouchEvent 方法。
返回 true 表示當前視圖可以處理對應的事件,事件不會向上傳遞給父視圖;
返回 false 表示當前視圖不處理這個事件,事件會傳遞給父視圖的onTouchEvent方法進行處理。
用例子來深入說明一下吧:
View事件分發
新建一個View , 繼承 TextView
public class MyView extends TextView {
private String TAG = "MyView" ;
public MyView(Context context) {
this(context,null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "dispatchTouchEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "dispatchTouchEvent: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "dispatchTouchEvent: ACTION_UP");
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "onTouchEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "onTouchEvent: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "onTouchEvent: ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}
編寫 MyActivity ,也實現 onTouchEvent 與 dispatchTouchEvent 方法。同時也為 mTvMyView 設置點擊事件與觸碰事件。代碼如下:
public class MyActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {
private TextView mTvMyView;
private String TAG = "MyActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
mTvMyView = (TextView) findViewById(R.id.tv_my_view);
mTvMyView.setOnClickListener(this);
mTvMyView.setOnTouchListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.tv_my_view:
Log.i(TAG, "MyView onClick ");
break ;
}
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (view.getId()){
case R.id.tv_my_view:
switch (motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "MyView onTouch: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MyView onTouch: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "MyView onTouch: ACTION_UP");
break;
}
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "onTouchEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "onTouchEvent: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "onTouchEvent: ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "dispatchTouchEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "dispatchTouchEvent: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "dispatchTouchEvent: ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
}
}
布局如圖所示,只是在一個布局中添加了自己的自定義控件而已。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_my"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.zwr.androideventdispatch.view.MyView
android:id="@+id/tv_my_view"
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_centerInParent="true"
android:background="#868062"
android:gravity="center"
android:text="MyView"
android:textSize="35sp" />
</RelativeLayout>
運行 并 點擊 MyView 。
運行結果如下:
MyActivity: dispatchTouchEvent: ACTION_DOWN
MyView: dispatchTouchEvent: ACTION_DOWN
MyActivity: MyView onTouch: ACTION_DOWN
MyView: onTouchEvent: ACTION_DOWN
MyActivity: dispatchTouchEvent: ACTION_UP
MyView: dispatchTouchEvent: ACTION_UP
MyActivity: MyView onTouch: ACTION_UP
MyView: onTouchEvent: ACTION_UP
MyActivity: MyView onClick
在onTouchEvent 與dispatchTouchEvent返回的值有以下三種情況:
- 返回true
- 返回false
- 返回父類的同名方法
不同的返回值,最后事件的分發也會受一定的影響,所以可以畫出以下的流程圖:
- 由上圖可知,只要事件被攔截了,就不會再繼續分發了。
- 先執行onTouch方法,然后執行onClick方法。如果onTouch方法返回了true,那么onClick方法將不會被調用。
ViewGroup的事件分發機制
現在只要自定義一個ViewGroup并實現onTouchEvent、dispatchTouchEvent、onInterceptTouchEvent方法并
包裹在上述的自定義View
-
進行相似的操作。
public class MyViewGroup extends RelativeLayout { private String TAG = "MyViewGroup"; public MyViewGroup(Context context) { this(context,null); } public MyViewGroup(Context context, AttributeSet attrs) { this(context, attrs,0); } public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG, "dispatchTouchEvent: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "dispatchTouchEvent: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG, "dispatchTouchEvent: ACTION_UP"); break; } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG, "onTouchEvent: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "onTouchEvent: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG, "onTouchEvent: ACTION_UP"); break; } return super.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG, "onInterceptTouchEvent: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "onInterceptTouchEvent: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG, "onInterceptTouchEvent: ACTION_UP"); break; } return super.onInterceptTouchEvent(ev); } }
運行結果如下:
MyActivity: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN
MyView: dispatchTouchEvent: ACTION_DOWN
MyActivity: MyView onTouch: ACTION_DOWN
MyView: onTouchEvent: ACTION_DOWN
MyActivity: dispatchTouchEvent: ACTION_UP
MyViewGroup: dispatchTouchEvent: ACTION_UP
MyViewGroup: onInterceptTouchEvent: ACTION_UP
MyView: dispatchTouchEvent: ACTION_UP
MyActivity: MyView onTouch: ACTION_UP
MyView: onTouchEvent: ACTION_UP
MyActivity: MyView onClick
相似的,返回不同的結果,事件的分發就有所不同。總結成流程圖如下:
事件的分發由Activity到ViewGroup,再由ViewGroup到子View。