准备花几天事件写下关于touch事件分发机制,然后结合实际案例讲解怎么处理,这样就能更好的理解!如果有的不对的地方请指出来并一同学习,提高!
先从简单的view的一些click,touch事件讲起,现在写一个例子,布局中就一个自定义的textview,然后给这个textview设置click,touch事件,
<?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.test.touch.CustomTextView android:id="@+id/customTV" android:layout_width="match_parent" android:layout_height="100px" android:text="测试touch事件" android:background="#ff00ff" android:gravity="center" /> </LinearLayout>
MainActivity.java
public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private CustomTextView customTV; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); customTV = (CustomTextView) findViewById(R.id.customTV); customTV.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.i(TAG,"textview---click------"); } }); customTV.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"textview----onTouch------down"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"textview----onTouch------move"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"textview----onTouch------up"); break; } return false; } }); } }自定义textview,
package com.test.touch; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.TextView; /** * Created by admin on 2016/5/31. */ public class CustomTextView extends TextView { private static final String TAG ="CustomTextView" ; public CustomTextView(Context context) { super(context); } public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public CustomTextView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"textview----dispatchTouchEvent------down"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"textview----dispatchTouchEvent------move"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"textview----dispatchTouchEvent------up"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"textview----onTouchEvent------down"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"textview----onTouchEvent------move"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"textview----onTouchEvent------up"); break; } return super.onTouchEvent(event); } }效果图:
log:
05-31 06:47:58.772 5411-5411/com.test.touch I/CustomTextView: dispatchTouchEvent------down
05-31 06:47:58.772 5411-5411/com.test.touch I/MainActivity: onTouch------down
05-31 06:47:58.772 5411-5411/com.test.touch I/CustomTextView: onTouchEvent------down
05-31 06:47:58.842 5411-5411/com.test.touch I/CustomTextView: dispatchTouchEvent------move
05-31 06:47:58.842 5411-5411/com.test.touch I/MainActivity: onTouch------move
05-31 06:47:58.842 5411-5411/com.test.touch I/CustomTextView: onTouchEvent------move
05-31 06:47:58.856 5411-5411/com.test.touch I/CustomTextView: dispatchTouchEvent------move
05-31 06:47:58.856 5411-5411/com.test.touch I/MainActivity: onTouch------move
05-31 06:47:58.856 5411-5411/com.test.touch I/CustomTextView: onTouchEvent------move
05-31 06:47:58.872 5411-5411/com.test.touch I/CustomTextView: dispatchTouchEvent------move
05-31 06:47:58.872 5411-5411/com.test.touch I/MainActivity: onTouch------move
05-31 06:47:58.872 5411-5411/com.test.touch I/CustomTextView: onTouchEvent------move
05-31 06:47:58.906 5411-5411/com.test.touch I/CustomTextView: dispatchTouchEvent------move
05-31 06:47:58.906 5411-5411/com.test.touch I/MainActivity: onTouch------move
05-31 06:47:58.906 5411-5411/com.test.touch I/CustomTextView: onTouchEvent------move
05-31 06:47:58.922 5411-5411/com.test.touch I/CustomTextView: dispatchTouchEvent------move
05-31 06:47:58.922 5411-5411/com.test.touch I/MainActivity: onTouch------move
05-31 06:47:58.922 5411-5411/com.test.touch I/CustomTextView: onTouchEvent------move
05-31 06:47:58.940 5411-5411/com.test.touch I/CustomTextView: dispatchTouchEvent------move
05-31 06:47:58.940 5411-5411/com.test.touch I/MainActivity: onTouch------move
05-31 06:47:58.940 5411-5411/com.test.touch I/CustomTextView: onTouchEvent------move
05-31 06:47:58.957 5411-5411/com.test.touch I/CustomTextView: dispatchTouchEvent------move
05-31 06:47:58.957 5411-5411/com.test.touch I/MainActivity: onTouch------move
05-31 06:47:58.957 5411-5411/com.test.touch I/CustomTextView: onTouchEvent------move
05-31 06:47:59.066 5411-5411/com.test.touch I/CustomTextView: dispatchTouchEvent------up
05-31 06:47:59.066 5411-5411/com.test.touch I/MainActivity: onTouch------up
05-31 06:47:59.066 5411-5411/com.test.touch I/CustomTextView: onTouchEvent------up
05-31 06:47:59.066 5411-5411/com.test.touch E/MainActivity: text---click------
上面的log看出来执行顺序是:
dispatchTouchEvent()---------------->onTouch()------------->onTouchEvent()------------->onClick()
上面的第一个方法dispatchTouchEvent()是view的事件分发,只要手指触及到了view上,第一个被执行的就是dispatchTouchEvent()方法,这个方法是在view上,在基类上,现在找下view中的dispatchTouchEvent()源码看下
/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { // We don't have focus or no virtual descendant has it, do not handle the event. if (!isAccessibilityFocusedViewOrHost()) { return false; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }dispatchTouchEvent()方法有一个返回值,看google官网文档解释:
True if the event was handled by the view, false otherwise
意思是说如果返回true就表示这个事件(event)被veiw处理了,否则就是没有
而自定义View中的disPatchTouchevent()方法返回的是super.disPatchTouchevent(),那么在view中这个默认返回值是false还是true呢?,现在通过看disPatchTouchevent()方法源码去找答案,
disPatchTouchevent()方法源码不可能一句句去解释,其中好多地方我也不知道答案,只要看关键的几步就行,在disPatchTouchevent()方法中首先定义一个boolean result默认为false,
disPatchtouchEnevt()方法中有下面的逻辑
if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } }if()条件里的方法不太懂,可以看下下面的二个if()条件 这才是重点中的重点,先看下第一个if条件:
ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; }if条件中是&运算符,所以必须四个条件都必须成功才走if里面的逻辑,
第一个条件:li!=null 这个li变量是 mListenerInfo赋值给它的,查看 mListenerInfo这个变量
ListenerInfo getListenerInfo() { if (mListenerInfo != null) { return mListenerInfo; } mListenerInfo = new ListenerInfo(); return mListenerInfo; }在这个方法中给mListenerInfo变量赋值的,现在只要找到这个getListenerInfo()方法在哪被调用了,二个这个调用必须在我们调用view的disPatchTouchEnevt()方法之前被赋值,否则mListenerInfo就为null了,
/** * Add a listener for attach state changes. * * This listener will be called whenever this view is attached or detached * from a window. Remove the listener using * {@link #removeOnAttachStateChangeListener(OnAttachStateChangeListener)}. * * @param listener Listener to attach * @see #removeOnAttachStateChangeListener(OnAttachStateChangeListener) */ public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) { ListenerInfo li = getListenerInfo(); if (li.mOnAttachStateChangeListeners == null) { li.mOnAttachStateChangeListeners = new CopyOnWriteArrayList<OnAttachStateChangeListener>(); } li.mOnAttachStateChangeListeners.add(listener); }
这个是监听veiw是否被挂载在window上的监听回调,这个时候mListenerInfo变量一定不为null,因为view只要被挂载Window上我们才有权利对这个view有一系列的操作
第二个条件就是 li.mOnTouchListener != null,其实ListenerInfo类就是定义了一些变量
static class ListenerInfo { /** * Listener used to dispatch focus change events. * This field should be made private, so it is hidden from the SDK. * {@hide} */ protected OnFocusChangeListener mOnFocusChangeListener; /** * Listeners for layout change events. */ private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners; protected OnScrollChangeListener mOnScrollChangeListener; /** * Listeners for attach events. */ private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners; /** * Listener used to dispatch click events. * This field should be made private, so it is hidden from the SDK. * {@hide} */ public OnClickListener mOnClickListener; /** * Listener used to dispatch long click events. * This field should be made private, so it is hidden from the SDK. * {@hide} */ protected OnLongClickListener mOnLongClickListener; /** * Listener used to dispatch context click events. This field should be made private, so it * is hidden from the SDK. * {@hide} */ protected OnContextClickListener mOnContextClickListener; /** * Listener used to build the context menu. * This field should be made private, so it is hidden from the SDK. * {@hide} */ protected OnCreateContextMenuListener mOnCreateContextMenuListener; private OnKeyListener mOnKeyListener; private OnTouchListener mOnTouchListener; private OnHoverListener mOnHoverListener; private OnGenericMotionListener mOnGenericMotionListener; private OnDragListener mOnDragListener; private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener; OnApplyWindowInsetsListener mOnApplyWindowInsetsListener; }
li.mOnTouchListener 的赋值在这里:
/** * Register a callback to be invoked when a touch event is sent to this view. * @param l the touch listener to attach to this view */ public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; }这个l变量赋值给mOntouchListener了,而l变量是我们传递进去的,
那么第二个条件也是成立的,
第三个条件:(mViewFlags & ENABLED_MASK) == ENABLED,意思是判断当前点击的控件是否是enable的,这个是true,
第四个条件:li.mOnTouchListener.onTouch(this, event)就是我们:
如果这个条件返回true的话,disPatchTouchEvent()方法中的这段代码:
if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } }onTouch()方法返回true那么result就为true了,下面的if条件就不成立了,那么onTouchEvent()方法就不会执行了,所以一般不会返回true,那么四个条件有一个条件不成立那么就走,就走这段代码了,
if (!result && onTouchEvent(event)) { result = true; }result是为false,那么!result就为true了,现在关键是onTouchEvent()方法返回值了,先进入onTouchEvent()方法看下源码:
一下子就蒙圈了,这方法太多代码了,发现我在MotionEvent封装的DOWN,MOVE操作中都没找到想要的答案,那么就在UP上了,public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } // Walk up the hierarchy to determine if we're inside a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x, y); // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; }
经过万水千山终于找到想要的代码:performClick();对 就是它,就是它,妈的 ,藏的这么深,点鸡进去看看:
public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }发现这没几行代码,心情顿时爽多了,首先看if条件
if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; }li这个变量是不等于null的,这个上面已经讲了,li.mOnClickListener这个变量就是我们设置
在android中view的事件都是通过回调,什么click,touch等等,关键代码出来了
li.mOnClickListener.onClick(this);这个就是我们设置view的点击事件的回调了,现在知道为什么onTouch要先与onClick执行吧,因为performClick()是在onTouchEnevt()的UP事件上触发的,所以onTouchEnevt()也是优先于onClick执行,现在的执行顺序通过源码就分析出来了,
那么在什么时候onTouchEvent()方法不会执行呢?有二种情况下它不会执行,
第一种情况:
@Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"textview----dispatchTouchEvent------down"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"textview----dispatchTouchEvent------move"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"textview----dispatchTouchEvent------up"); break; } return true; }就是 dispatchTouchEvent()方法返回true的情况下,
log:
06-01 06:53:46.517 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------down
06-01 06:53:46.581 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:46.597 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:46.614 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:46.632 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:46.648 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:46.664 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:46.681 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:46.697 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:46.847 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:46.864 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:46.881 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:46.897 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:46.914 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:47.031 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 06:53:47.079 15215-15215/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------up
发现只有什么onTouch(),onTouchEvent(),click()方法都没有执行,是因为disPatchTouchEvent()方法返回了true,表示这个事件被处理了,就不往下传递了,所以导致click,touch都没起作用的原因,从这里也可以看的出来事件传递是从顶层往下传递的,打个比方就好像是一个武林高手,一掌能打到一排人,如果这一排人非第一个人都吃了药,那么倒霉的就是第一个人,如果都没吃药,那么就是按顺序一个个倒下去了,
第二种情况:
就是在:
customTV.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"textview----onTouch------down"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"textview----onTouch------move"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"textview----onTouch------up"); break; } return true; } });就是这个return true了,至于为什么导致onTouchEvent()方法不执行,不解释,贴下disPatchTouchEvent()方法中的一小片代码:
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; }可以了,相信应该明白为什么onTouchEvent()方法不执行的原因了,
现在我在onTouch的DOWN的时候return false,MOVE的时候returntrue,其他时候返回return false,看会出现什么奇怪现象:
customTV.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"textview----onTouch------down"); return false; case MotionEvent.ACTION_MOVE: Log.i(TAG,"textview----onTouch------move"); return true; case MotionEvent.ACTION_UP: Log.i(TAG,"textview----onTouch------up"); break; } return false; } });log:
06-01 08:12:08.291 29886-29886/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------down
06-01 08:12:08.291 29886-29886/com.test.touch I/MainActivity: textview----onTouch------down
06-01 08:12:08.291 29886-29886/com.test.touch E/CustomTextView: textview----onTouchEvent------down
06-01 08:12:08.314 29886-29886/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 08:12:08.314 29886-29886/com.test.touch I/MainActivity: textview----onTouch------move
06-01 08:12:08.331 29886-29886/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 08:12:08.331 29886-29886/com.test.touch I/MainActivity: textview----onTouch------move
06-01 08:12:08.347 29886-29886/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 08:12:08.347 29886-29886/com.test.touch I/MainActivity: textview----onTouch------move
06-01 08:12:08.364 29886-29886/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 08:12:08.364 29886-29886/com.test.touch I/MainActivity: textview----onTouch------move
06-01 08:12:08.382 29886-29886/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 08:12:08.382 29886-29886/com.test.touch I/MainActivity: textview----onTouch------move
06-01 08:12:08.398 29886-29886/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 08:12:08.398 29886-29886/com.test.touch I/MainActivity: textview----onTouch------move
06-01 08:12:08.414 29886-29886/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 08:12:08.414 29886-29886/com.test.touch I/MainActivity: textview----onTouch------move
06-01 08:12:08.431 29886-29886/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-01 08:12:08.431 29886-29886/com.test.touch I/MainActivity: textview----onTouch------move
06-01 08:12:08.495 29886-29886/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------up
06-01 08:12:08.495 29886-29886/com.test.touch I/MainActivity: textview----onTouch------up
06-01 08:12:08.495 29886-29886/com.test.touch E/CustomTextView: textview----onTouchEvent------up
06-01 08:12:08.496 29886-29886/com.test.touch I/MainActivity: textview---click------
发现onTouch和onTouchEvent这二者之间的返回值是有必然的联系,如图:
这个可能跟一些低版本的源码不一样,导致的结果也不一样,
现在还有onTouchEvent()方法在down---move----up这三个返回值问题了,一样通过log,
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.e(TAG,"textview----onTouchEvent------down"); return false; case MotionEvent.ACTION_MOVE: Log.e(TAG,"textview----onTouchEvent------move"); break; case MotionEvent.ACTION_UP: Log.e(TAG,"textview----onTouchEvent------up"); break; } return super.onTouchEvent(event); }在down的时候return了false,log:
06-01 09:05:41.609 12017-12017/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------down
06-01 09:05:41.609 12017-12017/com.test.touch I/MainActivity: textview----onTouch------down
06-01 09:05:41.609 12017-12017/com.test.touch E/CustomTextView: textview----onTouchEvent------down
发现在onTouchEvent down的时候return false了后,通过上面的log可以看到,touch和onTouchEvent的move和up都不执行了,
这说明:在onTouchEvent()中down,move,up只有前一个action返回true,才会执行下一个action
其实我们一直被view中的dispatchTouchEvent()方法的返回值理解错了,返回true表示事件继续分发,返回false表示终止分发,所以你在onTouchEvent()中down时候返回false,那么dispatchTouchEvent()就返回false,就终止了touch分发,所以move和up接受不到touch事件,
总结:
1:上面说onTouchEvent()中的action(down,move,up)是前一个事件只有返回true后面事件才会执行,这是错误的,原理我讲不清楚,但是我多次的实践中发现这个问题,也就是说down事件比较特殊,只有在down的时候返回false,move和up不会执行,在move的时候不管你返回什么up都不会执行,先把结论放在这里,等哪一天懂了,再结合源码看下,
2:dispathcTouchEvent()也是在down的时候返回false就终止了touch事件分发,move和up就接受不到,这个down也是很特殊,在move的时候你返回false对应的onTouchEvent()中的move就接受不到事件,但是up正常能接受到!
今天有时间讲下ViewGroup的touch事件,前面把view的touch事件讲完了,也是项目中遇到问题比较多的!
关于ViewGroup的touch事件,会涉及到几个方法,dispatchTouchEvent() onInterceptTouchEvent() onTouchEvent(),但是activity中也有dispatchTouchEvent()和onTouchEvent()方法,通过demo以及log分析:
<?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.test.touch.CustomLinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffff00" > <com.test.touch.CustomTextView android:id="@+id/customTV" android:layout_width="match_parent" android:layout_height="100px" android:text="测试touch事件" android:background="#ff00ff" android:gravity="center" android:layout_marginTop="100dp" /> </com.test.touch.CustomLinearLayout> </LinearLayout>activity:
@Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"activity----dispatchTouchEvent------down"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"activity----dispatchTouchEvent------move"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"activity----dispatchTouchEvent------up"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"CustomLinearLayout----onTouchEvent------down"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"CustomLinearLayout----onTouchEvent------move"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"CustomLinearLayout----onTouchEvent------up"); break; } return super.onTouchEvent(event); }button:
public class CustomTextView extends Button { private static final String TAG ="CustomTextView" ; public CustomTextView(Context context) { super(context); } public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public CustomTextView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"textview----dispatchTouchEvent------down"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"textview----dispatchTouchEvent------move"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"textview----dispatchTouchEvent------up"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"textview----onTouchEvent------down"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"textview----onTouchEvent------move"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"textview----onTouchEvent------up"); break; } return super.onTouchEvent(event); } }自定义的LinearLayout:
public class CustomLinearLayout extends LinearLayout { private static final String TAG ="CustomLinearLayout" ; public CustomLinearLayout(Context context) { super(context); } public CustomLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"CustomLinearLayout----dispatchTouchEvent------down"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"CustomLinearLayout----dispatchTouchEvent------move"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"CustomLinearLayout----dispatchTouchEvent------up"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"CustomLinearLayout----onInterceptTouchEvent------down"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"CustomLinearLayout----onInterceptTouchEvent------move"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"CustomLinearLayout----onInterceptTouchEvent------up"); break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG,"CustomLinearLayout----onTouchEvent------down"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"CustomLinearLayout----onTouchEvent------move"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"CustomLinearLayout----onTouchEvent------up"); break; } return super.onTouchEvent(event); } }
现在在button上轻轻的滑动一下,log:
06-08 05:46:08.162 6073-6073/com.test.touch I/MainActivity: activity----dispatchTouchEvent------down
06-08 05:46:08.162 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----dispatchTouchEvent------down
06-08 05:46:08.162 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----onInterceptTouchEvent------down
06-08 05:46:08.162 6073-6073/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------down
06-08 05:46:08.162 6073-6073/com.test.touch I/CustomTextView: textview----onTouchEvent------down
06-08 05:46:08.215 6073-6073/com.test.touch I/MainActivity: activity----dispatchTouchEvent------move
06-08 05:46:08.215 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----dispatchTouchEvent------move
06-08 05:46:08.215 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----onInterceptTouchEvent------move
06-08 05:46:08.215 6073-6073/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-08 05:46:08.215 6073-6073/com.test.touch I/CustomTextView: textview----onTouchEvent------move
06-08 05:46:08.232 6073-6073/com.test.touch I/MainActivity: activity----dispatchTouchEvent------move
06-08 05:46:08.232 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----dispatchTouchEvent------move
06-08 05:46:08.232 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----onInterceptTouchEvent------move
06-08 05:46:08.232 6073-6073/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-08 05:46:08.232 6073-6073/com.test.touch I/CustomTextView: textview----onTouchEvent------move
06-08 05:46:08.248 6073-6073/com.test.touch I/MainActivity: activity----dispatchTouchEvent------move
06-08 05:46:08.248 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----dispatchTouchEvent------move
06-08 05:46:08.248 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----onInterceptTouchEvent------move
06-08 05:46:08.248 6073-6073/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-08 05:46:08.248 6073-6073/com.test.touch I/CustomTextView: textview----onTouchEvent------move
06-08 05:46:08.264 6073-6073/com.test.touch I/MainActivity: activity----dispatchTouchEvent------move
06-08 05:46:08.264 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----dispatchTouchEvent------move
06-08 05:46:08.264 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----onInterceptTouchEvent------move
06-08 05:46:08.264 6073-6073/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-08 05:46:08.264 6073-6073/com.test.touch I/CustomTextView: textview----onTouchEvent------move
06-08 05:46:08.282 6073-6073/com.test.touch I/MainActivity: activity----dispatchTouchEvent------move
06-08 05:46:08.282 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----dispatchTouchEvent------move
06-08 05:46:08.282 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----onInterceptTouchEvent------move
06-08 05:46:08.282 6073-6073/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-08 05:46:08.282 6073-6073/com.test.touch I/CustomTextView: textview----onTouchEvent------move
06-08 05:46:08.297 6073-6073/com.test.touch I/MainActivity: activity----dispatchTouchEvent------move
06-08 05:46:08.297 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----dispatchTouchEvent------move
06-08 05:46:08.297 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----onInterceptTouchEvent------move
06-08 05:46:08.297 6073-6073/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-08 05:46:08.297 6073-6073/com.test.touch I/CustomTextView: textview----onTouchEvent------move
06-08 05:46:08.314 6073-6073/com.test.touch I/MainActivity: activity----dispatchTouchEvent------move
06-08 05:46:08.314 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----dispatchTouchEvent------move
06-08 05:46:08.314 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----onInterceptTouchEvent------move
06-08 05:46:08.314 6073-6073/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-08 05:46:08.314 6073-6073/com.test.touch I/CustomTextView: textview----onTouchEvent------move
06-08 05:46:08.331 6073-6073/com.test.touch I/MainActivity: activity----dispatchTouchEvent------move
06-08 05:46:08.331 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----dispatchTouchEvent------move
06-08 05:46:08.331 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----onInterceptTouchEvent------move
06-08 05:46:08.331 6073-6073/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-08 05:46:08.331 6073-6073/com.test.touch I/CustomTextView: textview----onTouchEvent------move
06-08 05:46:08.347 6073-6073/com.test.touch I/MainActivity: activity----dispatchTouchEvent------move
06-08 05:46:08.347 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----dispatchTouchEvent------move
06-08 05:46:08.347 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----onInterceptTouchEvent------move
06-08 05:46:08.347 6073-6073/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------move
06-08 05:46:08.347 6073-6073/com.test.touch I/CustomTextView: textview----onTouchEvent------move
06-08 05:46:08.397 6073-6073/com.test.touch I/MainActivity: activity----dispatchTouchEvent------up
06-08 05:46:08.397 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----dispatchTouchEvent------up
06-08 05:46:08.397 6073-6073/com.test.touch I/CustomLinearLayout: CustomLinearLayout----onInterceptTouchEvent------up
06-08 05:46:08.398 6073-6073/com.test.touch I/CustomTextView: textview----dispatchTouchEvent------up
06-08 05:46:08.398 6073-6073/com.test.touch I/CustomTextView: textview----onTouchEvent------up
通过log发现它的输出的顺序是:
通过日记发现LinearLayout和activity中的onTouchEvent()没有执行,这个问题先放在这里,后期会讲解,
哪我们就从上往下分析:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }上面是activity的disPatchTouchEvent()方法,第一个if是我们手指在屏幕上按下的时候,就进入到这个方法中onUserInteraction(),这个方法是一个空实现,我们无需关心,继续看第二个if条件getWindow().superDispatchTouchEvent(ev),首先getWindow()返回的是Window对象,进入Window类发现public abstract boolean superDispatchTouchEvent(MotionEvent event);它是一个抽象的方法,那么只好找它的子类了,Window唯一的一个子类是PhoneWindow,这个可以在Window类的注释中有写,发现你想在eclipse或者studio下找到这个PhoneWindow这个类是找不到的,必须下载源码后去找,
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }这是PhoneWindow类中的superDispatchTouchEvent(),看下mDecor这个变量是啥,发现它是DecorView,发现它是PhoneWindow的内部类,截取了一部分
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { /* package */int mDefaultOpacity = PixelFormat.OPAQUE; /** The feature ID of the panel, or -1 if this is the application's DecorView */ private final int mFeatureId; private final Rect mDrawingBounds = new Rect(); private final Rect mBackgroundPadding = new Rect(); private final Rect mFramePadding = new Rect(); private final Rect mFrameOffsets = new Rect(); private boolean mChanging; private Drawable mMenuBackground; private boolean mWatchingForMenu; private int mDownY; private ActionMode mActionMode; private ActionBarContextView mActionModeView; private PopupWindow mActionModePopup; private Runnable mShowActionModePopup; // View added at runtime to draw under the status bar area private View mStatusGuard; // View added at runtime to draw under the navigation bar area private View mNavigationGuard; private View mStatusColorView; private View mNavigationColorView; private final BackgroundFallback mBackgroundFallback = new BackgroundFallback(); private int mLastTopInset = 0; private int mLastBottomInset = 0; private int mLastRightInset = 0;发现它是继承自FrameLayout,而它的disPatchTouchEvent()方法为:
@Override public boolean dispatchTouchEvent(MotionEvent ev) { final Callback cb = getCallback(); return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); }发现它是有一个基本的判断,这个实现看不懂,就当做它返回的是super.dispatchTouchEvent()这个是调用了父类FrameLayout的disPatchTouchEvent(),发现FrameLayout并没有这个方法,而是在ViewGroup中有这个方法,
@Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } // If the event targets the accessibility focused view and this is it, start // normal event dispatch. Maybe a descendant is what will handle the click. if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { ev.setTargetAccessibilityFocus(false); } boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); } // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; } // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // Check for cancelation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // If the event is targeting accessiiblity focus we give it to the // view that has accessibility focus and if it does not handle it // we clear the flag and dispatch the event to all children as usual. // We are looking up the accessibility focused host to avoid keeping // state since these events are very rare. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; }这个就是全部的ViewGroup中的disPatchTouchEvent()方法,在这插一句,上面分析到Window,PhoneWindow,DecorView类,我们来工具来看下我们一个普通的布局它的顶级view是那个,使用hierarchyviewer工具,这个在sdk中自带的,路径为E:\soft\adt-bundle-windows-x86_64-20140624\sdk\tools
就拿我们上面的activity_main来看:
发现顶级的view是DecorView这个,然后它有二个分支,其实在android低版本的话是一个分支的,下面的分支跟标题栏以及状态栏有关,然后就是FrameLayout了,这个才是我们在Activity类通过setContentView(layoutId)设置内容区域,
那么从上面的结构图以及结合我们之前的log日记可以得出这个结论:
这个结论是通过实践总结出来的,那么现在通过看源代码的方式从理论上验证这个结论是否正确,关于这个可以看下这位大神的博客,http://blog.csdn.net/xiaanming/article/details/21696315(夏安明老师的)
发现高版本ViewGroup类的disPatchTouchEvent()读起来真是费劲,准备找一下2.2的源码,发现他们好多都是用2.2版本的,
2.2的ViewGroup 中的dispatchtouchevent()源码如下:
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();//获取手指的事件类型 是down 还是move up
final float xf = ev.getX(); //获取手指移动的x坐标,以该view的左上角为原点(也就是参考点)
final float yf = ev.getY();//同上
final float scrolledXFloat = xf + mScrollX;//上一次的偏移量+xf就是构建新矩形的左上角x点
final float scrolledYFloat = yf + mScrollY;//同上
final Rect frame = mTempRect;//定义一个临时的矩形,因为所有的view其实在屏幕上都是矩形存在的 只要计算它的坐标就好了
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//disallowIntercept这个默认值为false,这个值和requestDisallowInterceptTouchEvent(boolean disallowIntercept)有关,这个方法里面的形参结果就是这个成员变量的值,
if (action == MotionEvent.ACTION_DOWN) {//判断是否是down事件
if (mMotionTarget != null) {//判断这个mMotionTarget是否为null
// this is weird, we got a pen down, but we thought it was
// already down!
// XXX: We should probably send an ACTION_UP to the current
// target.
mMotionTarget = null;//如果if条件是为true,就把这个mMotionTarget变量赋值为null
}
// If we're disallowing intercept or if we're allowing and we didn't
// intercept
if (disallowIntercept || !onInterceptTouchEvent(ev)) { 这个if条件只要2个中一个为true就行,前面说了disallowIntercept这个为false,那么onInterceptTouchEvent()这个返回值一定为false,这个if条件才成功,这个在源码中默认返回就是false 所以if条件成立
// reset this event's action (just to protect ourselves)
ev.setAction(MotionEvent.ACTION_DOWN);
// We know we want to dispatch the event down, find a child
// who can handle it, start with the front-most child.
final int scrolledXInt = (int) scrolledXFloat;//赋值操作
final int scrolledYInt = (int) scrolledYFloat;//赋值操作
final View[] children = mChildren;//把mChildren数组赋值给children数组
final int count = mChildrenCount;//记录viewgroup多少个子view
for (int i = count - 1; i >= 0; i--) {//遍历所有的子view 从最后一个倒着遍历
final View child = children[i];//获取一个子view
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {//判断这个view是否可见以及是否在执行动画
child.getHitRect(frame);//获取这个view所在的矩形区域
if (frame.contains(scrolledXInt, scrolledYInt)) {//判断(scrolledXInt,scrolledYInt)这个坐标点是否在这个矩形区域内
// offset the event to the view's coordinate system
final float xc = scrolledXFloat - child.mLeft;//手指在子view的移动离父view的原坐标-子view离父view左边的距离
final float yc = scrolledYFloat - child.mTop;//同上
ev.setLocation(xc, yc);//
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {//如果子view的dispatchtouchevent()返回true 就进入这个if逻辑判断 (标识一)
// Event handled, we have a target now.
mMotionTarget = child;//把当前的子view赋值给mMotionTarget
return true;//返回true 表示事件被子view消费掉了
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
}
}
//判断是否是手指抬起或者取消touch事件
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
// Note, we've already copied the previous state to our local
// variable, so this takes effect on the next event
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// The event wasn't an ACTION_DOWN, dispatch it to our target if
// we have one.
final View target = mMotionTarget;(关键点)
if (target == null) {//如果target==null就说明mMotionTarget为null,那么也就说了 (标识一) 这个if条件不成立 那就是子view的dispatchtouchevent()返回了false
// We don't have a target, this means we're handling the
// event as a regular view.
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);//返回了父类中的dispatchtouchevent()值 也就是调用了view的dispatchtouchevent()方法
}
// if have a target, see if we're allowed to and want to intercept its
// events
if (!disallowIntercept && onInterceptTouchEvent(ev)) {//这是判断如果disallowIntercept为false和onInterceptTouchEvent()返回true 这个if条件成立,也就是说明 事件被拦截了
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
// target didn't handle ACTION_CANCEL. not much we can do
// but they should have.
}
// clear the target
mMotionTarget = null;
// Don't dispatch this event to our own view, because we already
// saw it when intercepting; we just want to give the following
// event to the normal onTouchEvent().
return true;
}
if (isUpOrCancel) {//每次抬起或者事件被取消的时候都要把这个mMotionTarget变量赋值为null
mMotionTarget = null;
}
// finally offset the event to the target's coordinate system and
// dispatch the event.
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
return target.dispatchTouchEvent(ev);//什么时候能执行到这一步,只要看看上面的return 几下就行了,也就是没有拦截的情况下才会走这里,那么走到这一步,说明target一定不为null,而target的值是mMotionTarget赋值给
如图:
我们手指touch的是在textview上,但是touch事件分发确实从上外层到里面(也就是textview),如果里面没有对这个touch做处理,那么这个touch会一步步去找它间接的父view,