事件分发机制【案例】【总结】

示例之Activity

public class MainActivity  extends Activity implements OnTouchListener {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.layout_second);

findViewById(R.id.root).setTag("root");

findViewById(R.id.ll_child).setTag("ll_child");

findViewById(R.id.tv).setTag("tv");

findViewById(R.id.tv2).setTag("tv2");

findViewById(R.id.tv3).setTag("tv3");

findViewById(R.id.tv_child).setTag("tv_child");

findViewById(R.id.tv_child2).setTag("tv_child2");

findViewById(R.id.root).setOnTouchListener(this);

findViewById(R.id.ll_child).setOnTouchListener(this);

findViewById(R.id.tv).setOnTouchListener(this);

findViewById(R.id.tv2).setOnTouchListener(this);

findViewById(R.id.tv_child).setOnTouchListener(this);

findViewById(R.id.tv_child2).setOnTouchListener(this);

}

@Override

public boolean onTouch(View v, MotionEvent event) {

//ACTION_DOWN = 0,按下动作;ACTION_UP = 1,离开动作;ACTION_MOVE = 2,移动动作

Log.i("onTouch:", "onTouch--" + v.getTag() + "--" + event.getAction());

return false;

}

}

示例之布局

<com.bqt.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/root"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="#00f"

android:orientation="vertical"

android:padding="30dp" >

<com.bqt.MyTextView

android:id="@+id/tv"

android:layout_width="match_parent"

android:layout_height="65dp"

android:background="#0ff"

android:gravity="center"

android:text="LinearLayout的子View1" />

<com.bqt.MyTextView

android:id="@+id/tv2"

android:layout_width="match_parent"

android:layout_height="65dp"

android:background="#ff0"

android:gravity="center"

android:text="LinearLayout的子View2" />

<com.bqt.MyLinearLayout

android:id="@+id/ll_child"

android:layout_width="match_parent"

android:layout_height="220dp"

android:background="#000"

android:orientation="vertical"

android:padding="30dp" >

<com.bqt.MyTextView

android:id="@+id/tv_child"

android:layout_width="match_parent"

android:layout_height="65dp"

android:background="#fff"

android:gravity="center"

android:text="子子View1" />

<com.bqt.MyTextView

android:id="@+id/tv_child2"

android:layout_width="match_parent"

android:layout_height="65dp"

android:background="#f00"

android:gravity="center"

android:text="子子View2" />

</com.bqt.MyLinearLayout>

<com.bqt.MyTextView

android:id="@+id/tv3"

android:layout_width="match_parent"

android:layout_height="65dp"

android:background="#0ff"

android:gravity="center"

android:text="LinearLayout的子View3" />

</com.bqt.MyLinearLayout>

示例之MyLinearLayout

package com.bqt;

import android.content.Context;

import android.util.AttributeSet;

import android.util.Log;

import android.view.MotionEvent;

import android.widget.LinearLayout;

public class MyLinearLayout extends LinearLayout {

public MyLinearLayout(Context context) {

super(context);

}

public MyLinearLayout(Context context, AttributeSet attrs) {

super(context, attrs);

}

@Override

public boolean dispatchTouchEvent(MotionEvent event) {

Log.i("onTouch:", "dispatchTouchEvent--" + getTag() + "--" + event.getAction());

//if (getId() == R.id.ll_child) return true;//在【示例1】时添加

return super.dispatchTouchEvent(event);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

Log.i("onTouch:", "onTouchEvent--" + getTag() + "--" + event.getAction());

//if (getId() == R.id.ll_child) return true;//在【示例1】时增加此行代码没有任何意义。在【示例3】时添加。

return super.onTouchEvent(event);

}

@Override

public boolean onInterceptTouchEvent(MotionEvent event) {

Log.i("onTouch:", "onInterceptTouchEvent--" + getTag() + "--" + event.getAction());

//if (getId() == R.id.ll_child) return true;//在【示例5】时添加。

return super.onInterceptTouchEvent(event);

}

}

示例之MyTextView

