onInterceptTouchEvent()是ViewGroup的一個方法,目的是在系統向該ViewGroup及其各個childView觸發onTouchEvent()以前對相關事件進行一次攔截,Android這麼設計的想法也很好理解,因爲ViewGroup會包含若干childView,所以須要可以統一監控各類touch事件的機會,所以純粹的不能包含子view的控件是沒有這個方法的,如LinearLayout就有,TextView就沒有。 java
onInterceptTouchEvent()使用也很簡單,若是在ViewGroup裏覆寫了該方法,那麼就能夠對各類touch事件加以攔截。可是如何攔截,是否全部的touch事件都須要攔截則是比較複雜的,touch事件在onInterceptTouchEvent()和onTouchEvent以及各個childView間的傳遞機制徹底取決於onInterceptTouchEvent()和onTouchEvent()的返回值。而且,針對down事件處理的返回值直接影響到後續move和up事件的接收和傳遞。 android
關於返回值的問題,基本規則很清楚,若是return true,那麼表示該方法消費了這次事件,若是return false,那麼表示該方法並未處理徹底,該事件仍然須要以某種方式傳遞下去繼續等待處理。 app
SDK給出的說明以下: ide
· You will receive the down event here. 函數
· The down event will be handled either by a child of this view group, or given to your own onTouchEvent() method to handle; this means you should implement onTouchEvent() to return true, so you will continue to see the rest of the gesture (instead of looking for a parent view to handle it). Also, by returning true from onTouchEvent(), you will not receive any following events in onInterceptTouchEvent() and all touch processing must happen in onTouchEvent() like normal. 佈局
· For as long as you return false from this function, each following event (up to and including the final up) will be delivered first here and then to the target's onTouchEvent(). this
· If you return true from here, you will not receive any following events: the target view will receive the same event but with the action ACTION_CANCEL, and all further events will be delivered to your onTouchEvent() method and no longer appear here. spa
因爲onInterceptTouchEvent()的機制比較複雜,上面的說明寫的也比較複雜,總結一下,基本的規則是: 設計
1. down事件首先會傳遞到onInterceptTouchEvent()方法 rest
2. 若是該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成以後return false,那麼後續的move, up等事件將繼續會先傳遞給該ViewGroup,以後才和down事件同樣傳遞給最終的目標view的onTouchEvent()處理。
3. 若是該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成以後return true,那麼後續的move, up等事件將再也不傳遞給onInterceptTouchEvent(),而是和down事件同樣傳遞給該ViewGroup的onTouchEvent()處理,注意,目標view將接收不到任何事件。
4. 若是最終須要處理事件的view的onTouchEvent()返回了false,那麼該事件將被傳遞至其上一層次的view的onTouchEvent()處理。
5. 若是最終須要處理事件的view 的onTouchEvent()返回了true,那麼後續事件將能夠繼續傳遞給該view的onTouchEvent()處理。
下面用一個簡單的實驗說明上述複雜的規則。視圖自底向上共3層,其中LayoutView1和LayoutView2就是LinearLayout, MyTextView就是TextView:
對應的xml佈局文件以下:
<?xml version="1.0" encoding="utf-8"?>
<com.touchstudy.LayoutView1 xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<com.touchstudy.LayoutView2
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center">
<com.touchstudy.MyTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv"
android:text="AB"
android:textSize="40sp"
android:textStyle="bold"
android:background="#FFFFFF"
android:textColor="#0000FF"/>
</com.touchstudy.LayoutView2>
</com.touchstudy.LayoutView1>
下面看具體狀況:
1. onInterceptTouchEvent()處理down事件均返回false,onTouchEvent()處理事件均返回true
------------------------------------------------------------------------------------------------------------------------------
04-11 03:58:42.620: DEBUG/LayoutView1(614): onInterceptTouchEvent action:ACTION_DOWN
04-11 03:58:42.620: DEBUG/LayoutView2(614): onInterceptTouchEvent action:ACTION_DOWN
04-11 03:58:42.620: DEBUG/MyTextView(614): onTouchEvent action:ACTION_DOWN
04-11 03:58:42.800: DEBUG/LayoutView1(614): onInterceptTouchEvent action:ACTION_MOVE
04-11 03:58:42.800: DEBUG/LayoutView2(614): onInterceptTouchEvent action:ACTION_MOVE
04-11 03:58:42.800: DEBUG/MyTextView(614): onTouchEvent action:ACTION_MOVE
…… //省略過多的ACTION_MOVE
04-11 03:58:43.130: DEBUG/LayoutView1(614): onInterceptTouchEvent action:ACTION_UP
04-11 03:58:43.130: DEBUG/LayoutView2(614): onInterceptTouchEvent action:ACTION_UP
04-11 03:58:43.150: DEBUG/MyTextView(614): onTouchEvent action:ACTION_UP
------------------------------------------------------------------------------------------------------------------------------
這是最多見的狀況,onInterceptTouchEvent並無作任何改變事件傳遞時序的操做,效果上和沒有覆寫該方法是同樣的。能夠看到,各類事件的傳遞自己是自底向上的,次序是:LayoutView1->LayoutView2->MyTextView。注意,在onInterceptTouchEvent均返回false時,LayoutView1和LayoutView2的onTouchEvent並不會收到事件,而是最終傳遞給了MyTextView。
2. LayoutView1的onInterceptTouchEvent()處理down事件返回true,
MyTextView的onTouchEvent()處理事件返回true
------------------------------------------------------------------------------------------------------------------------------
04-11 03:09:27.589: DEBUG/LayoutView1(446): onInterceptTouchEvent action:ACTION_DOWN
04-11 03:09:27.589: DEBUG/LayoutView1(446): onTouchEvent action:ACTION_DOWN
04-11 03:09:27.629: DEBUG/LayoutView1(446): onTouchEvent action:ACTION_MOVE
04-11 03:09:27.689: DEBUG/LayoutView1(446): onTouchEvent action:ACTION_MOVE
…… //省略過多的ACTION_MOVE
04-11 03:09:27.959: DEBUG/LayoutView1(446): onTouchEvent action:ACTION_UP
------------------------------------------------------------------------------------------------------------------------------
從Log能夠看到,因爲LayoutView1在攔截第一次down事件時return true,因此後續的事件(包括第一次的down)將由LayoutView1自己處理,事件再也不傳遞下去。
3. LayoutView1,LayoutView2的onInterceptTouchEvent()處理down事件返回false,
MyTextView的onTouchEvent()處理事件返回false
LayoutView2的onTouchEvent()處理事件返回true
----------------------------------------------------------------------------------------------------------------------------
04-11 09:50:21.147: DEBUG/LayoutView1(301): onInterceptTouchEvent action:ACTION_DOWN
04-11 09:50:21.147: DEBUG/LayoutView2(301): onInterceptTouchEvent action:ACTION_DOWN
04-11 09:50:21.147: DEBUG/MyTextView(301): onTouchEvent action:ACTION_DOWN
04-11 09:50:21.147: DEBUG/LayoutView2(301): onTouchEvent action:ACTION_DOWN
04-11 09:50:21.176: DEBUG/LayoutView1(301): onInterceptTouchEvent action:ACTION_MOVE
04-11 09:50:21.176: DEBUG/LayoutView2(301): onTouchEvent action:ACTION_MOVE
04-11 09:50:21.206: DEBUG/LayoutView1(301): onInterceptTouchEvent action:ACTION_MOVE
04-11 09:50:21.217: DEBUG/LayoutView2(301): onTouchEvent action:ACTION_MOVE
…… //省略過多的ACTION_MOVE
04-11 09:50:21.486: DEBUG/LayoutView1(301): onInterceptTouchEvent action:ACTION_UP
04-11 09:50:21.486: DEBUG/LayoutView2(301): onTouchEvent action:ACTION_UP
----------------------------------------------------------------------------------------------------------------------------
能夠看到,因爲MyTextView在onTouchEvent()中return false,down事件被傳遞給其父view,即LayoutView2的onTouchEvent()方法處理,因爲在LayoutView2的onTouchEvent()中return true,因此down事件傳遞並無上傳到LayoutView1。注意,後續的move和up事件均被傳遞給LayoutView2的onTouchEvent()處理,而沒有傳遞給MyTextView。
----------------------------------------------------------------------------------------------------------------
應你們的要求,我把源代碼貼上,其實很簡單,就是基礎文件,主要是用來觀察事件的傳遞。
主Activity: InterceptTouchStudyActivity.java:
public class InterceptTouchStudyActivity extends Activity {
static final String TAG = "ITSActivity";
TextView tv;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layers_touch_pass_test);
}
}
LayoutView1.java:
public class LayoutView1 extends LinearLayout {
private final String TAG = "LayoutView1";
public LayoutView1(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG,TAG);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch(action){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,"onInterceptTouchEvent action:ACTION_DOWN");
// return true;
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,"onInterceptTouchEvent action:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG,"onInterceptTouchEvent action:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG,"onInterceptTouchEvent action:ACTION_CANCEL");
break;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch(action){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,"onTouchEvent action:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,"onTouchEvent action:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG,"onTouchEvent action:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG,"onTouchEvent action:ACTION_CANCEL");
break;
}
return true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
super.onLayout(changed, l, t, r, b);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
LayoutView2.java:
public class LayoutView2 extends LinearLayout {
private final String TAG = "LayoutView2";
public LayoutView2(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG,TAG);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch(action){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,"onInterceptTouchEvent action:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,"onInterceptTouchEvent action:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG,"onInterceptTouchEvent action:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG,"onInterceptTouchEvent action:ACTION_CANCEL");
break;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch(action){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,"onTouchEvent action:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,"onTouchEvent action:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG,"onTouchEvent action:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG,"onTouchEvent action:ACTION_CANCEL");
break;
}
return true;
}
}
MyTextView.java:
public class MyTextView extends TextView {
private final String TAG = "MyTextView";
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG,TAG);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch(action){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,"onTouchEvent action:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,"onTouchEvent action:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG,"onTouchEvent action:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG,"onTouchEvent action:ACTION_CANCEL");
break;
}
return false;
}
public void onClick(View v) {
Log.d(TAG, "onClick");
}
public boolean onLongClick(View v) {
Log.d(TAG, "onLongClick");
return false;
}
}
android官方文檔有個標準解釋,現摘錄過來:
首先,看Android的官方文檔正解
- onInterceptTouchEvent()與onTouchEvent()的機制:
- 1. down事件首先會傳遞到onInterceptTouchEvent()方法
- 2. 若是該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之return false,那麼後續的move, up等事件將繼續會先傳遞給該ViewGroup,以後才和down事件同樣傳遞給最終的目標view的onTouchEvent()處理
- 3. 若是該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成以後return true,那麼後續的move, up等事件將再也不傳遞給onInterceptTouchEvent(),而是和down事件同樣傳遞給該ViewGroup的onTouchEvent()處理,注意,目標view將接收不到任何事件。
- 4. 若是最終須要處理事件的view的onTouchEvent()返回了false,那麼該事件將被傳遞至其上一層次的view的onTouchEvent()處理
- 5. 若是最終須要處理事件的view 的onTouchEvent()返回了true,那麼後續事件將能夠繼續傳遞給該view的onTouchEvent()處理。
僅僅看這個官方文檔解釋,就能理解清楚這兩個函數關係以及用途的絕對是富有經驗的framework高手。
不然,必定須要一個案例來闡釋。假設咱們有這樣一個layout,很是典型的
- <com.test.LayoutView1 xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical" android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <com.test.LayoutView2
- android:orientation="vertical" android:layout_width="fill_parent"
- android:layout_height="fill_parent" android:gravity="center">
- <com.test.MyTextView
- android:layout_width="wrap_content" android:layout_height="wrap_content"
- />
- </com.test.LayoutView2>
- </com.test.LayoutView1>
用一個示例圖來解釋這個layout:
一般外圍的layoutview1,layoutview2,只是佈局的容器不須要響應觸屏的點擊事件,僅僅Mytextview須要相應點擊。但這只是通常狀況,一些特殊的佈局可能外圍容器也要響應,甚至不讓裏面的mytextview去響應。更有特殊的狀況是,動態更換響應對象。
那麼首先看一下默認的觸屏事件的在兩個函數之間的傳遞流程。以下圖:
若是僅僅想讓MyTextView來響應觸屏事件,讓MyTextView的OnTouchEvent返回true,那麼事件流就變成以下圖,能夠看到layoutview1,layoutview2已經不能進入OnTouchEvent:
另一種狀況,就是外圍容器想獨自處理觸屏事件,那麼就應該在相應的onInterceptTouchEvent函數中返回true,表示要截獲觸屏事件,好比layoutview1做截獲處理,處理流變成以下圖:
以此類推,咱們能夠獲得各類具體的狀況,整個layout的view類層次中都有機會截獲,並且能看出來外圍的容器view具備優先截獲權。
當咱們去作一些相對來說具備更復雜的觸屏交互效果的應用時候,常常須要動態變動touch event的處理對象,好比launcher待機桌面和主菜單(見下圖),從滑動屏幕開始到中止滑動過程中,只有外圍的容器view才能夠處理touch event,不然就會誤點擊上面的應用圖標或者widget.反之在靜止不動的狀態下則須要可以響應圖標(子view)的touch事件。摘取framework中abslistview代碼以下
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- switch (action & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: {
- if (touchMode == TOUCH_MODE_FLING) {
- return true; //fling狀態,截獲touch,由於在滑動狀態,不讓子view處理
- }
- break;
- }
- case MotionEvent.ACTION_MOVE: {
- switch (mTouchMode) {
- case TOUCH_MODE_DOWN:
- final int pointerIndex = ev.findPointerIndex(mActivePointerId);
- final int y = (int) ev.getY(pointerIndex);
- if (startScrollIfNeeded(y - mMotionY)) {
- return true;//開始滑動狀態,截獲touch事件,不讓子view處理
- }
- break;
- }
- break;
- }
- }