转载:http://blog.csdn.net/jiangwei0910410003/article/details/17504315#quote

博主推荐:

风萧兮兮易水寒,“天真”一去兮不复还。如何找到天真的那份快乐。小编倾力推荐app: 天真无谐

下载方式:豌豆荚,应用宝,360手机助手,百度手机助手,安卓,91市场搜索:天真无谐

关注我们:查看详情

$*********************************************************************************************$

今天在做项目的时候遇到一个问题,就是怎么让ListView中的item点击后其内部的内容跟着变色,比如现在我的item布局中有一个TextView,现在点击item的时候,让其背景色发生改变,这个我们可以为item布局背景定义一个selctor.xml就可以了,但是现在的问题是item内容布局中的TextView中的内容也要跟着变色,这个立马想到了触摸监听器onTouch方法,只需要在ACTION_UP和ACTION_DOWN中实现对TextView的字体颜色的改变就可以了,

但是现在遇到一个问题,就是当我实现了onTouch方法的时候,ListView的点击事件就没有了(setOnItemClick方法),上网查看了很多内容就是说onTouch方法的返回值问题。如果onTouch方法返回true的话,setOnItemClick方法就不执行了,但是我把onTouch的返回值设置成false之后,出现一个问题就是onTouch方法只执行了一次就是ACTION_DOWN中的代码,而ACTION_UP中的代码并没有执行,很是纠结的时候最后找到的方法就是在ListView的适配器中的getView方法中对contentView实现onClick方法就可以了,

具体原因下面来分析一下:

问题是解决了,但是内部的原理要弄清楚一下,于是查看了View和ViewGroup中的关于onTouch,onClick方法的源代码,问题总算弄明白了:

现在主要有两个问题:

(1) 第一个问题是控件本省的onTouch和onClick方法的执行冲突

(2) 第二个问题是子控件和父控件之间的onTouch执行过程的分析

首先自定义一个MyLayout继承LinearLayout,重写dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent方法,代码如下:

  1. package com.bbdtek.demo;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.widget.LinearLayout;
  7. public class MyLayout extends LinearLayout {
  8. @Override
  9. public boolean dispatchTouchEvent(MotionEvent ev) {
  10. Log.e("Demo:","父View的dispatchTouchEvent方法执行了");
  11. return super.dispatchTouchEvent(ev);
  12. }
  13. @Override
  14. public boolean onInterceptTouchEvent(MotionEvent ev) {
  15. Log.e("Demo:","父View的onInterceptTouchEvent方法执行了");
  16. return super.onInterceptTouchEvent(ev);
  17. }
  18. @Override
  19. public boolean onTouchEvent(MotionEvent event) {
  20. Log.e("Demo:","父View的onTouchEvent方法执行了");
  21. return super.onTouchEvent(event);
  22. }
  23. public MyLayout(Context context) {
  24. super(context);
  25. }
  26. public MyLayout(Context context,AttributeSet attr) {
  27. super(context,attr);
  28. }
  29. }

在这三个方法中打印一段信息,有助于查看这三个方法的执行流程

下面是自定义一个MyTextView继承TextView,重写dispatchTouchEvent,onTouchEvent方法(这里没有onInterceptTouchEvent方法,原因很简单,就是TextView是一个View没有子View了,不需要拦截信息了)代码如下:

  1. package com.bbdtek.demo;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.KeyEvent;
  6. import android.view.MotionEvent;
  7. import android.widget.TextView;
  8. public class MyTextView extends TextView{
  9. @Override
  10. public boolean dispatchTouchEvent(MotionEvent ev) {
  11. Log.e("Demo:","子View的dispatchTouchEvent方法执行了");
  12. return super.dispatchTouchEvent(ev);
  13. }
  14. @Override
  15. public boolean onTouchEvent(MotionEvent event) {
  16. Log.e("Demo:","子View的onTouchEvent方法执行了");
  17. return super.onTouchEvent(event);
  18. }
  19. public MyTextView(Context context) {
  20. super(context);
  21. }
  22. public MyTextView(Context context, AttributeSet attrs) {
  23. super(context, attrs);
  24. }
  25. public MyTextView(Context context, AttributeSet attrs,int defStyle) {
  26. super(context, attrs, defStyle);
  27. }
  28. }