public class MyTextView extends TextView {

public MyTextView(Context context) {

super(context);

}

public MyTextView(Context context, AttributeSet attrs) {

super(context, attrs);

}

@Override

public boolean dispatchTouchEvent(MotionEvent event) {

Log.i("onTouch:", "dispatchTouchEvent--TextView--" + getTag() + "--" + event.getAction());

//if (getId() == R.id.tv2) return true;//在【示例2】时添加

return super.dispatchTouchEvent(event);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

Log.i("onTouch:", "onTouchEvent--" + getTag() + "--" + event.getAction());

//if (getId() == R.id.tv2) return true;//在【示例2】时增加此行代码没有任何意义。在【示例4】时添加。在【示例5】时增加此行代码没有任何意义。

return super.onTouchEvent(event);

}

}

【示例1,VG的dispatchTE方法】

我们在【子LinearLayout】的dispatchTouchEvent方法中返回true

或者在【子LinearLayout】的dispatchTouchEvent和onTouchEvent方法中都返回true

当触摸根部的LinearLayout时



当触摸根部的子View1时



当触摸子LinearLayout或子子View1、子子View2时

可以看到:

1、触摸事件由Action_Down==0、Action_Move==2、Aciton_UP==1组成,其中一次完整的触摸事件中,Down只有一个、Up有一个或0个、Move有若干个(包括0个),一旦Aciton_UP发生,就表明此次触摸事件已经结束了。

2、不管触摸哪里,不管是触摸事件中的哪个Action(DOWN、MOVE、UP),对于同一个View来说,都是按照下面的顺序执行的:

dispatchTouchEvent --> onTouch(如果有) --> onTouchEvent

3、如果触摸点区域没有一个View的dispatchTouchEvent方法的返回值为true(大前提是:也没有一个View的onTouchEvent方法和onTouch方法的返回值为true),如当触摸根部的子View1时,则在分发完Down事件后(Down事件一定会分发),其余的Move、UP事件将不再分发,也即任何View都不会收到后续的Move、UP事件(后续事件不再分发),因为没有View需要处理。

4、同样是在上面的条件下,即:如果触摸点区域没有一个View的dispatchTouchEvent方法的返回值为true(大前提是:也没有一个View的onTouchEvent方法和onTouch方法的返回值为true),如当触摸根部的子View1时,当Down事件从root逐级下传到最底层的View1后,View1的dispatchTouchEvent又将Down事件传给了自己的onTouchEvent方法,然后是逐级逆向、上传给父View的onTouchEvent方法。

5、一旦某个View(如子LinearLayout)的dispatchTouchEvent方法返回值为true(表明事件分发到此结束),则其子View(如子子View1、子子View2)就不可能会获取到任何Touch事件(包括Down事件);但是此View的所有父View(如root)可以获取到此View能获取到的任何事件(包括Move、UP事件) 。

6、如果一个ViewGroup的dispatchTouchEvent方法返回值为true,则此ViewGroup的onTouchEvent方法和onTouch方法将不会被调用。

【示例2,View的dispatchTE方法】

我们在【子View2】的dispatchTouchEvent方法中返回true

或者在【子View2】的dispatchTouchEvent和onTouchEvent方法中都返回true

当触摸其他View时的过程和上面的情况完全一样

当触摸根部的子View2时

我们发现,示例1中的结论对于View来说也完全适用。

【示例3,VG的onTouchEvent方法】

我们仅在【子LinearLayout】的onTouchEvent方法中返回true

当触摸子LinearLayout时

可以发现,对于事件的分发过程,其和示例1、示例2完全一样。

唯一的区别是:

如果子LinearLayout的onTouchEvent方法返回true,那么子LinearLayout的onTouchEvent方法将不再逐级逆向、上传给父View的onTouchEvent方法,即其父View的onTouchEvent将无法调用。

换句话说就是,如果子LinearLayout的onTouchEvent方法返回true,那么Touch事件将会在这里完全被消耗掉。

当触摸子子View1、子子View2时

可以发现:

1、对于【DOWN事件】,虽然子LinearLayout的onTouchEvent方法返回了true,但是子子View1的dispatchTouchEvent方法仍【会】被调用,所以可以知道,任何View的onTouchEvent方法返回true都不影响DOWN事件的正常分发。

