跟touch事件相关的3个方法: public boolean dispatchTouchEvent(MotionEvent ev); //用来分派event public boolean onInterceptTouchEvent(MotionEvent ev); //用来拦截event public boolean onTouchEvent(MotionEvent ev); //用来处理event
三个方法的用法: dispatchTouchEvent() 用来分派事件。 其中调用了onInterceptTouchEvent()和onTouchEvent(),一般不重写该方法 onInterceptTouchEvent() 用来拦截事件。 ViewGroup类中的源码实现就是{return false;}表示不拦截该事件, 事件将向下传递(传递给其子View); 若手动重写该方法,使其返回true则表示拦截,事件将终止向下传递, 事件由当前ViewGroup类来处理,就是调用该类的onTouchEvent()方法 onTouchEvent() 用来处理事件。 返回true则表示该View能处理该事件,事件将终止向上传递(传递给其父View); 返回false表示不能处理,则把事件传递给其父View的onTouchEvent()方法来处理
先上一段代码演示: TestScrollView.java
package com.example.dispatchevent; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.ScrollView; public class TestScrollView extends ScrollView { public TestScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public TestScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public TestScrollView(Context context) { super(context); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.i("dispatchTouchEvent", "TestScrollView"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.i("onInterceptTouchEvent", "TestScrollView"); return super.onInterceptTouchEvent(ev); //return true; } @Override public boolean onTouchEvent(MotionEvent ev) { Log.i("onTouchEvent", "TestScrollView"); return super.onTouchEvent(ev); } }
MyButton.java
package com.example.dispatchevent; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.Button; public class MyButton extends Button { public MyButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public MyButton(Context context, AttributeSet attrs) { super(context, attrs); } public MyButton(Context context) { super(context); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.i("dispatchTouchEvent", "MyButton"); return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { Log.i("onTouchEvent", "MyButton"); return super.onTouchEvent(ev); } }
TestActivity.java
package com.example.dispatchevent; import android.app.Activity; import android.os.Bundle; public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.test); } }
test.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.example.dispatchevent.TestScrollView android:id="@+id/testScrollView1" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.example.dispatchevent.MyButton android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> </com.example.dispatchevent.TestScrollView> </LinearLayout>
当TestScrollView 的onInterceptTouchEvent返回true时,表示被拦截,那么调用自身的onTouchEvent
当返回默认值(false)时,表示不拦截,那么往下传到button
演示完毕。接下来要解释两个地方
1.onInterceptTouchEvent返回默认值(false)? 跟踪源码
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { /* * This method JUST determines whether we want to intercept the motion. * If we return true, onMotionEvent will be called and we do the actual * scrolling there. */ /* * Shortcut the most recurring case: the user is in the dragging * state and he is moving his finger. We want to intercept this * motion. */ final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; } /* * Don't try to intercept touch if we can't scroll anyway. */ if (getScrollY() == 0 && !canScrollVertically(1)) { return false; } switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { /* * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check * whether the user has moved far enough from his original down touch. */ /* * Locally do absolute value. mLastMotionY is set to the y value * of the down event. */ final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) { // If we don't have a valid id, the touch down wasn't on content. break; } final int pointerIndex = ev.findPointerIndex(activePointerId); if (pointerIndex == -1) { Log.e(TAG, "Invalid pointerId=" + activePointerId + " in onInterceptTouchEvent"); break; } final int y = (int) ev.getY(pointerIndex); final int yDiff = Math.abs(y - mLastMotionY); if (yDiff > mTouchSlop) { mIsBeingDragged = true; mLastMotionY = y; initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); if (mScrollStrictSpan == null) { mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll"); } final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } break; } case MotionEvent.ACTION_DOWN: { final int y = (int) ev.getY(); if (!inChild((int) ev.getX(), (int) y)) { mIsBeingDragged = false; recycleVelocityTracker(); break; } /* * Remember location of down touch. * ACTION_DOWN always refers to pointer index 0. */ mLastMotionY = y; mActivePointerId = ev.getPointerId(0); initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); /* * If being flinged and user touches the screen, initiate drag; * otherwise don't. mScroller.isFinished should be false when * being flinged. */ mIsBeingDragged = !mScroller.isFinished(); if (mIsBeingDragged && mScrollStrictSpan == null) { mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll"); } break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: /* Release the drag */ mIsBeingDragged = false;//AAAAAAAAAAAAAAAA mActivePointerId = INVALID_POINTER; recycleVelocityTracker(); if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { postInvalidateOnAnimation(); } break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } /* * The only time we want to intercept motion events is if we are in the * drag mode. */ return mIsBeingDragged; }
MotionEvent一共有多种值,但是最后的落脚点一定是ACTION_UP,(看标注了AAAAAA的那一行),里面mIsBeingDragged = false;最后return mIsBeingDragged表示默认值是false
2.演示的图片里面为什么有两份调用 因为我点击了一次,依次点击包含两种ACTION:ACTION_DOWN和ACTION_UP,所以有两份。
3.演示图片中第二份为什么会少了一个onInterceptTouchEvent调用 dispatchTouchEvent()方法中还有“记忆”的功能,如果第一次事件向下传递到某View,它把事件继续传递交给它的子View,它会记录该事件是否被它下面的View给处理成功了,(怎么能知道呢?如果该事件由我的onTouchEvent()来处理,那就说明被拦截);当第二次事件向下传递到该View,该View的dispatchTouchEvent()方法机会判断,若上次的事件由下面的view成功处理了,那么这次的事件就继续交给下面的来处理,若上次的事件没有被下面的处理成功,那么这次的事件就不会向下传递了,该View直接调用自己的onTouchEvent()方法来处理该事件。 当然,“记忆”功能的信息只在一系列事件完成之前有效,如从ACTION_DOWN事件开始,直到后续事件ACTION_MOVE,ACTION_UP结束后,“记忆”的信息就会清除。也就是说如果某View处理ACTION_DOWN事件失败了(onTouchEvent()返回false),那么后续的ACTION_MOVE,ACTION_UP等事件就不会再传递到该View了,由其父View自己来处理。在下一次发生ACTION_DOWN事件的时候,还是会传递到该View的。
加Android进阶群 4112676,可免费获取Android进阶资料。