在方法中输出log信息,便于查看方法的执行流程

定义的一个布局文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent">
  6. <com.bbdtek.demo.MyLayout
  7. android:id="@+id/layout"
  8. android:layout_width="fill_parent"
  9. android:layout_height="fill_parent"
  10. android:orientation="vertical">
  11. <com.bbdtek.demo.MyButton
  12. android:id="@+id/btn"
  13. android:layout_width="wrap_content"
  14. android:layout_height="wrap_content"
  15. android:text="点我"/>
  16. <com.bbdtek.demo.MyTextView
  17. android:id="@+id/txt1"
  18. android:layout_width="fill_parent"
  19. android:layout_height="wrap_content"
  20. android:text="我是个好人我是个好人呢我是啊速度将法律上的经费福建省的雷锋精神多了爽肤水两地分居SD卡飓风桑迪快乐分类数据的法律进多少"
  21. android:textSize="25dp"/>
  22. </com.bbdtek.demo.MyLayout>
  23. </LinearLayout>

MyLayout中包含一个MyTextView控件:

下面来看一下测试代码:

  1. package com.bbdtek.demo;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.view.View;
  7. import android.view.View.OnClickListener;
  8. import android.view.View.OnTouchListener;
  9. public class AndroidDemoActivity extends Activity{
  10. public void onCreate(Bundle savedInstanceState) {
  11. super.onCreate(savedInstanceState);
  12. setContentView(R.layout.main);
  13. MyTextView txt1 = (MyTextView)findViewById(R.id.txt1);
  14. //MyLayout layout = (MyLayout)findViewById(R.id.layout);
  15. txt1.setOnClickListener(new OnClickListener(){
  16. @Override
  17. public void onClick(View v) {
  18. Log.e("Demo:","点击了txt1");
  19. }});
  20. //给MyTextView定义一个onTouchListener监听器
  21. txt1.setOnTouchListener(new OnTouchListener(){
  22. @Override
  23. public boolean onTouch(View v, MotionEvent event) {
  24. if(event.getAction() == MotionEvent.ACTION_DOWN){
  25. Log.e("Demo:","txt1按下");
  26. }else if(event.getAction() == MotionEvent.ACTION_UP){
  27. Log.e("Demo:","txt1弹起");
  28. }
  29. return false;
  30. }});
  31. //给MyLayout定义一个onTouchListener监听器
  32. /*layout.setOnClickListener(new OnClickListener(){
  33. @Override
  34. public void onClick(View v) {
  35. Log.e("Demo:","点击了父layout");
  36. }});*/
  37. }
  38. }

看一下运行结果:

可以看到先执行的是MyTextView的dispatchTouchEvent方法(这个方法是每次触发onTouch方法都会执行的),然后是执行了onTouch方法中的ACTION_UP中的代码,然后执行了MyTextView中的onTouchEvent方法,当用户弹起手指的时候又一次执行了这样的一个过程,最后就是执行了onClick方法,在这里就来看一下onTouchEvent中的源代码:

因为TextView继承View,可以查看View中的onTouchEvent方法,在这个方法中又调用了ViewGroup中的onTouchEvent方法,下面在来看一下ViewGroup中的onTouchEvent方法:

在ACTION_UP中的这段代码就是执行了onClick方法,具体可以看一下performClick方法解释:

mOnClickListener就是OnClickListener监听器,执行了onClick方法,所以上面的onClick方法是在ACTION_UP之后执行了。

下面在来看一下这种情况,现在把onTouch方法的返回值改成true:看一下执行结果:

可以看到首先还是执行了MyTextView的dispatchTouchEvent方法,然后执行了onTouch中的ACTION_DOWN代码,同样当用户的弹起手指的时候执行了同样的过程,现在的问题是这样的执行过程为什么和上面的不一样呢?