2、同样,对于【DOWN事件】,子子View1的dispatchTouchEvent被调用后会回调自己的onTouchEvent方法,然后子子View1的onTouchEvent方法会回调其父View的onTouchEvent方法。这些都和上面的分析是完全一样的。

3、下面是关键了,我们发现,对于【非DOWN事件】,子子View1的dispatchTouchEvent方法【不会】被调用(当然,子子View1的onTouchEvent方法更不可能会被调用),这意味着分发过程在此之前已经结束了。

4、完整的流程为:一旦某个View的onTouchEvent返回true,当【DOWN】事件按照【正常的分发流程】逐级【下传】到【最底层的View】的【dispatchTouchEvent】后,【最底层的View】将通过其【onTouchEvent】逐级【上传】,直到上传到【此View】的【onTouchEvent】方法之后,DOWN事件将会被消耗掉;然而,此后的【MOVE、UP】事件在按照【正常的分发流程】逐级【下传】到【此View】后将直接结束分发(而不会下传到最底层的View),并且在调用【此View】的onTouchEvent方法后被消耗掉。

5、由此可见,MOVE、UP事件仅onTouchEvent返回true的那个View及其所有父View能接收到,这也是onTouchEvent方法最核心的作用,即用来告诉系统,Touch事件到底应该【传递】给哪个View。注意,不要误解为:MOVE、UP事件仅onTouchEvent返回值为true的那个View才能接收到。而应该理解为:MOVE、UP事件仅onTouchEvent返回值为true的那个View的onTouchEvent方法才会被回调到。

6、简单的说就是:

  • 事件【向下】传递过程中,如果中间任一View的dispatchTouchEvent方法返回true,则事件将结束分发(此时不会调用任何View的onTouchEvent方法)
  • 事件【向上】传递过程中,如果中间任一View的onTouchEvent方法返回true,除了DOWN事件会正常分发外(会向下分发,dispatchTouchEvent方法会被正常回调,onTouchEvent方法会在回调到此View时结束回调),其他Touch事件在【分发】到此View之后将结束分发(不会再向下分发),且事件会在此View的onTouchEvent中被消费掉(不会再向上传递)。

【示例4,View的onTouchEvent方法】

我们仅在【子View2】的onTouchEvent方法中返回true

当触摸其他View时的过程和上面的情况完全一样

当触摸根部的子View2时



实在没啥好说的,该说的上面的都说完了。

也就是说,示例3所有的结论对于View来说也完全适用。

【示例5,VG的onInterceptTE方法】

我们仅在【子LinearLayout】的onInterceptTouchEvent方法中返回true

或者在【子LinearLayout】的onInterceptTouchEvent方法中返回true,并且在【子View2】的onTouchEvent方法中返回true

当触摸其他View时的过程和上面的情况完全一样

当触摸子LinearLayout时



可以看到,在调用dispatchTouchEvent方法之后立刻调用了View的onInterceptTouchEvent方法,除此之外没任何区别。

当触摸子子View1和子子View2时:

我们发现,事件分发过程中,子子View1、子子View2的任何回调方法都回调不到了,包括万能的DOWN事件。

另外,虽然子View2的onTouchEvent方法返回true,但是也没有任何卵用,因为在dispatchTouchEvent下发DOWN事件的过程中,在DOWN事件分发到它之前就被拦截掉了,后续也更不会收到MOVE、UP事件。

这是onInterceptTouchEvent的一个重要作用:拦截包括Down事件在内的所有事件【向下】分发。

【用onInterceptTE解决滑动冲突的思想】

设想一下在一个ViewPager中,每个Item都是个ImageView,我们需要对这些ImageView做Matrix操作,这不可避免要捕获掉Touch事件,但是我们又需要做到不影响ViewPager翻页效果,这又必须保证ViewPager能捕获到Move事件,我们该怎么做呢?

我们可以对ViewPager的onInterceptTouchEvent接收到的Move事件做一个过滤,当适当条件的Move事件(如持续若干时间或移动若干距离)触发时,会拦截掉,返回子View一个Action_Cancel事件,这个时候子View就没有Up事件了,很多需要在Up中处理的事物要转到Cancel中处理。

