按钮点击事件详解

  最近一个项目需要给应用初始界面上的动态按钮添加在不同状态的变换效果,如点击(俗一点也可称为按压)后实现背景图的更换或者图标的缩放等效果。由于按钮点击的时间有长有短,所以采用OnTouchListener监听器对点击事件进行监听,并利用对应的onTouch(View v, MotionEvent event)方法来实现按钮图标的变换效果(背景图更换或者图标缩放)。但是项目中除了利用Touch事件来处理按钮基本的变换外,还需要响应LongClick或者Click事件来为用户做进一步的响应,即Touch和Click事件分别完成不同的任务。

  那么问题来了,表面上看Touch、LongClick及Click这三个事件的关系很普通(均可由用户点击组件触发),在一般的应用中对它们中的个别进行监听也不太会遇到什么奇怪的现象。但是深入研究与测试之后,会发现当它们一起用的时候,有太多地方需要注意,否则很容易出错。下面就来看看有哪些平时不太注意却可能出现意外的地方。

  读书期间一直用VC++,现在转为Android,个人觉得在对于点击事件的监听即响应的实现这个点上很多语言间非常相似,即解决问题的思路是相通的。

  本文的测试案例是用Android进行实现,先看下面两段代码。

1、xml布局文件

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:context="com.example.eventtest.MainActivity" > <ImageView android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" /> </LinearLayout>

  由于只是观察Touch、LongClick及Click三者之间对点击事件的响应关系,所以整个界面布局中仅仅放置了一个ImageView组件。

2、Java代码实现

 package com.example.eventtest;

 import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView; public class MainActivity extends ActionBarActivity { private ImageView imageView = null;
private String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); imageView = (ImageView)findViewById(R.id.imageView);
imageView.setOnTouchListener(mOnTouchListener);
imageView.setOnLongClickListener(mOnLongClickListener);
imageView.setOnClickListener(mOnClickListener);
} View.OnTouchListener mOnTouchListener = new View.OnTouchListener() { @Override
public boolean onTouch(View arg0, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction() == MotionEvent.ACTION_DOWN){
Log.d(TAG, "touch down");
return false; //1 FALSE
}
else if(event.getAction() == MotionEvent.touch move){
Log.d(TAG, "touch move");
return false; //2 FALSE
}
else if(event.getAction() == MotionEvent.ACTION_UP){
Log.d(TAG, "touch up");
return false; //3 FALSE
}
return false;
}
}; View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() { @Override
public boolean onLongClick(View arg0) {
// TODO Auto-generated method stub
Log.d(TAG, "long click");
return false; //4 FALSE
}
}; View.OnClickListener mOnClickListener = new View.OnClickListener() { @Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
Log.d(TAG, "click"); //5 NULL
}
}; @Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
} @Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}

  从上面给出的Java代码可以看出:

  1、分别为点击ImageView组件后的Touch、LongClick及Click事件设置了OnTouchListener、OnLongClickListener及OnClickListener监听器,并且在对应的响应方法onTouch()、onLongClick()及onClick()中进行了日志输出函数的调用与返回值的设定。其中返回值的设定是本文的关键,某个响应方法返回的是true还是false,对于后续的响应方法的影响非常大,后面会慢慢地进行分析。

  2、为了观察组件点击后的一系列响应情况,利用Log类的d(Tag, msg)方法来输出运行日志,由于系统本身在应用运行时会输出很多我们不必要查看的信息,所以最好在logcat中设置一个日志信息输出过滤器,名称要和程序中的TAG字串相同。如此处的TAG为主类名字串“MainActicity”,那么Filter的名称也要取为“MainActicity”,设置完之后保存,然后在logcat中选择debug选项(上述的d()方法对应debug,e()对应error(),还有几个类别感兴趣的朋友可以自己研究),那窗口中就会只输出我们设定的打印信息了。如下图中的输出结果,看着简单、舒服。

  3、说明,java代码行35、39、43、56及65的注释是方法返回值的说明,由于onClick()方法无返回值,所以用NULL表示。在测试中会对各种方法的返回值进行改变,形成不同的组合,然后通过输出的日志信息观察它们的响应情况。由于Android中对点击事件的响应顺序为touch down-touch move-long click-touch up-click(当然,不是每次点击均会产生所有的事件,这只是完整的流程描述),所以描述返回值组合时也是按照这个顺序。如上述代码中方法的返回值组合为false-false-false-false-null,而且对组件的短按、长按、移开(短按+移开或长按+移开,算两种不同的状态)(移开表示最终没有触发Click事件,但有可能触发LongClick事件)这四种点击状态都进行了测试,对于是否执行点击状态用yes/no表示,那么上面代码运行后组件的长按+移开的完整组合(返回值和操作标记)就是false-false-false-false-null-no-yes-yes。