首先来看一下有哪些不一样的地方:

第一个不一样的地方就是”子View的onTouchEvent方法执行了“这句话没有执行,那就是MyTextView中的onTouchEvent方法没有执行了,来看一下源码:

在View中的dispatchTouchEvent方法中可以看到,是先执行OnTouchListener监听器中的onTouch方法,如果onTouchListener不为null,并且onTouch方法返回false的时候才执行onTouchEvent方法,现在我们把onTouch方法的返回值变成了true,所以onTouchEvent方法就不执行了。这里一定要注意onTouchEvent方法和onTouch方法的区别。

第二个不一样的地方就是onClick方法没有执行了,对于这样的问题,在源代码中没有找到答案,但是记住一点就可以了那就是如果onTouch方法返回true,说明View这次消费了这次事件,所以就不会再执行后续的onClick方法了,

从上面的例子可以看出来:onClick方法的执行时依赖于onTouch方法的,下面就总结一下onClick和onTouch两个方法之间的执行关系:

如果该View 是disable 状态:那么给它加上pressed标志位,重绘一下(在屏幕上显示为灰显按下去的效果)
如果该View不是disable状态,并且是clickable,那么在ACTION_DOWN的时候会加上pressed标志位,并启动一个timer(长按事件onLongClickListener)。重绘一下(显示出按下去的效果)。此时还是MOVE事件监视该View的状态,如果这个MOVE滑出了该View的范围,那么会复位View的click状态。UP事件时,会检查是否已经执行过LongPress,如果已经执行了LongPress,那么就不执行了Click,反之,会取消掉LongPress的Timer,然后在执行Click。
在UP事件的最后,做一下收尾处理,完事。
所以TouchEvent是Click 和 LongPress的底层实现,View的派生类如Button等等就不需要重写OnTouchEvent。

现在来看一下第二个问题就是:父控件和子控件的onTouch方法的执行流程:

首先来看一下测试代码:

  1. package com.bbdtek.demo;
  2. public class AndroidDemoActivity extends Activity{
  3. public void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.main);
  6. MyTextView txt1 = (MyTextView)findViewById(R.id.txt1);
  7. MyLayout layout = (MyLayout)findViewById(R.id.layout);
  8. txt1.setOnClickListener(new OnClickListener(){
  9. @Override
  10. public void onClick(View v) {
  11. Log.e("Demo:","点击了txt1");
  12. }});
  13. //给MyTextView定义一个onTouchListener监听器
  14. txt1.setOnTouchListener(new OnTouchListener(){
  15. @Override
  16. public boolean onTouch(View v, MotionEvent event) {
  17. if(event.getAction() == MotionEvent.ACTION_DOWN){
  18. Log.e("Demo:","txt1按下");
  19. }else if(event.getAction() == MotionEvent.ACTION_UP){
  20. Log.e("Demo:","txt1弹起");
  21. }
  22. return false;
  23. }});
  24. //给MyTextView定义一个onTouchListener监听器
  25. layout.setOnTouchListener(new OnTouchListener(){
  26. @Override
  27. public boolean onTouch(View v, MotionEvent event) {
  28. if(event.getAction() == MotionEvent.ACTION_DOWN){
  29. Log.e("Demo:","父layout按下");
  30. }else if(event.getAction() == MotionEvent.ACTION_UP){
  31. Log.e("Demo:","父layout弹起");
  32. }
  33. return false;
  34. }});
  35. //给MyLayout定义一个onTouchListener监听器
  36. layout.setOnClickListener(new OnClickListener(){
  37. @Override
  38. public void onClick(View v) {
  39. Log.e("Demo:","点击了父layout");
  40. }});
  41. }
  42. }

点击MyTextView后观察log信息如下:

这样就可以清楚的看到onTouch的执行过程了:

这三种情况就详细说明了这个执行过程,其中dispatchTouchEvent方法没有标注,比较简单,首先会执行父控件的dispatchTouchEvent方法,下面在来看一种情况,就是在测试代码中将这些代码注释:

/*txt1.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Log.e("Demo:","点击了txt1");
}});*/

