Android 事件分发机制分析
来源:互联网 发布:pc蛋蛋幸运28算法技巧 编辑:程序博客网 时间:2024/06/01 22:21
在Android开发中,我们经常会遇到滑动冲突的情况,当遇到这种情况我们要怎么去解决它,那就需要弄明白事件分发的过程以及原理,这里我先画出了整个事件分发过程的流程图:
注释:
- 上面流程图中的super, true, false 字代表返回值(return true、return false、return super.xxxxx(),super 的意思是调用父类实现。
- dispatchTouchEvent和 onTouchEvent的框里有个【true—->消费】的字,表示如果它们的返回值是true,那么事件就在此消费,不会在往别的地方传了,即事件终止。
从上面流程图可以看出整个事件流向应该是从Activity—->ViewGroup—>View 从上往下调用dispatchTouchEvent方法,一直到最后一个View的时候,再由View—>ViewGroup—>Activity从下往上调用onTouchEvent方法;而且可以看出事件的分发过程是由三个很重要的方法来共同完成的:
dispatchTouchEvent()
该方法是用来处理事件的分发。如果事件能够传递到当前View,那么一定会调用此方法;它的返回值是个boolean类型:
如果返回true,表示事件就在此消费,不会在往别的地方传,即事件终止 ;
如果返回false,那么事件将传递到它的父View中, 并且父View执行onTouchEvent方法;
如果返回super.dispatchTouchEvent(ev),这里分两种情况,如果当前控件是View,那么执行当前View的onTouchEvent的方法;如果当前控件是ViewGroup, 那么将执行当前ViewGroup的onInterceptTouchEvent() 方法,询问是否拦截。onInterceptTouchEvent()
只有ViewGroup中有这个方法,Activity和View都没有此方法。通过ViewGroup源码我们知道,onInterceptTouchEvent是在dispatchTouchEvent方法中调用的,来判断自己是否需要拦截此事件;它的返回值是个boolean类型:
如果返回true, 表示拦截,然后执行当前控件的onTouchEvent方法;
如果返回false和返回super.onInterceptTouchEvent(ev),表示不拦截 , 事件继续向下传递(即传递到当前控件下子View的dispatchTouchEvent方法)。onTouchEvent()
和onInterceptTouchEvent()一样也是在dispatchTouchEvent中调用的。用来处理点击事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP。它的返回值是个boolean类型:
如果返回true,表示消费当前view的onTouchEvent事件,不在向上传递;如果当前容器是ViewGroup,它的onTouchEvent返回true,那么后续执行move,up时 ,它的onInterceptTouchEvent不在执行;
如果返回false,表示不消费当前view的onTouchEvent事件,那么事件将传递到它的父View中, 并且父View执行onTouchEvent方法;
如果返回super.onTouchEvent(event),这里分两种情况,如果这个view是可点击的(比如:button),也就是clickable或者longClickable为true(View的longClickable默认为false),这种情况super.onTouchEvent(event)返回的是true,表示消费当前view的onTouchEvent事件,不在向上传递;如果这个view是不可点击的(比如:TextView),也就是clickable和longClickable都为false,这种情况super.onTouchEvent(event)返回的是false,表示不消费当前view的onTouchEvent事件,继续向上传递(即传递到当前控件的父View的onTouchEvent方法)。在这里要强调View的OnTouchListener。如果View设置了该监听,那么OnTouch()将会回调。如果返回为true,那么该View的OnTouchEvent将不会在执行,从源码(截取关键代码)中我们也可以看出来:
/** * 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 (onFilterTouchEventForSecurity(event)) { ..... //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } } }
上面代码中,我们可以看到当li.mOnTouchListener.onTouch(this, event)返回true时,那么程序就会走到if方法体里面,这时直接返回了true,所以下面的OnTouchEvent代码就没有执行到,所以如果你设置了OnClickListener监听,onClick里面的方法也不会执行。
好了,上面对dispatchTouchEvent,OnInterceptTouchEvent ,OnTouchEvent方法做了详细讲解,下面我们通过事例来更进一步的理解事件分发的过程,我们这里新建一个项目,布局如图:
ViewGroupA嵌套ViewGroupB , 然后在嵌套ViewC,然后在相应的控件中重写dispatchTouchEvent,OnInterceptTouchEvent ,OnTouchEvent方法(View中没有OnInterceptTouchEvent),这里在Activity也重写dispatchTouchEvent,OnTouchEvent方法,并在每个方法中输出相应的Log:
Activity中:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("lw", "Activity----->dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("lw", "Activity----->onTouchEvent"); return super.onTouchEvent(event); }}
ViewGroupA中:
public class ViewGroupA extends LinearLayout{ public ViewGroupA(Context context) { super(context); } public ViewGroupA(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public ViewGroupA(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("lw", "ViewGroupA----->dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d("lw", "ViewGroupA----->onInterceptTouchEvent"); return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("lw", "ViewGroupA----->onTouchEvent"); return super.onTouchEvent(event); }}
ViewGroupB中:
public class ViewGroupB extends LinearLayout{ public ViewGroupB(Context context) { super(context); } public ViewGroupB(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public ViewGroupB(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("lw", "ViewGroupB----->dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d("lw", "ViewGroupB----->onInterceptTouchEvent"); return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("lw", "ViewGroupB----->onTouchEvent"); return super.onTouchEvent(event); }}
ViewC中:
public class ViewC extends View { public ViewC(Context context) { super(context); } public ViewC(Context context, AttributeSet attrs) { super(context, attrs); } public ViewC(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("lw", "ViewC----->dispatchTouchEventC"); return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("lw", "ViewC----->onTouchEventC"); return super.onTouchEvent(event); }}
不做任何修改,运行程序,点击ViewC:
从log信息中可以看出,事件传递顺序是:
Activity ——> ViewGroup ——-> View; 首先会先执行Activity的 dispatchTouchEvent方法,然后执行ViewGroupA的dispatchTouchEvent方法,接着会调用onInterceptTouchEvent方法来判断是否需要拦截事件,默认是不拦截的,接着事件又会传递到ViewGroupA的子View(ViewGroupB)。即ViewGroupB 的dispatchTouchEvent方法被调用,直到ViewC,当事件传递到ViewC后,ViewC的dispatchTouchEvent方法又会执行。此时如果ViewC 不消费该事件,也就是说ViewC的onTouchEvent方法返回false,那么事件会向上传递给它的父View(ViewGroupB)的OnTouchEvent方法事件处理,以此类推。
将MainActivity中dispatchTouchEvent方法的返回值改成true,然后点击ViewC:
从log信息中可以看出,将MainActivity中dispatchTouchEvent方法的返回值改成true后,事件传递到
dispatchTouchEvent就终止了,不在往任何地方传递了。
将MainActivity中dispatchTouchEvent方法的返回值改成false,然后点击ViewC:
从log信息中可以看出,将MainActivity中dispatchTouchEvent方法的返回值改成false和返回值改成true都是一样的效果,都是终止事件的传递。
将ViewGroupB中dispatchTouchEvent方法的返回值改成true,然后点击ViewC:
从log信息中可以看出,将MainActivity中dispatchTouchEvent方法的返回值改成true后,事件传递到
dispatchTouchEvent就终止了,不在往任何地方传递了。
从log信息中可以看出,将ViewGroupB中dispatchTouchEvent方法的返回值改成true后,事件传递到
dispatchTouchEvent就终止了,不在往任何地方传递了。
将ViewGroupB中dispatchTouchEvent的返回值改成false,然后点击ViewC:
从log信息中可以看出,将ViewGroupB中dispatchTouchEvent方法的返回值改成false后,事件传递到
dispatchTouchEvent,然后又将事件向上传递给了ViewGroupA(即它的父元素)的onTouchEvent方法。
将ViewGroupB中onInterceptTouchEvent的返回值改成true,然后点击ViewC:
从log信息中可以看出,将ViewGroupB中onInterceptTouchEvent方法的返回值改成true后,事件没有向下传递,而是执行了当前ViewGroupB中的onTouchEvent方法。
将ViewGroupB中onInterceptTouchEvent的返回值改成false,然后点击ViewC:
看log没啥好说的,将ViewGroupB中onInterceptTouchEvent的返回值改成false,和将ViewGroupB中onInterceptTouchEvent的返回值改成super.onInterceptTouchEvent(ev)一样的效果,都表示不拦截事件,继续向下传递。
将ViewC中onTouchEvent的返回值改成true,然后点击ViewC:
从log信息中可以看出 , 将ViewC中onTouchEvent方法的返回值改成true后,事件就在此消费,不在向上传递了,事件终止。
将ViewC中onTouchEvent的返回值改成false,然后点击ViewC:
从log信息中可以看出 , 将ViewC中onTouchEvent方法的返回值改成false后,事件不消费,并且将事件向上传递给了ViewGroupB(它的父View)的onTouchEvent方法。
了解下requestDisallowInterceptTouchEvent:
requestDisallowInterceptTouchEvent方法是子View用来告诉父容器不要拦截子View事件的。
下面在ViewC代码中中,添加requestDisallowInterceptTouchEvent方法:
public class ViewC extends View { public ViewC(Context context) { super(context); } public ViewC(Context context, AttributeSet attrs) { super(context, attrs); } public ViewC(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("lw", "ViewC----->dispatchTouchEventC"); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.d("lw", "ViewC dispatchTouchEvent ACTION_DOWN"); getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: Log.d("lw", "ViewC dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: getParent().requestDisallowInterceptTouchEvent(false); Log.d("lw", "ViewC dispatchTouchEvent ACTION_UP"); break; default: break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("lw", "ViewC----->onTouchEventC"); return true; }}
在ViewC中添加requestDisallowInterceptTouchEvent相关代码,然后点击ViewC:
首先我们要先消费当前View,即让onTouchEvent返回时true,将事件交给当前View处理,这样后续的action(move,up)才会执行。
requestDisallowInterceptTouchEvent(true)表示当前View的所有父ViewGroup都会跳过onInterceptTouchEvent方法,父ViewGroup执行dispatchTouchEvent后,不在询问是否拦截,直接传递到子view,子view执行dispatchTouchEvent,上面的log也证明了这一点。
requestDisallowInterceptTouchEvent(false)与true的情况相反,它不会跳过onInterceptTouchEvent方法,父ViewGroup执行dispatchTouchEvent后,也会执行onInterceptTouchEvent方法,询问是否拦截法。
好了 ,这样View的整个事件分发的流程我们就差不多清楚了,不过这里需要注意的是,如果你在执行ACTION_DOWN的时候返回了false,那么后面一系列其它的action(move,up)就不会再执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。
- Android事件分发机制分析
- android事件分发机制分析
- Android 事件分发机制分析
- 分析Android的Touch事件分发机制
- Android事件分发机制源码分析
- Android事件分发机制源码分析
- Android事件分发机制源码分析
- Android 事件拦截和分发机制分析
- Android事件分发机制案例分析(一)
- Android事件分发机制源码分析
- 源码分析android的事件分发机制
- Android事件分发机制代码片段分析
- Android 事件分发机制-源码分析
- Android 事件拦截机制、事件分发机制简单分析
- android事件分发机制
- Android事件分发机制
- Android 事件分发机制
- Android事件分发机制
- Zookeeper安装使用
- HTML | <meta> 的详细属性使用 名称/值对
- 关于Class对象、类加载机制、虚拟机运行时内存布局的全面解析和推测
- ImageNet 1000个类 具体内容
- Oracle connect by level
- Android 事件分发机制分析
- JavaWeb(四)EL表达式
- 写过的代码笔记
- Linux应用层的i2c读写
- xcode里面找不到头文件
- java多线程总结学习-Queue、容器、单例模式
- CentOS 下配置 MongoDB 为服务
- Windows程序设计-动态链接库
- Linux ALSA声卡驱动之五:移动设备中的ALSA