3、常规测试结果

  说了这么多,所谓有图有真相,不给出结果怎么说得清呢。如果表述有不恰当或者内容有缺陷的地方还希望朋友您能够指出,谢谢!

  按照上面的返回值组合(各方法均返回false),四种点击状态的打印日志信息如下:

  1、短按(组合:false-false-false-false-null-yes-no-no)

  短按其实专业点说是手指(或者触笔,有些手机和平板会配备)在应用界面的组件上轻触,结果为touch down-touch up-click,有时候输出结果会是touch down-touch move-touch up-click(产生touch move事件但还至于到long click事件)。

  2、长按(组合:false-false-false-false-null-no-yes-no)

  这里就产生了long click事件,输出为结果为touch down-touch move-long click-touch move-touch up-click,其实touch move事件的产生与否、个数和手指按压与抬起的速度有关,不必深究(后面还会提到不必深究的原因)。

  3、短按移开(组合:false-false-false-false-null-yes-no-yes)

  由于短按不会产生long click事件,而按压组件并最终移开后不会产生click事件,所以结果为touch down-touch move-touch up。

  4、长按移开(组合:false-false-false-false-null-no-yes-yes)

  到这里,就不要多解释了,日志输出结果为touch down-touch move-long click-touch move-touch up。

4、测试结果分析

  上面的测试结果相信大部分人都预想到了,是不是觉得挺简单的呢?

  但是得注意一个细节:上述测试结果是在所有事件响应方法的返回值均为false(onClick()除外,为void,后面会用null表示)的情况下得到的。这意味着什么,如果各方法的返回值不全是false又会怎么样呢?下面先从全局层面来简单解释一下Android对于点击事件的响应流程吧。

  之前提到过,点击事件的一般响应流程为touch down-touch move-long click-touch move-touch up-click。这是整体响应情况的描述,前提就是像上面程序中设置的那样——返回值均为false。从顺序的角度出发,如果touch down事件对应的模块在执行完自身的实现后返回false,即它还想把当前的点击状态继续向上(上层类,一般为父类)传递,而不是扼杀在自己的摇篮里;按住一段时间后,touch move事件触发;随后long click事件产生,返回false,继续交给上层处理;这中间还会有touch move事件产生;当手指抬起时(不是移开),touch up事件触发,返回false;最终到了click事件,本次点击事件结束。

  还有,组件按压并移开,是不会触发click事件的,如果按压时间够久,会有long ckick事件发生。

  好了,简单的测试与描述就到这里。下面该挑战复杂的返回值组合了,希望大家还保持着清醒的头脑,之后的描述不会那么啰嗦,组合值及输出日志将以表格形式进行简洁明了的展现。代码中进行的相应改变也不再给出,感兴趣的朋友自己实现一下吧。

  1、

touch down touch move touch up long click click 轻触 长按 按住移出 响应结果
FALSE BOTH FALSE FALSE NULL YES NO NO down-move-up-click
FALSE BOTH FALSE FALSE NULL NO YES NO down-move...-long click-move..-up-click
FALSE BOTH FALSE FALSE NULL YES NO YES down-move...-up
FALSE BOTH FALSE FALSE NULL NO YES YES down-move...-long click-move...-up

  上面描述过的测试状态的返回值组合和输出日志信息也重新以表格形式给出,touch down简化为down,其他类似,move...表示多个touch move事件的输出信息。

  2、

touch down touch move touch up long click click 轻触 长按 按住移出 响应结果
FALSE BOTH FALSE TRUE NULL YES NO NO down-move-up-click
FALSE BOTH FALSE TRUE NULL NO YES NO down-move...-long click-move..-up
FALSE BOTH FALSE TRUE NULL YES NO YES down-move...-up
FALSE BOTH FALSE TRUE NULL NO YES YES down-move...-long click-move...-up

  发现不同之处了吗?将onLongClick()方法返回值设置为true,相应地,组件长按并抬起后,click事件并没有触发。即点击事件到touch up事件后就不在往上传递触发消息了。到此,可以解释一下touch move事件了,可以看到将其对应的响应模块的返回值设置为true或者false,对执行结果不会产生任何影响,它只需要完成自己的任务就好,其他的模块和它并没有多大的关系。注意,表格中标红的部分是以第1种测试状态的组合和输出做为基础的,下同。

  3、