【面试题:滑动冲突问题如何解决?】

答:

要解决滑动冲突,其实最主要的就是有一个核心思想:你到底想在一个事件序列中让哪个view 来响应你的滑动?

比如,从上到下滑,是哪个view来处理这个事件,从左到右呢?

业务需求想明白以后,剩下的其实就很好做了。核心的方法就是2个,外部拦截也就是父亲拦截,另外就是内部拦截,也就是子view拦截法。学会这2种,基本上所有的滑动冲突都是这2种的变种,而且核心代码思想都一样。

外部拦截法:

思路就是重写父容器的onInterceptTouchEvent即可,子元素一般不需要管。可以很容易理解,因为这和android自身的事件处理机制逻辑是一模一样的,例如:

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

boolean intercepted = false;

int x = (int) ev.getX();

int y = (int) ev.getY();

switch (ev.getAction()) {

case MotionEvent.ACTION_DOWN://down事件肯定不能拦截,拦截了后面的就收不到了

intercepted = false;

break;

case MotionEvent.ACTION_MOVE:

if (你的业务需求)  intercepted = true;//如果确定拦截了,就去自己的onTouchEvent里处理拦截之后的操作即可

else  intercepted = false;

break;

case MotionEvent.ACTION_UP:

//up事件我们一般都是返回false的,一般父容器都不会拦截他, 因为up是事件的最后一步,这里返回true也没啥意义。

//唯一的意义就是,如果父元素把up拦截了,将导致子元素收不到up事件,

//那子元素就肯定没有onClick事件触发了,这里的小细节 要想明白

intercepted = false;

break;

default:

break;

}

return intercepted;

}

内部拦截法:

内部拦截法稍微复杂一点,就是事件到来的时候,父容器不管,让子元素自己来决定是否处理。如果消耗了就最好,没消耗自然就转给父容器处理了。

子元素代码:

@Override

public boolean dispatchTouchEvent(MotionEvent event) {

int x = (int) event.getX();

int y = (int) event.getY();

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

getParent().requestDisallowInterceptTouchEvent(true);

break;

case MotionEvent.ACTION_MOVE:

if (如果父容器需要这个点击事件) getParent().requestDisallowInterceptTouchEvent(false);

//否则的话就交给自己本身view的onTouchEvent自动处理了

break;

case MotionEvent.ACTION_UP:

break;

default:

break;

}

return super.dispatchTouchEvent(event);

}

父亲容器代码也要修改一下,其实就是保证父亲别拦截down事件:

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

if (ev.getAction() == MotionEvent.ACTION_DOWN)  return false;

return true;

}

时间: 2024-10-20 13:58:36

事件分发机制【案例】【总结】的相关文章

Android View 事件分发机制 源码解析 (上)

一直想写事件分发机制的文章,不管咋样,也得自己研究下事件分发的源码,写出心得~ 首先我们先写个简单的例子来测试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个MyButton继承Button,然后把跟事件传播有关的方法进行复写,然后添加上日志~ MyButton [java] view plain copy package com.example.zhy_event03; import android.content.Context; import andr

一步步理解Android事件分发机制

回想一下,通常在Android开发中,我们最常接触到的是什么东西?显然除了Activity以外,就是各种形形色色的控件(即View)了. 与此同时,一个App诞生的起因,终究是根据不同需求完成与用户的各种交互.而所谓的交互,本质就是友好的响应用户的各种操作行为. 所以说,有很多时候,一个控件(View)出现在屏幕当中,通常不会是仅仅为了摆设,而是还要能够负责响应用户的操作. 以最基本的例子而言:现在某一个界面中有一个按钮(Button),而每当用户点击了该按钮,我们的程序将做出一定回应. 那么,

Android 源码解析View的touch事件分发机制

