关闭

android touch事件传递与分发

970人阅读 评论(0) 收藏 举报
分类:

准备花几天事件写下关于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()方法看下源码:

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;
}
一下子就蒙圈了,这方法太多代码了,发现我在MotionEvent封装的DOWN,MOVE操作中都没找到想要的答案,那么就在UP上了,

经过万水千山终于找到想要的代码: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,












0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:340719次
    • 积分:7255
    • 等级:
    • 排名:第3044名
    • 原创:388篇
    • 转载:1篇
    • 译文:0篇
    • 评论:135条
    github地址:
    https://github.com/zhouguizhi/
    关注
    学习前人留下知识提高自己
    最新评论