touch down touch move touch up long click click 轻触 长按 按住移出 响应结果
TRUE BOTH FALSE FALSE NULL YES NO NO down-move-up
TRUE BOTH FALSE FALSE NULL NO YES NO down-move...-up
TRUE BOTH FALSE FALSE NULL YES NO YES down-move...-up
TRUE BOTH FALSE FALSE NULL NO YES YES down-move...-up

  将touch down事件的返回值设置为true,long click和click事件居然都没有触发,结合点击事件的响应流程就容易理解了。即touch down返回true之后,表示不再需要上层类的协助(这里指long click和click事件)。没有特殊的情况下,touch down-move-up是组件touch事件的一个完整流程。当然,后面会出现特殊的情况。

  4、

touch down touch move touch up long click click 轻触 长按 按住移出 响应结果
FALSE BOTH TRUE BOTH NULL YES NO NO down-move-up-long click
FALSE BOTH TRUE BOTH NULL NO YES NO down-move...-long lick-move...-up
FALSE BOTH TRUE BOTH NULL YES NO YES down-move...-up
FALSE BOTH TRUE BOTH NULL NO YES YES down-move...-long lick-move...-up

  将touch up事件的返回值设置为true,组件轻触并抬起时,出现了奇怪的现象:触发了long click事件,并且long click事件处理完之后,不管返回值是false还是true,均没有再出现touch move和touch up事件。

  其实,结合点击事件的流程,在touch up事件返回true之后,click事件不会触发很容易理解,但为什么还会产生long click呢?这是目前没有搞懂的一个疑问!!!

  5、

touch down touch move touch up long click click 轻触 长按 按住移出 响应结果
FALSE BOTH BOTH YES NO NO down
FALSE BOTH BOTH NO YES NO down
FALSE BOTH BOTH YES NO YES down
FALSE BOTH BOTH NO YES YES down

  这里主要是对组件ImageView监听器的设置进行了改变,“无”表示没有设置对应的监听器,即将java代码的24、25行注释掉。从响应结果可以看出,在touch down事件返回false时,会希望上层类的long click或者click事件来处理,而此时组件只设置了OnTouchListener监听器,所以会一直等待,永远不会触发touch move和touch up事件了。

  6、

touch down touch move touch up long click click 轻触 长按 按住移出 响应结果
TRUE BOTH BOTH YES NO NO down-move-up
TRUE BOTH BOTH NO YES NO down-move...-up
TRUE BOTH BOTH YES NO YES down-move...-up
TRUE BOTH BOTH NO YES YES down-move...-up

  有了以上的基础,这种情况就很好理解了其实和第3种测试状态是类似的(甚至相同)。在touch down事件返回true后,不管有没有设置long click和click事件的监听器,都不会再触发了,而touch move-up事件正常了。

5、总结

  本篇文章通过对Touch、Long Click及Click事件的响应方法设置各种不同返回值组合,测试组件点击状态的响应情况。对于带问好的标题,可以确定的是:点击组件后产生什么事件,做怎样的实现,是由编程者进行控制。

  遗留问题,即上述测试第4种情况:轻触组件,在touch up事件返回true之后,为什么会触发long click?

  更新,针对这个问题查了一些资料,自己也思考了很久,目前个人的理解是:

    当touch down返回false时,是希望有上一层的click或者long click监听器来处理当前传递的点击事件;

    由于是轻触,在touch move到touch up过程中不会触发long click事件,理论上up后会由click监听器处理;

    但是偏偏touch up返回的是true,即click监听器不会捕捉该事件,继续传递;

    那这时候,能够处理该事件的只有路过的long click监听器了;

