ViewGroup事件传递机制
来源:互联网 发布:java网上商城视频教程 编辑:程序博客网 时间:2024/06/08 10:42
前言
上一篇View的事件传递机制总结介绍了View的事件传递的过程,今天当然要看看ViewGroup的事件传递情况了。
示例代码
public class MyViewGroup extends LinearLayout { public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { System.out.println("======MyViewGroup dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { System.out.println("======MyViewGroup onInterceptTouchEvent"); return super.onInterceptTouchEvent(ev); }}
public class MainActivity extends AppCompatActivity { private Button button; private ViewGroup viewGroup; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.button); viewGroup = (ViewGroup) findViewById(R.id.viewgroup); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { System.out.println("====Button onClick"); } }); button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { System.out.println("====Button onTouch"); return false; } }); viewGroup.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { System.out.println("======MyViewGroup onClick"); } }); viewGroup.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { System.out.println("======MyViewGroup onTouch"); return false; } }); }}
我们分别点击Button和空白区域,观察控制台输出
点击按钮输出如下:
03-29 02:21:47.918 1891-1891/? I/System.out: ======MyViewGroup dispatchTouchEvent03-29 02:21:47.918 1891-1891/? I/System.out: ======MyViewGroup onInterceptTouchEvent03-29 02:21:47.918 1891-1891/? I/System.out: ====Button onTouch03-29 02:21:48.038 1891-1891/? I/System.out: ======MyViewGroup dispatchTouchEvent03-29 02:21:48.038 1891-1891/? I/System.out: ======MyViewGroup onInterceptTouchEvent03-29 02:21:48.038 1891-1891/? I/System.out: ====Button onTouch03-29 02:21:48.038 1891-1891/? I/System.out: ====Button onClick
点击空白区域输出如下:
03-29 02:24:48.541 1891-1891/? I/System.out: ======MyViewGroup dispatchTouchEvent03-29 02:24:48.541 1891-1891/? I/System.out: ======MyViewGroup onInterceptTouchEvent03-29 02:24:48.541 1891-1891/? I/System.out: ======MyViewGroup onTouch03-29 02:24:48.631 1891-1891/? I/System.out: ======MyViewGroup dispatchTouchEvent03-29 02:24:48.631 1891-1891/? I/System.out: ======MyViewGroup onTouch03-29 02:24:48.631 1891-1891/? I/System.out: ======MyViewGroup onClick
从上面的打印可以看出,当ViewGoup内嵌套Button的时候,点击Button只会执行Button的onTouch和onClick事件,当点击Button以为的区域时,才会执行ViewGroup的触摸事件。
接下来我们看看源码
public boolean dispatchTouchEvent(MotionEvent ev) { 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); 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); 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; 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); return target.dispatchTouchEvent(ev); }
1.获取当前手指在屏幕上触摸点击的坐标位置,用于判断当前手指触摸点击的是View区域还是ViewGroup区域。
2.获得disallowIntercept的值,disallowIntercept指的是是否禁用掉事件拦截功能,默认值是false,你可以调用requestDisallowInterceptTouchEvent方法修改它。
3.由于disallowIntercept默认值是false,所以条件是否满足完全取决于方法onInterceptTouchEvent返回值取反。而我们进入该方法会发现里面的实现仅仅是返回一个false。也就是if条件满足
4.通过一个for循环遍历当前ViewGroup下所以子View
5.获取遍历子View在屏幕上的坐标位置,然后代码第36行,判断当前屏幕手指触摸点击坐标是否包含遍历的子View在屏幕上的坐标位置范围?如果包含,者表示当前手指触摸点击的地方是该子View,也就是点击了Button。否则表示当前手指触摸并没有点击到ViewGroup中的子View,也就是点击到了空白区域。
6.调用子View的dispatchTouchEvent方法来处理View的触摸事件分发,这里一步就是我们上一篇博客分析的View的事件传递总结入口。在这篇博客中我们知道,当View是可点击的或者长安点击或者设置了setOnClickListener点击监听事件的,View#dispatchTouchEvent方法一律返回true,否则返回false。所以当条件满足,也就是子View设置了点击事件时ViewGroup#dispatchTouchEvent方法返回true,触摸对象mMotionTarget = child赋值成当前点击的子Viwe执行结束。因此这也验证了上面示例代码,当button设置了点击事件时只执行了Button的onClick事件,并没有执行任何关于ViewGroup的触摸点击事件。
总结:
- onInterceptTouchEvent方法是用于ViewGroup对子View的触摸事件拦截功能,默认返回false,不拦截子View的触摸事件,可以重写该方法,返回true来拦截子View的触摸事件传递。此时只会执行ViewGroup的触摸事件传递。
当子View是不可点击的且没有设置setOnClickListener点击监听事件时,会先执行子View的触摸事件,然后在执行ViewGroup的触摸事件。
现在俩验证以上两个结论。
onInterceptTouchEvent返回true
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { System.out.println("======MyViewGroup onInterceptTouchEvent"); return true; }
打印如下:
03-29 02:48:50.932 2496-2496/? I/System.out: ======MyViewGroup dispatchTouchEvent03-29 02:48:50.932 2496-2496/? I/System.out: ======MyViewGroup onInterceptTouchEvent03-29 02:48:50.932 2496-2496/? I/System.out: ======MyViewGroup onTouch03-29 02:48:51.032 2496-2496/? I/System.out: ======MyViewGroup dispatchTouchEvent03-29 02:48:51.032 2496-2496/? I/System.out: ======MyViewGroup onTouch03-29 02:48:51.042 2496-2496/? I/System.out: ======MyViewGroup onClick
由打印可以看出,当onInterceptTouchEvent返回true时,是不会执行子View的触摸点击事件的。也就是父View拦截了子View的触摸事件,不允许向下传递。
子View不可点击且没设置setOnClickListener
我们把Button修改成ImageView,并且不设置点击事件
public class MainActivity extends AppCompatActivity { private ImageView imageView; private ViewGroup viewGroup; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = (ImageView) findViewById(R.id.imageview); viewGroup = (ViewGroup) findViewById(R.id.viewgroup); imageView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { System.out.println("====Button onTouch"); return false; } }); viewGroup.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { System.out.println("======MyViewGroup onClick"); } }); viewGroup.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { System.out.println("======MyViewGroup onTouch"); return false; } }); }}
打印如下:
03-29 02:56:35.059 2735-2735/? I/System.out: ======MyViewGroup dispatchTouchEvent03-29 02:56:35.059 2735-2735/? I/System.out: ======MyViewGroup onInterceptTouchEvent03-29 02:56:35.059 2735-2735/? I/System.out: ====Button onTouch03-29 02:56:35.059 2735-2735/? I/System.out: ======MyViewGroup onTouch03-29 02:56:35.149 2735-2735/? I/System.out: ======MyViewGroup dispatchTouchEvent03-29 02:56:35.149 2735-2735/? I/System.out: ======MyViewGroup onTouch03-29 02:56:35.159 2735-2735/? I/System.out: ======MyViewGroup onClick
由打印可以看出,既执行了子View的触摸事件,也执行了ViewGroup的触摸事件,由于ImageView默认情况是不可点击的,因此:当子View不可点击或者么有设置setOnClickListener点击事件时,点击子View是先执行View的触摸事件,然后在执行ViewGroup的触摸事件的。
总结:
1.Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。
2.在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。
3.子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。
- ViewGroup事件传递机制
- ViewGroup事件传递机制
- Android ViewGroup事件传递机制
- ViewGroup的事件传递机制
- 深入探索 ViewGroup 的事件传递机制
- Android ViewGroup 触摸事件传递机制
- Android ViewGroup 触摸事件传递机制
- Android ViewGroup中事件触发和传递机制
- Android ViewGroup中事件触发和传递机制
- Android ViewGroup中事件触发和传递机制
- Android Activity 和 ViewGroup中事件触发和传递机制
- Android ViewGroup中事件触发和传递机制
- Android Activity 和 ViewGroup中事件触发和传递机制
- Android ViewGroup中事件触发和传递机制
- Android ViewGroup中事件触发和传递机制
- Android ViewGroup中事件触发和传递机制
- Android ViewGroup中事件触发和传递机制
- Android ViewGroup中事件触发和传递机制
- IMUL、MUL和div的用法
- 扣丁学堂笔记第14天Handler与多线程
- Could not open ServletContext resource [/WEB-INF/dispatcherservlet-servlet.xml]
- 导航控制器在pushViewController时的动画卡顿问题
- switch(开关按钮) 控件的用法
- ViewGroup事件传递机制
- 机器学习的学习资源--入门书-进阶书-入门视频-继续阅读推荐
- Android之XML序列化和解析
- 排序算法--冒泡排序(一)
- oracle 使用sql获取数据库表,表的字段
- Smile - Statistical Machine Intelligence and Learning Engine
- ubuntu14.04系统加固(4)
- 【ZJOI2011】【BZOJ2229】最小割
- LayoutInflater.inflate的用法总结