Android 中的Touch事件分发机制

来源:互联网 发布:股票历史分时数据获取 编辑:程序博客网 时间:2024/05/18 03:00

写这篇博客的主要用意,是自己mark下。

由来:1.ScrollView+EditText结合使用时出现触摸事件异常

2.自定义控件使用时,touch处理不对

3.面试问到


事件分发主要分为两部分:view的事件分发viewgroup的事件分发。在探讨事件分发机制之前,先需要搞清楚android两个基础控件view和viewgroup,以及它们之间的关系:view是没有子控件的,像button,textview都是view控件。而viewgroup继承自view,是可以存在子控件的。也就是说viewgroup就是一组view或者是viewroup的集合,它是所有页面布局的父类(eg linearlayout,relativelayout) 

 1.view的事件分发:  dsipatchTouchEvent 方法:事件分发 和 onTouchEvent方法:事件消费

下面例子用来模拟View事件处理流程

所以定义了CustomButton类,重写了dispatchTouchEvent方法和onTouchEvent方法

<span style="font-size:14px;">public class CustomButton extends Button {private static final String TAG = "MainActivity";public CustomButton(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// TODO Auto-generated constructor stub}public CustomButton(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stub}public CustomButton(Context context) {super(context);// TODO Auto-generated constructor stub}    @Override      public boolean onTouchEvent(MotionEvent event) {          switch (event.getAction()) {          case MotionEvent.ACTION_DOWN:              Log.i(TAG, "MainActivity-CustomButton-onTouchEvent-ACTION_DOWN");              break;          case MotionEvent.ACTION_UP:              Log.i(TAG, "MainActivity-CustomButton-onTouchEvent-ACTION_UP");              break;          default:              break;          }          return super.onTouchEvent(event);      }        // 位于view里面      @Override      public boolean dispatchTouchEvent(MotionEvent event) {          switch (event.getAction()) {          case MotionEvent.ACTION_DOWN:              Log.i(TAG, "MainActivity-CustomButton-dispatchTouchEvent-ACTION_DOWN");              break;          case MotionEvent.ACTION_UP:              Log.i(TAG, "MainActivity-CustomButton-dispatchTouchEvent-ACTION_UP");              break;          default:              break;          }          return super.dispatchTouchEvent(event);      }  }</span>

2.MainActivity中对Button的onClick事件进行监听,重写Activity中的dispatchTouchEvent()方法和onTouchEvent()方法

public class MainActivity extends Activity implements OnClickListener {private CustomButton mViewBtn;@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);setContentView(R.layout.main);mViewBtn = (CustomButton) findViewById(R.id.view_btn);mViewBtn.setOnClickListener(this);mViewBtn.setOnTouchListener(new View.OnTouchListener() {                          @Override              public boolean onTouch(View v, MotionEvent event) {                  switch (event.getAction()) {                  case MotionEvent.ACTION_DOWN:                      Log.i("MainActivity", "CustomButton-onTouch-ACTION_DOWN");                      break;                  case MotionEvent.ACTION_UP:                      Log.i("MainActivity", "CustomButton-onTouch-ACTION_UP");                      break;                  default:                      break;                  }                  return false;              }          });  }@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {// TODO Auto-generated method stubint action = ev.getAction();switch (action) {case MotionEvent.ACTION_DOWN:Log.i("MainActivity", "MainActivity-dispatchTouchEvent action = action down");break;case MotionEvent.ACTION_UP:Log.i("MainActivity", "MainActivity-dispatchTouchEvent action = action up");break;default:break;}return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubint action = event.getAction();switch (action) {case MotionEvent.ACTION_DOWN:Log.i("MainActivity", "MainActivity-onTouchEvent action = action down");break;case MotionEvent.ACTION_UP:Log.i("MainActivity", "MainActivity-onTouchEvent action = action up");break;default:break;}return super.onTouchEvent(event);}@Overridepublic void onClick(View v) {// TODO Auto-generated method stubint id = v.getId();switch (id) {case R.id.view_btn:Log.i("MainActivity", "MainActivity view_btn clicked");break;default:break;}}}


点击Button输出结果如下:

03-23 06:21:36.549: I/MainActivity(2056): MainActivity-dispatchTouchEvent action = action down03-23 06:21:36.549: I/MainActivity(2056): MainActivity-CustomButton-dispatchTouchEvent-ACTION_DOWN03-23 06:21:36.549: I/MainActivity(2056): CustomButton-onTouch-ACTION_DOWN03-23 06:21:36.549: I/MainActivity(2056): MainActivity-CustomButton-onTouchEvent-ACTION_DOWN03-23 06:21:36.677: I/MainActivity(2056): MainActivity-dispatchTouchEvent action = action up03-23 06:21:36.677: I/MainActivity(2056): MainActivity-CustomButton-dispatchTouchEvent-ACTION_UP03-23 06:21:36.677: I/MainActivity(2056): CustomButton-onTouch-ACTION_UP03-23 06:21:36.677: I/MainActivity(2056): MainActivity-CustomButton-onTouchEvent-ACTION_UP03-23 06:21:36.687: I/MainActivity(2056): MainActivity view_btn clicked


从上述的结果上,我们可以看出,touch down事件,首先由的Activity中dispatchTouchEvetn方法进行事件分发,然后传递给CustomButton中的dispatchTouchEvent进行事件分发,然后传递给onTouchEvent方法对touch事件处理,up事件处理流程同down事件
流程,最后执行button中的onclick方法。

通过查看View类中的dispatchTouchEvent和onTouchEvent源码

public boolean dispatchTouchEvent(MotionEvent event) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(event, 0);        }        if (onFilterTouchEventForSecurity(event)) {            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                return true;            }            if (onTouchEvent(event)) {                return true;            }        }        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);        }        return false;    }
li != null && li.mOnTouchListener && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)如果这三个条件都为真,就返回true,否则的话就去执行onTouchEvent(event)并返回结果,如果都不合适就返回false。