看一下运行结果:

这里就是把MyTextView的点击监听器注释了,但是运行的结果差距很大:我们一步一步来分析一下:

首先来看一下View的setOnClickListener方法的源代码:

这个方法很简单就是设置监听器mOnClickListener,但是之前还有一段代码那就是setClick(true);从注释中可以看到,如果设置了点击监听器说明这个控件就是可以点击的了,再来看一下setClick方法:

这个方法就是设置一下可以点击的标志变量了,现在再来看一下onTouchEvent方法:

由于onTouchEvent方法的代码太长了,不要截图,但是大体意思是如果这个控件是可以点击的,那么onTouchEvent方法就返回true,

当方法onTouchEvent方法返回true的时候说明这次事件被该控件消费了,不会再往上传递了,所以,我们给txt1添加onClick监听器的时候,运行结果中可以看到父控件的onTouch方法没有执行;

当我们把onClick监听器删除的时候,父控件的onTouch方法执行了,这次的事件被父控件消费了,所以txt1的onTouch中的ACTION_UP中的代码就没有执行了。为了验证这一点其实很简单,你可以直接设置txt1.setClickable(false);这个方法通过设置false和true来观察结果;

这里还有一个重要的信息是Android中像Button,CheckBox这样的控件默认都是可以点击的,所以不需要setClickable(true)方法来实现了,但是像TextView这样的控件默认是不可点击的,所以要通过setClickable(true)这样的方法来实现。

当然你也可以通过设置dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent这三个方法的返回值来观察结果,具体可以查看我的另外一篇文章:http://blog.csdn.net/jiangwei0910410003/article/details/16986039

至此,上述的两个问题就解决了,最大的收获还是学会了怎样去查找问题,最好的方式就是看源码!

现在回到刚开始的地方,就是那个ListView为什么用setOnItemClick这个方法和onTouch这个方法有冲突?

现在来想一想其实很简单了,因为如果onTouch方法中返回true的话,这次事件就被ListView中的item控件消费了,所以不会执行ListVIew的setOnItemClick这个方法了,如果onTouch方法返回false,那么会执行setOnItemClick方法,同时事件会被ListView消费了,所以onTouch方法只会执行ACTION_DOWN中的代码了,这里也说明了一点就是ListView的setOnItemClick方法和在getView中单独给item添加onClick方法的效果是不一样的。

同样还有另外的一个问题就是ListView中的Item中如果有Button,CheckBox等这样的组件的话,ListView中的setOnItemClick方法就是失效了,原因是Item没有获取焦点,焦点被Button等控件默认获取到了,这里有两种解决方法:

第一种就是让Button控件失去焦点,可以在布局文件中设置代码:android:focusable="false"即可

第二种就是把setOnItemClick方法中的逻辑代码方法getView中的contentView的onClick方法中

为什么要这么做,有待研究呀!今天就写到这里了,头都写大了,很纠结,也很开心呀,如果发现有什么不正确的地方,希望给予批评和指正,本人将不胜感激!