点击按钮后到底发生了什么,Touch,LongClick或者Click?的更多相关文章

  1. TProcedure,TMethod,TNotifyEvent,TWndMethod的区别,并模拟点击按钮后发生的动作

    忽然发现TProcedure和TNotifEvent的区别还挺大的: procedure TForm1.Button2Click(Sender: TObject); begin ShowMessage ...

  2. response 后刷新页面,点击按钮后,禁用该按钮

    一,正常的点击按钮后,将其灰显,全部执行完毕再正常显示. this.btnSave.Attributes.Add("onclick", "if (typeof(Page_ ...

  3. Java基础 awt Button 点击按钮后在控制台输出文字

        JDK :OpenJDK-11      OS :CentOS 7.6.1810      IDE :Eclipse 2019‑03 typesetting :Markdown   code ...

  4. 如何让Web程序在点击按钮后出现如执行批处理程序般的效果

    在cli程序中,输入命令得到连续的输出已经是一种进度而美观的页面交互形式,好比下图: 而web程序里也有类似的场景,比如执行一个耗时任务,除了显示出等待图标外,用户还希望把执行的状态及时显示出来.如下 ...

  5. 如何点击按钮后在加载外部的Js文件

    或许有朋友遇到过,想等自己点击按钮之后才执行某一个js文件,那么,你运气好,看到了我的代码了哈哈, <html> <head> <title></title& ...

  6. 移动端开发H5页面点击按钮后出现闪烁或黑色背景的解决办法

    H5页面在IOS端测试的时候发现,点击按钮会闪动,出现一个黑色的背景一闪而过,影响用户体验.最后通过度娘,找到解决方法: 就是给点击的元素添加一个CSS属性或者全局添加一个css. -webkit-t ...

  7. unity编辑器扩展_07(创建对话框,检测按钮的点击,点击按钮后提示信息,保存设置的数据,显示点击按钮后的处理的进度条信息)

    代码: using UnityEditor;using UnityEngine; public class ChangeValue : ScriptableWizard {               ...

  8. vue+element 点击按钮后 导致 刷新页面 致url中拼接 ? 或者拼接参数

    https://blog.csdn.net/sinat_37255207/article/details/88917162 element 自己的<el-form></el-form ...

  9. 利用JS实现点击按钮后图片自动切换

    我么常常看到一个网站的主界面的图片可以切换自如,那么又是如何实现的呢? 1.HTML页面布局如图所示: Main(div) top(div)(显示需要显示的图片) bottom UL (li)< ...

随机推荐

  1. angular源码分析:$compile服务——指令的编写

    这一期中,我不会分析源码,只是翻译一下"https://docs.angularjs.org/api/ng/service/$compile",当然不是逐字逐句翻译,讲解指令应该如 ...

  2. 【原】iOS动态性(五)一种可复用且解耦的用户统计实现(运行时Runtime)

    声明:本文是本人 编程小翁 原创,转载请注明. 为了达到更好的阅读效果,强烈建议跳转到这里查看文章. iOS动态性是我的关于iOS运行时的系列文章,由浅入深,从理论到实践.本文是第5篇.有兴趣可以看看 ...

  3. APP上架证书无效:解决

    转发:http://www.cnblogs.com/pruple/p/5523767.html 转发:http://blog.csdn.net/sunnyboy9/article/details/50 ...

  4. Linux命令学习总结:last

    命令简介:     该命令用来列出目前与过去登录系统的用户相关信息.指令英文原义:show listing of last logged in users 执行权限 :有些需要特殊权限 指令所在路径: ...

  5. WinForm常用属性

    Text: 字符串,窗体标题 MaximizeBox: 布尔, 窗体能否最大化 MinimizeBox: 布尔,窗体能否最小化 ShowIcon: 布尔,左上角图标 ShowInTaskbar: 布尔 ...

  6. Hadoop2.5.0 搭建实录

    目录: 第一步:准备相关材料 第二步:虚拟机环境搭建 第三步:用户信息 第四步 安装.配置Java环境 第五步 Zookeeper安装配置 第六步 Hadoop安装.配置 第七步:HBase安装部署 ...

  7. ERROR! MySQL is running but PID file could not be found

    /etc/init.d/mysql status提示ERROR! MySQL is running but PID file could not be found先打印MYSQL进程ps aux | ...

  8. OpenStack 行业正进入拓展期:行业云将成为新一轮工业革命的基础设施和引擎

    一直在关注华为2016 Connect 大会,本来没票,后来找朋友搞到了一张,参加了大会第一天下午的会议,时间虽短,但非常有收获.本来出发前还带了纸和笔,但是到了现场才发现只带了笔记本,笔却丢下了,所 ...

  9. 在Azure上实现Linux Server故障转移

    要充分利用公有云的弹性扩展和高可用, 首先要在应用系统层面支持横向扩展(scale out),这个说起来很容易,或者说对新开发的应用系统而言已经成为标配.但是对已有的.老旧的应用系统来说,这就比较困难 ...

  10. JS -- 异步加载进度条

    今天在博客园问答里面看到博友问道怎么实现Ajax异步加载产生进度条. 很好奇就自己写了一个. 展现效果: 1) 当点击Load的时候,模拟执行异步加载. 浏览器被遮挡. 进度条出现. 实现思路: 1. ...