第一个条件:li != null && li.mOnTouchListener ,只要控件注册了onTouchListener监听,就不能为null

第二个条件: (mViewFlags & ENABLED_MASK) == ENABLED ,一般控件的属性enabled都是true,所以成立

第三个条件:li.mOnTouchListener.onTouch(this, event),这里是回调touch注册事件里面的onTouch方法,如果onTouch返回true则dsipatchTouchEvent 也会返回true;如果onTouch方法返回false,则会再去执行onTouchEvent方法。

这里可以看出dsipatchTouchEvent 中onTouch和onTouchEvent方法都会被执行,最先执行的是onTouch,所以也就解释了为什么打印出来的log中onTouch比onclick要先出现咯,现在也仅仅是解释了这个。既然oclick事件被执行了,那么就肯定是在onTouchEvent方法里面被执行的。

public boolean onTouchEvent(MotionEvent event) {        final int viewFlags = mViewFlags;        if ((viewFlags & ENABLED_MASK) == DISABLED) {            if (event.getAction() == 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));        }        if (mTouchDelegate != null) {            if (mTouchDelegate.onTouchEvent(event)) {                return true;            }        }        if (((viewFlags & CLICKABLE) == CLICKABLE ||                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {            switch (event.getAction()) {                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);                       }                        if (!mHasPerformedLongPress) {                            // 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();                    }                    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();                        }                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());                    } else {                        // Not inside a scrolling container, so show the feedback right away                        setPressed(true);                        checkForLongClick(0);                    }                    break;                case MotionEvent.ACTION_CANCEL:                    setPressed(false);                    removeTapCallback();                    removeLongPressCallback();                    break;                case MotionEvent.ACTION_MOVE:                    final int x = (int) event.getX();                    final int y = (int) event.getY();                    // 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;    }

上述代码可以看到up事件后调用了performClick方法

    public boolean performClick() {        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);        ListenerInfo li = mListenerInfo;        if (li != null && li.mOnClickListener != null) {            playSoundEffect(SoundEffectConstants.CLICK);            li.mOnClickListener.onClick(this);            return true;        }        return false;    }
只要li != null && li.mOnClickListener != null条件成立,就进行onClick事件处理,现在整个流程就通了。

总结,touch事件入口是dispatchTouchEvent,首先执行注册的onTouch监听,如果返回的结果是false,就会接着执行onTouchEvent。在执行onTouchEvent的时候,会执行onClick监听,这样就解释清楚了打印的顺序。在dsipatchTouchEvent 进行事件分发的时候,一旦返回true,就表示该事件已经被消费了处理了,不会再继续往下传了。如果onTouch返回true,那么它dsipatchTouchEvent 就会返回true,表示事件已被处理。不会去执行onTouchEvent了,也就更不会去处理onclick监听了。这里我们改一下上面的例子,看下打印出来的结果哈:

03-23 06:20:42.499: I/MainActivity(2006): MainActivity-dispatchTouchEvent action = action down03-23 06:20:42.499: I/MainActivity(2006): MainActivity-CustomButton-dispatchTouchEvent-ACTION_DOWN03-23 06:20:42.499: I/MainActivity(2006): CustomButton-onTouch-ACTION_DOWN03-23 06:20:42.541: I/MainActivity(2006): MainActivity-dispatchTouchEvent action = action up03-23 06:20:42.541: I/MainActivity(2006): MainActivity-CustomButton-dispatchTouchEvent-ACTION_UP03-23 06:20:42.541: I/MainActivity(2006): CustomButton-onTouch-ACTION_UP

这样onTouchEvent就没有执行了。

文章学习参考了:http://blog.csdn.net/guolin_blog/article/details/9097463

 2.viewGroup的事件分发:  dsipatchTouchEvent 方法:事件分发 和 onTouchEvent方法:事件消费  onInterceptTouchevent:事件拦截

相比较view而言,多了一个onInterceptTouchevent函数,那么它的作用是什么勒,看过另一篇文章,觉得它的比喻比较恰当和形象:onInterceptTouchevent就相当于viewgroup的一个管家,家庭秘书。为什么view没有呢,因为它没有子控件,用不着啊。一个touch事件来了,onInterceptTouchevent函数就负责判断决定是viewgroup自己消费处理呢,还是传递给它的孩子进行消费处理。
首先,自定义Layout

public class CustomLayout extends LinearLayout {private final static String TAG = "MainActivity";public CustomLayout(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stub}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:Log.i(TAG, "CustomLayout-onTouchEvent-ACTION_DOWN");break;case MotionEvent.ACTION_UP:Log.i(TAG, "CustomLayout-onTouchEvent-ACTION_UP");break;default:break;}return super.onTouchEvent(event);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:Log.i(TAG, "CustomLayout-dispatchTouchEvent-ACTION_DOWN");break;case MotionEvent.ACTION_UP:Log.i(TAG, "CustomLayout-dispatchTouchEvent-ACTION_UP");break;default:break;}return super.dispatchTouchEvent(ev);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:Log.i(TAG, "CustomLayout-onInterceptTouchEvent-ACTION_DOWN");break;case MotionEvent.ACTION_UP:Log.i(TAG, "CustomLayout-onInterceptTouchEvent-ACTION_UP");break;default:break;}return super.onInterceptTouchEvent(ev);}}
布局文件

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <com.yjx.touchhandlerdemo.ui.CustomLayout        android:id="@+id/linearlayout"        android:layout_width="200dp"        android:layout_height="200dp" >        <com.yjx.touchhandlerdemo.ui.CustomButton            android:id="@+id/view_btn"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginTop="150dp"            android:text="@string/view" />    </com.yjx.touchhandlerdemo.ui.CustomLayout></RelativeLayout>