事件之onTouch方法的执行过程 及和 onClick执行发生冲突的解决办法的更多相关文章

  1. Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法

    $*********************************************************************************************$ 博主推荐 ...

  2. php脚本的执行过程(编译与执行相分离)

    php脚本的执行过程(编译与执行相分离) 深入理解PHP代码的执行的过程 PHP程序的执行流程 Apache + PHP 的并发访问

  3. jQuery的AJAX方法简介及与其他文件$符号冲突的解决办法

    一.重要的jQuery AJAX方法简介 $.load(url) 从服务器载入数据 $.get(url,callback) 从服务器请求数据,并执行回调函数 $.post(url,data,callb ...

  4. Android onTouchEvent事件中onTouch方法返回值介绍

    1.若return false说明没有成功执行onTouch事件,在执行完onTouch里面的代码之后,onTouch事件并没有结束.因此某些组件如Gallery会自动执行它所在view里onTouc ...

  5. 在Linux下安装PHP过程中,编译时出现错误的解决办法

    在Linux下安装PHP过程中,编译时出现configure: error: libjpeg.(a|so) not found 错误的解决办法 configure: error: libjpeg.(a ...

  6. Oracle安装过程物理内存检查及临时temp空间不足解决办法

    物理内存 – 此先决条件将测试系统物理内存总量是否至少为 922MB (944128.0KB). 预期值 : N/A 实际值 : N/A 错误列表: – 可用物理内存 PRVF-7531 : 无法在节 ...

  7. 经测试稳定可用的蓝牙链接通信Demo,记录过程中遇到的问题的思考和解决办法,并整理后给出一个Utils类可以简单调用来实现蓝牙功能

    说明:这是本人在蓝牙开发过程中遇到过的问题记录和分析,以及解决办法. 在研究过程中,许多的前人给出的解决方案和思路指导对我相当有帮助,但并非都是可采取的解决方法, 经过本人对这些方法的测试和使用过后, ...

  8. ubuntu执行sudo apt-get update 时出现的错误及解决办法

    一.错误描述 W: GPG error: http://ppa.launchpad.net/fkrull/deadsnakes/ubuntu xenial InRelease: The followi ...

  9. 【转】bootstrap模态框(modal)使用remote方法加载数据,只能加载一次的解决办法

    http://blog.csdn.net/coolcaosj/article/details/38369787 bootstrap的modal中,有一个remote选项,可以动态加载页面到modal- ...

随机推荐

  1. JAVA的初始化顺序:

    JAVA的初始化顺序: 父类的静态成员初始化>父类的静态代码块>子类的静态成员初始化>子类的静态代码块>父类的代码块>父类的构造方法>子类的代码块>子类的构造 ...

  2. LG5056 【模板】插头dp

    题意 题目背景 ural 1519 陈丹琦<基于连通性状态压缩的动态规划问题>中的例题 题目描述 给出n*m的方格,有些格子不能铺线,其它格子必须铺,形成一个闭合回路.问有多少种铺法? 输 ...

  3. java的数组和arraylist

    1.数组 1.0   一开始就错了 int a[8];   //没有像C在内存中开辟了8个区域 改: int a[] = {1,2,3} ; System.out.println(a.length); ...

  4. GitLab Shell如何通过SSH工作

    转自:https://wayjam.me/post/how-gitlab-shell-works-with-ssh.md GitLab访问Git仓库 首先回顾GitLab的Git仓库四种访问方式: g ...

  5. madlib centos yum 包安装

    使用centos 测试安装madlib sql 机器学习类库 安装步骤 添加pg 10 repo yum install https://download.postgresql.org/pub/rep ...

  6. 关于宽带接两台路由,并且第二台需要关闭DHCP的设置

    关于宽带接两台路由,并且第二台需要关闭DHCP的设置 https://wenku.baidu.com/view/e317a12d4b35eefdc8d333cb?pcf=2#1

  7. Python_getter和setter方法

    当给属性赋值的时候,使用实例.属性=属性值的方式显然把属性暴露出来了,并且也无法对属性值进行限制检查,java中提供了setter和getter方法,那么python是如何做的呢?更多内容请参考:Py ...

  8. Jenkins发布后自动通知【钉钉】

    阅读目录 一.前言 二.使用钉钉推送的优势 三.配置 一.前言 最近使用Jenkins进行自动化部署,但是发布署后,并没有相应的通知,虽然有邮件发送通知,但是发现邮件会受限于大家接受的设置,导致不能及 ...

  9. node api 之:Buffer

    在 ECMAScript 2015 引入 TypedArray 之前,JavaScript 语言没有读取或操作二进制数据流的机制. Buffer 类被引入作为 Node.js API 的一部分,使其可 ...

  10. ASP.NET AJAX入门系列(10):Timer控件简单使用

    本文主要通过一个简单示例,让Web页面在一定的时间间隔内局部刷新,来学习一下ASP.NET AJAX中的服务端Timer控件的简单使用. 主要内容 Timer控件的简单使用 1.添加新页面并切换到设计 ...