概述 本篇主要分析的是touch事件的分发机制,网上关于这个知识点的分析文章非常多.但是还是想通过结合自身的总结,来加深自己的理解.对于事件分发机制,我将使用两篇文章对其进行分析,一篇是针对View的事件分发机制解析,一篇是针对ViewGroup的事件分发机制解析.本片是对View的事件分发机制进行解析,主要采用案例结合源码的方式来进行分析. 前言 在分析事件分发机制之前,我们先来学习一下基本的知识点,以便后面的理解. View中有两个关键方法参与到Touch事件分发 dispatchTouch

Android ViewGroup事件分发机制

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39102591,本文出自[张鸿洋的博客] 上一篇已经完整的解析了Android View的事件分发机制,今天给大家代码ViewGroup事件分发的源码解析~~凡是自定义ViewGroup实现各种滑动效果的,不可避免的会出现很多事件的冲突,对ViewGroup事件分发机制的了解,也有益于大家了解冲突产生的原因,以及对冲突进行处理~ 1.案例 首先我们接着上一篇的代码,在代码中添加一

Android View框架总结(七)View事件分发机制

请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52282833 View布局告一段落,从本篇开始View事件相关分析,今天分析的是View的事件分发机制(PS:本篇文章中源码均是android 6.0,请知晓) View 事件的分发机制 dispatchTouchEvent onInterceptTouchEvent onTouchEvent 案例 事件通常重要的有如下三种: MotionEvent.ACTION_

从ViewPager嵌套RecyclerView再嵌套RecyclerView看安卓事件分发机制

这两天伟大的PM下了一个需求,在一个竖滑列表里实现一个横向滑动的列表,没错,又是这种常见但是又经常被具有着强烈责任心和职业操守程序员所嗤之以鼻的效果,废话不多说,先上图: 实现的方式很多,因为项目中已经ViewPager+RV实现基本框架,所以现我也选择再添加一个RV实现相应的效果. 不过在写代码之前,先预估一下这个效果所有的坑. VP是横向滑动的,RV是竖向滑动的,那么现在再添加一个横向滑动的RV,肯定会有滑动冲突,主要表现在 VP和横向滑动RV 的冲突,因为两者都是横向滑动的,肯定有冲突,无

Android的Touch事件分发机制简单探析

前言 Android中关于触摸事件的分发传递是一个很值得研究的东西.曾不见你引入了一个ListView的滑动功能,ListView就不听你手指的指唤来滚动了:也不知道为啥Button设置了onClick和onTouch,其中谁会先响应:或许你会问onTouch和onTouchEvent有什么区别,又该如何使用?这里一切的一切,只要你了解了事件分发机制,你会发现,解释这都不是事儿! 相关Touch事件的方法 1.public boolean dispatchTouchEvent(MotionEve

Android事件分发机制详解:史上最全面、最易懂

前言 Android事件分发机制是每个Android开发者必须了解的基础知识 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化等等 今天,我将全面总结Android的事件分发机制,我能保证这是市面上的最全面.最清晰.最易懂的 本文秉着"结论先行.详细分析在后"的原则,即先让大家感性认识,再通过理性分析从而理解问题: 所以,请各位读者先记住结论,再往下继续看分析: 文章较长,阅读需要较长时间,建议收藏等充足时间再进行阅读 目

Android中的事件分发机制(下)——View的事件处理

综述 在上篇文章Android中的事件分发机制(上)--ViewGroup的事件分发中,对ViewGroup的事件分发进行了详细的分析.在文章的最后ViewGroup的dispatchTouchEvent方法调用dispatchTransformedTouchEvent方法成功将事件传递给ViewGroup的子View.并交由子View进行处理.那么现在就来分析一下子View接收到事件以后是如何处理的. View的事件处理 对于这里描述的View,它是ViewGroup的父类,并不包含任何的子元

Android事件分发机制

转载请注明出处:http://blog.csdn.net/chziroy/article/details/44401615 要理解Android事件分发机制,首先得了解几个概念,也算是总结,如果暂时看不懂也无妨,本文会讲解这几个问题. 1,点击屏幕,首先事件的传递从Activity的dispatchTouchEvent()方法开始. 2,关于Android事件分发机制,相关方法的方法有三个:onTouchEvent(),dispatchTouchEvent(),还有onInterceptTouc