然后修改MainActivity,添加自定义layout的click和touch事件:

public class MainActivity extends Activity implements OnClickListener {private CustomButton mViewBtn;private CustomLayout mLayout;@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);setContentView(R.layout.main);mViewBtn = (CustomButton) findViewById(R.id.view_btn);mLayout = (CustomLayout) findViewById(R.id.linearlayout);mViewBtn.setOnClickListener(this);mViewBtn.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:Log.i("MainActivity", "CustomButton-onTouch-ACTION_DOWN");break;case MotionEvent.ACTION_UP:Log.i("MainActivity", "CustomButton-onTouch-ACTION_UP");break;default:break;}return false;}});mLayout.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.i("MainActivity", "CustomLayout---onClick");}});mLayout.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:Log.i("MainActivity", "CustomLayout-onTouch-ACTION_DOWN");break;case MotionEvent.ACTION_UP:Log.i("MainActivity", "CustomLayout-onTouch-ACTION_UP");break;default:break;}return false;}});}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {// TODO Auto-generated method stubint action = ev.getAction();switch (action) {case MotionEvent.ACTION_DOWN:Log.i("MainActivity", "MainActivity-dispatchTouchEvent action = action down");break;case MotionEvent.ACTION_UP:Log.i("MainActivity", "MainActivity-dispatchTouchEvent action = action up");break;default:break;}return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubint action = event.getAction();switch (action) {case MotionEvent.ACTION_DOWN:Log.i("MainActivity", "MainActivity-onTouchEvent action = action down");break;case MotionEvent.ACTION_UP:Log.i("MainActivity", "MainActivity-onTouchEvent action = action up");break;default:break;}return super.onTouchEvent(event);}@Overridepublic void onClick(View v) {// TODO Auto-generated method stubint id = v.getId();switch (id) {case R.id.view_btn:Log.i("MainActivity", "MainActivity view_btn clicked");break;default:break;}}}

点击CustomButton,结果反馈如下:

03-23 06:25:31.962: I/MainActivity(2169): MainActivity-dispatchTouchEvent action = action down03-23 06:25:31.962: I/MainActivity(2169): CustomLayout-dispatchTouchEvent-ACTION_DOWN03-23 06:25:31.962: I/MainActivity(2169): CustomLayout-onInterceptTouchEvent-ACTION_DOWN03-23 06:25:31.962: I/MainActivity(2169): MainActivity-CustomButton-dispatchTouchEvent-ACTION_DOWN03-23 06:25:31.962: I/MainActivity(2169): CustomButton-onTouch-ACTION_DOWN03-23 06:25:31.962: I/MainActivity(2169): MainActivity-CustomButton-onTouchEvent-ACTION_DOWN03-23 06:25:32.021: I/MainActivity(2169): MainActivity-dispatchTouchEvent action = action up03-23 06:25:32.021: I/MainActivity(2169): CustomLayout-dispatchTouchEvent-ACTION_UP03-23 06:25:32.021: I/MainActivity(2169): CustomLayout-onInterceptTouchEvent-ACTION_UP03-23 06:25:32.021: I/MainActivity(2169): MainActivity-CustomButton-dispatchTouchEvent-ACTION_UP03-23 06:25:32.021: I/MainActivity(2169): CustomButton-onTouch-ACTION_UP03-23 06:25:32.021: I/MainActivity(2169): MainActivity-CustomButton-onTouchEvent-ACTION_UP03-23 06:25:32.061: I/MainActivity(2169): MainActivity view_btn clicked

上面的log打印结果显示首先调用viewgroup的dispatchTouchEvent,然后执行viewgroup的onInterceptTouchEvent拦截事件,最后执行的是子控件view的disPatchTouchEvent,剩下就和view的事件分发逻辑是一样的咯,但是为什么会是这样子的呢?

ViewGroup dispatchTouchEvent源码

