关于android事件分发之ViewGroup
2015-06-04 21:56
483 查看
感觉现在对touch事件的分发运用的越来越熟练,赶紧记录下来,免得以后忘记了。上一篇博客已经详细的讲解了关于View的touch事件的,感觉还不过瘾,赶紧把ViewGroup事件的传递也总结一下吧。
还是通过具体的实例进行总结吧。
实例中自定义MyLayout继承自LinearLayout。请看布局代码。
有两个button。我们分别注册click和touch事件。请看代码。
请两个button注册了单击事件,和布局注册了touch事件。
我现在分别点击button1和button2,并且触摸了button以外的区域。输出结果如下:
点击button1和button2时仅仅是执行了onclick,却没有执行ontouch。那我们就有一个疑问了,这个touch事件到底是从ViewGroup传递给View呢,还是从View传递到ViewGroup。关于这个问题,我们可以先放一放。先看看ViewGroup中的onInterceptTouchEvent方法的源码吧。
这个方法的作用就是判断是否拦截touch事件的分发,如果返回true,表示拦截,这时候就不会传递给子View,如果返回false,表示不拦截,正常传递给View即可。所有说touch事件的传递应该是从ViewGroup传递给View,需要层层传递的。那我们不妨在MyLayout方法中重写onInterceptTouchEvent方法,让它返回true,拦截一下试试,拦截以后,button应该是不能响应onclick事件才对,这时候MyLayout就不会讲touch事件传递给Button了,点击时只会执行MyLayout的touch事件才对。不妨拭目以待吧。
输出结果为:
我点击了button1和button1,和其他的区域。这说明MyLayout拦截了事件,没有传递到Button。上一篇我们就说过不管是View还是ViewGroup事件的分发都是从dispatchTouchEvent开始的,我们不妨看看ViewGroup代码吧。
今天的总结就要告一段落,我们再回顾一下ViewGroup事件分发的知识点吧。
1. touch事件的传递是从ViewGroup经过层层传递到View的。
2. 对于ViewGroup事件的传递也是从dispatchTouchEvent方法开发分发。
3.如果某一个ViewGroup想要拦截事件分发到子view上去,可以重写ViewGroup中的onInterceptTouchEvent方法。
4. 如果想要让ViewGroup不拦截事件,让touch事件顺利的传递到View,让View来处理这个手势的话,有两个方法,requestDisallowInterceptTouchEvent(true),而另一个就是重写方法onInterceptTouchEvent,即可解决。
下一篇将要总结touch事件在我工作中遇到的问题,以及是如何解决的。
还是通过具体的实例进行总结吧。
实例中自定义MyLayout继承自LinearLayout。请看布局代码。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.example.toucheventdemo.MyLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:id="@+id/ll" > <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="click 1" android:id="@+id/btn1" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="click 2" android:id="@+id/btn2" /> </com.example.toucheventdemo.MyLayout> </LinearLayout>
有两个button。我们分别注册click和touch事件。请看代码。
btn1.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.d(tag, "button 1 click"); } }); btn2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.d(tag, "button 1 click"); } }); layout.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.d(tag, "touch"); return false; } });
请两个button注册了单击事件,和布局注册了touch事件。
我现在分别点击button1和button2,并且触摸了button以外的区域。输出结果如下:
06-04 23:34:48.096: D/MainActivity(19416): button 1 click 06-04 23:34:50.646: D/MainActivity(19416): button 1 click 06-04 23:34:51.496: D/MainActivity(19416): touch
点击button1和button2时仅仅是执行了onclick,却没有执行ontouch。那我们就有一个疑问了,这个touch事件到底是从ViewGroup传递给View呢,还是从View传递到ViewGroup。关于这个问题,我们可以先放一放。先看看ViewGroup中的onInterceptTouchEvent方法的源码吧。
/** * Implement this method to intercept all touch screen motion events. This * allows you to watch events as they are dispatched to your children, and * take ownership of the current gesture at any point. * * <p>Using this function takes some care, as it has a fairly complicated * interaction with {@link View#onTouchEvent(MotionEvent) * View.onTouchEvent(MotionEvent)}, and using it requires implementing * that method as well as this one in the correct way. Events will be * received in the following order: * * <ol> * <li> You will receive the down event here. * <li> The down event will be handled either by a child of this view * group, or given to your own onTouchEvent() method to handle; this means * you should implement onTouchEvent() to return true, so you will * continue to see the rest of the gesture (instead of looking for * a parent view to handle it). Also, by returning true from * onTouchEvent(), you will not receive any following * events in onInterceptTouchEvent() and all touch processing must * happen in onTouchEvent() like normal. * <li> For as long as you return false from this function, each following * event (up to and including the final up) will be delivered first here * and then to the target's onTouchEvent(). * <li> If you return true from here, you will not receive any * following events: the target view will receive the same event but * with the action {@link MotionEvent#ACTION_CANCEL}, and all further * events will be delivered to your onTouchEvent() method and no longer * appear here. * </ol> * * @param ev The motion event being dispatched down the hierarchy. * @return Return true to steal motion events from the children and have * them dispatched to this ViewGroup through onTouchEvent(). * The current target will receive an ACTION_CANCEL event, and no further * messages will be delivered here. */ public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
这个方法的作用就是判断是否拦截touch事件的分发,如果返回true,表示拦截,这时候就不会传递给子View,如果返回false,表示不拦截,正常传递给View即可。所有说touch事件的传递应该是从ViewGroup传递给View,需要层层传递的。那我们不妨在MyLayout方法中重写onInterceptTouchEvent方法,让它返回true,拦截一下试试,拦截以后,button应该是不能响应onclick事件才对,这时候MyLayout就不会讲touch事件传递给Button了,点击时只会执行MyLayout的touch事件才对。不妨拭目以待吧。
public class MyLayout extends LinearLayout { public MyLayout(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } }
输出结果为:
06-04 23:50:28.736: D/MainActivity(23257): touch 06-04 23:50:30.306: D/MainActivity(23257): touch 06-04 23:50:31.916: D/MainActivity(23257): touch
我点击了button1和button1,和其他的区域。这说明MyLayout拦截了事件,没有传递到Button。上一篇我们就说过不管是View还是ViewGroup事件的分发都是从dispatchTouchEvent开始的,我们不妨看看ViewGroup代码吧。
/** * {@inheritDoc} */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (!onFilterTouchEventForSecurity(ev)) { return false; } final int action = ev.getAction(); final float xf = ev.getX(); final float yf = ev.getY(); final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (action == MotionEvent.ACTION_DOWN) { if (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 we're disallowing intercept or if we're allowing and we didn't // intercept if (disallowIntercept || !onInterceptTouchEvent(ev)) { // 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; final int count = mChildrenCount; for (int i = count - 1; i >= 0; i--) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { child.getHitRect(frame); if (frame.contains(scrolledXInt, scrolledYInt)) { // offset the event to the view's coordinate system final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; if (child.dispatchTouchEvent(ev)) { // Event handled, we have a target now. mMotionTarget = child; return true; } // The event didn't get handled, try the next view. // Don't reset the event's location, it's not // necessary here. } } } } } 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) { // 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); } // if have a target, see if we're allowed to and want to intercept its // events if (!disallowIntercept && onInterceptTouchEvent(ev)) { 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; } // 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); }代码比较长,我们重点看29行, if (disallowIntercept || !onInterceptTouchEvent(ev)),这个if是或的关系,只要有一个满足条件即可。其中disallowIntercept,表示是否禁止事件的分发,默认为false,我们可以通过getParent().requestDisallowInterceptTouchEvent(true)修改这个值,这个方法非常有用,我会在下一篇博客中进行介绍。而第二个onInterceptTouchEvent方法就行我们刚刚重写的方法了,默认返回false,那么这个表达式就为true,就会执行下边的for循环了,找到你触摸区域的View,然后调用View的dispatchTouchEvent方法,这时候流程就我们上一篇介绍的View的事件分发过程一样了。当我们重写onInterceptTouchEvent方法,返回true时,就不会执行for循环了,就会执行76行的代码,最终调用super.dispatchTouchEvent(ev)的方法,那就View的分发了,所有最终我们看到只执行touch事件,而没有执行onclick。
今天的总结就要告一段落,我们再回顾一下ViewGroup事件分发的知识点吧。
1. touch事件的传递是从ViewGroup经过层层传递到View的。
2. 对于ViewGroup事件的传递也是从dispatchTouchEvent方法开发分发。
3.如果某一个ViewGroup想要拦截事件分发到子view上去,可以重写ViewGroup中的onInterceptTouchEvent方法。
4. 如果想要让ViewGroup不拦截事件,让touch事件顺利的传递到View,让View来处理这个手势的话,有两个方法,requestDisallowInterceptTouchEvent(true),而另一个就是重写方法onInterceptTouchEvent,即可解决。
下一篇将要总结touch事件在我工作中遇到的问题,以及是如何解决的。
相关文章推荐
- Android获得SD卡路径
- 第一篇 android_AR_导航
- Android多线程从入门到精通
- android调用百度地图定位与附近搜索
- Android流量监控
- Android的数字选择器NumberPicker-android学习之旅(三十七)
- Android的数字选择器NumberPicker-android学习之旅(三十七)
- Android的数字选择器NumberPicker-android学习之旅(三十七)
- Android程序启动加载动画实现
- 【转】Android虚拟平台的编译和整合
- Android-Canvas&Drawable
- android设置图片自适应控件大小
- android开发之流量监控
- Android Service完全解析,关于服务你所需知道的一切(下)
- [android] OpenGL与OpenGL ES简介
- 基于 Android NDK 的学习之旅----- C调用Java
- Android Service完全解析,关于服务你所需知道的一切(上)
- Android必备的Java知识点
- Android的DatePicker和TimePicker-android学习之旅(三十八)
- Android的DatePicker和TimePicker-android学习之旅(三十八)