Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法
$*********************************************************************************************$
博主推荐:
风萧兮兮易水寒,“天真”一去兮不复还。如何找到天真的那份快乐。小编倾力推荐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方法,代码如下:
package com.bbdtek.demo; import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout; public class MyLayout extends LinearLayout { @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("Demo:","父View的dispatchTouchEvent方法执行了");
return super.dispatchTouchEvent(ev);
} @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("Demo:","父View的onInterceptTouchEvent方法执行了");
return super.onInterceptTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("Demo:","父View的onTouchEvent方法执行了");
return super.onTouchEvent(event);
} public MyLayout(Context context) {
super(context);
} public MyLayout(Context context,AttributeSet attr) {
super(context,attr);
} }
在这三个方法中打印一段信息,有助于查看这三个方法的执行流程
下面是自定义一个MyTextView继承TextView,重写dispatchTouchEvent,onTouchEvent方法(这里没有onInterceptTouchEvent方法,原因很简单,就是TextView是一个View没有子View了,不需要拦截信息了)代码如下:
package com.bbdtek.demo; import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.widget.TextView; public class MyTextView extends TextView{ @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("Demo:","子View的dispatchTouchEvent方法执行了");
return super.dispatchTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("Demo:","子View的onTouchEvent方法执行了");
return super.onTouchEvent(event);
} public MyTextView(Context context) {
super(context);
} public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
} public MyTextView(Context context, AttributeSet attrs,int defStyle) {
super(context, attrs, defStyle);
} }
在方法中输出log信息,便于查看方法的执行流程
定义的一个布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.bbdtek.demo.MyLayout
android:id="@+id/layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<com.bbdtek.demo.MyButton
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点我"/>
<com.bbdtek.demo.MyTextView
android:id="@+id/txt1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="我是个好人我是个好人呢我是啊速度将法律上的经费福建省的雷锋精神多了爽肤水两地分居SD卡飓风桑迪快乐分类数据的法律进多少"
android:textSize="25dp"/>
</com.bbdtek.demo.MyLayout>
</LinearLayout>
MyLayout中包含一个MyTextView控件:
下面来看一下测试代码:
package com.bbdtek.demo; import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener; public class AndroidDemoActivity extends Activity{ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
MyTextView txt1 = (MyTextView)findViewById(R.id.txt1);
//MyLayout layout = (MyLayout)findViewById(R.id.layout);
txt1.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Log.e("Demo:","点击了txt1");
}});
//给MyTextView定义一个onTouchListener监听器
txt1.setOnTouchListener(new OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
Log.e("Demo:","txt1按下");
}else if(event.getAction() == MotionEvent.ACTION_UP){
Log.e("Demo:","txt1弹起");
}
return false;
}});
//给MyLayout定义一个onTouchListener监听器
/*layout.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Log.e("Demo:","点击了父layout");
}});*/ }
}
看一下运行结果:
可以看到先执行的是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方法的执行流程:
首先来看一下测试代码:
package com.bbdtek.demo; import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener; public class AndroidDemoActivity extends Activity{ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
MyTextView txt1 = (MyTextView)findViewById(R.id.txt1);
MyLayout layout = (MyLayout)findViewById(R.id.layout);
txt1.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Log.e("Demo:","点击了txt1");
}});
//给MyTextView定义一个onTouchListener监听器
txt1.setOnTouchListener(new OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
Log.e("Demo:","txt1按下");
}else if(event.getAction() == MotionEvent.ACTION_UP){
Log.e("Demo:","txt1弹起");
}
return false;
}});
//给MyTextView定义一个onTouchListener监听器
layout.setOnTouchListener(new OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
Log.e("Demo:","父layout按下");
}else if(event.getAction() == MotionEvent.ACTION_UP){
Log.e("Demo:","父layout弹起");
}
return false;
}});
//给MyLayout定义一个onTouchListener监听器
layout.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Log.e("Demo:","点击了父layout");
}}); }
}
点击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方法中
为什么要这么做,有待研究呀!今天就写到这里了,头都写大了,很纠结,也很开心呀,如果发现有什么不正确的地方,希望给予批评和指正,本人将不胜感激!
Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法的更多相关文章
- 事件之onTouch方法的执行过程 及和 onClick执行发生冲突的解决办法
转载:http://blog.csdn.net/jiangwei0910410003/article/details/17504315#quote 博主推荐: 风萧兮兮易水寒,“天真”一去兮不复还.如 ...
- 【转】android中重复连接ble设备导致的连接后直接返回STATE_DISCONNECTED的解决办法---不错不错,重新连接需要花费很长的时间
原文网址:http://bbs.eeworld.com.cn/thread-438571-1-1.html /* * 通过使用if(gatt==null ...
- php中session同ip不同端口的多个网站session冲突的解决办法
在局域网内使用IP加端口的访问方式搭了两个相同程序的站,结果发现用户在一个站下登录后,在另一个站也同时登录了,在一个退出后,另一个站也同时退出了.看了下程序发现两个站都是使用纯session方式记录登 ...
- 源代码解析Android中View的layout布局过程
Android中的Veiw从内存中到呈如今UI界面上须要依次经历三个阶段:量算 -> 布局 -> 画图,关于View的量算.布局.画图的整体机制可參见博文 < Android中Vie ...
- php脚本的执行过程(编译与执行相分离)
php脚本的执行过程(编译与执行相分离) 深入理解PHP代码的执行的过程 PHP程序的执行流程 Apache + PHP 的并发访问
- jQuery的AJAX方法简介及与其他文件$符号冲突的解决办法
一.重要的jQuery AJAX方法简介 $.load(url) 从服务器载入数据 $.get(url,callback) 从服务器请求数据,并执行回调函数 $.post(url,data,callb ...
- android中控件公用产生的冲突的解决办法
1.ViewPager嵌套HorizontalScrollView滑动冲突的解决办法,重写ViewPager public class ZdyViewPage extends ViewPager { ...
- Android HttpURLConnection.connect找不到源 HttpURLConnection连接失败 HttpURLConnection.connect IO异常 解决办法
Android HttpURLConnection.connect找不到源 HttpURLConnection连接失败 HttpURLConnection.connect IO异常 解决办法 以下代 ...
- IIS7.5中调试.Net 4.0网站出现无厘头500错误的解决办法 (转)
刚刚 部署了ii7的dll的有x86写的,就会出现以下这样的问题 iis 7 x86,Could not load file or assembly 'Name' or one of its depe ...
随机推荐
- Dubbox服务的消费方开发
开发步骤: (1)创建Maven工程(WAR)dubboxdemo-web ,在pom.xml引入依赖 ,同“dubboxdemo-service”工程.区别就是把tomcat插件的运行端口改为808 ...
- mongoose 常用数据库操作 删除
删除 Model.remove(conditions, [callback]) try.js var User = require("./user.js"); function d ...
- mongoose 常用数据库操作 插入
项目 db.js var mongoose = require('mongoose'); mongoose.connect('mongodb://127.0.0.1:27017/whhhh', { u ...
- 弹窗的图片画廊lightslider插件
需要这三个文件
- 在IntelliJ IDEA中新建Maven项目
在IntelliJ IDEA中新建Maven项目,选择“File->New->Project”,创建一个简单项目,不选择模板,如下图所示: 2 选择“Maven”,不需要使用内置结构(模板 ...
- NOIp2018集训test-10-23
上午考了一套sb题,但是没有人AK.李巨290虐场. 下午又考了一套sb题,李巨AK虐场.%%% T1 % 中国剩余定理好像做不了啊,我一直在想如何用CRT做,然后就GG了. 然而正解是bike当初说 ...
- Less 和 Sass
LESS SASS sass 与 less 的区别与学习 : https://www.cnblogs.com/roashley/p/7731865.html 或 https://www.c ...
- 牛客Another Distinct Values
题目 我一开始试了一下n=3,发现不行,于是就猜测只有2可以,交了一发,通过了50%.所以我猜测只有偶数可以,奇数都不行. 中间空白部分放n=2的情况就行.因为n=2的情况时,最大只有2,所以我们可以 ...
- Golang 标准库提供的Log(一)
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://gotaly.blog.51cto.com/8861157/1405754 G ...
- CSS:教程
ylbtech-CSS:教程 1.返回顶部 1. CSS 教程 通过使用 CSS 我们可以大大提升网页开发的工作效率! 在我们的 CSS 教程中,您会学到如何使用 CSS 同时控制多重网页的样式和布局 ...