 public boolean dispatchTouchEvent(MotionEvent ev) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);        }        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;            }            // 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 (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 (childrenCount != 0) {                        // Find a child that can receive the event.                        // Scan children from front to back.                        final View[] children = mChildren;                        final float x = ev.getX(actionIndex);                        final float y = ev.getY(actionIndex);                        final boolean customOrder = isChildrenDrawingOrderEnabled();                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = customOrder ?                                    getChildDrawingOrder(childrenCount, i) : i;                            final View child = children[childIndex];                            if (!canViewReceivePointerEvents(child)                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                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();                                mLastTouchDownIndex = childIndex;                                mLastTouchDownX = ev.getX();                                mLastTouchDownY = ev.getY();                                newTouchTarget = addTouchTarget(child, idBitsToAssign);                                alreadyDispatchedToNewTouchTarget = true;                                break;                            }                        }                    }                    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;    }
通过上面的代码我们可以看到dispatchTransformedTouchEvent方法的调用对childview进行touch事件分发。

onInterceptTouchEvent方法返回true后,点击Button输入结果为

03-23 06:30:07.441: I/MainActivity(2226): MainActivity-dispatchTouchEvent action = action down03-23 06:30:07.441: I/MainActivity(2226): CustomLayout-dispatchTouchEvent-ACTION_DOWN03-23 06:30:07.441: I/MainActivity(2226): CustomLayout-onInterceptTouchEvent-ACTION_DOWN03-23 06:30:07.441: I/MainActivity(2226): CustomLayout-onTouch-ACTION_DOWN03-23 06:30:07.441: I/MainActivity(2226): CustomLayout-onTouchEvent-ACTION_DOWN03-23 06:30:07.487: I/MainActivity(2226): MainActivity-dispatchTouchEvent action = action up03-23 06:30:07.487: I/MainActivity(2226): CustomLayout-dispatchTouchEvent-ACTION_UP03-23 06:30:07.487: I/MainActivity(2226): CustomLayout-onTouch-ACTION_UP03-23 06:30:07.487: I/MainActivity(2226): CustomLayout-onTouchEvent-ACTION_UP03-23 06:30:07.487: I/MainActivity(2226): CustomLayout---onClick
上面的结果我们就看出,onInterceptTouchEvent返回true后,ViewGroup把childview的事件拦截,自己消费掉了。

现在整个ViewGroup的事件分发流程的分析也就到此结束了,我们最后再来简单梳理一下吧。

1. Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。

2. 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。

3. 子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。

4.简单来讲,dispatchTouchEvent方法是为了onTouch监听的,onTouchEvent是为了onClick监听的。如果ontouch监听返回false,事件会传递到onTouchEvent当中触发onClick,如果是true的话就不会继续往下传递了。





0 0
原创粉丝点击