点击按钮后到底发生了什么,Touch,LongClick或者Click?
按钮点击事件详解
最近一个项目需要给应用初始界面上的动态按钮添加在不同状态的变换效果,如点击(俗一点也可称为按压)后实现背景图的更换或者图标的缩放等效果。由于按钮点击的时间有长有短,所以采用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?的更多相关文章
- TProcedure,TMethod,TNotifyEvent,TWndMethod的区别,并模拟点击按钮后发生的动作
忽然发现TProcedure和TNotifEvent的区别还挺大的: procedure TForm1.Button2Click(Sender: TObject); begin ShowMessage ...
- response 后刷新页面,点击按钮后,禁用该按钮
一,正常的点击按钮后,将其灰显,全部执行完毕再正常显示. this.btnSave.Attributes.Add("onclick", "if (typeof(Page_ ...
- Java基础 awt Button 点击按钮后在控制台输出文字
JDK :OpenJDK-11 OS :CentOS 7.6.1810 IDE :Eclipse 2019‑03 typesetting :Markdown code ...
- 如何让Web程序在点击按钮后出现如执行批处理程序般的效果
在cli程序中,输入命令得到连续的输出已经是一种进度而美观的页面交互形式,好比下图: 而web程序里也有类似的场景,比如执行一个耗时任务,除了显示出等待图标外,用户还希望把执行的状态及时显示出来.如下 ...
- 如何点击按钮后在加载外部的Js文件
或许有朋友遇到过,想等自己点击按钮之后才执行某一个js文件,那么,你运气好,看到了我的代码了哈哈, <html> <head> <title></title& ...
- 移动端开发H5页面点击按钮后出现闪烁或黑色背景的解决办法
H5页面在IOS端测试的时候发现,点击按钮会闪动,出现一个黑色的背景一闪而过,影响用户体验.最后通过度娘,找到解决方法: 就是给点击的元素添加一个CSS属性或者全局添加一个css. -webkit-t ...
- unity编辑器扩展_07(创建对话框,检测按钮的点击,点击按钮后提示信息,保存设置的数据,显示点击按钮后的处理的进度条信息)
代码: using UnityEditor;using UnityEngine; public class ChangeValue : ScriptableWizard { ...
- vue+element 点击按钮后 导致 刷新页面 致url中拼接 ? 或者拼接参数
https://blog.csdn.net/sinat_37255207/article/details/88917162 element 自己的<el-form></el-form ...
- 利用JS实现点击按钮后图片自动切换
我么常常看到一个网站的主界面的图片可以切换自如,那么又是如何实现的呢? 1.HTML页面布局如图所示: Main(div) top(div)(显示需要显示的图片) bottom UL (li)< ...
随机推荐
- 如何面试前端工程师:GitHub 很重要
编者注:下面这篇文章从面试官的角度介绍到面试时可能会问到的一些问题. 我在Twitter和Stripe的一部分工作内容是面试前端工程师.其实关于面试你可能很有自己的一套,这里我想跟你们分享一下我常用的 ...
- Docker 从零开始制作基础镜像[centos]
http://www.oschina.net/news/62897/docker-hub-contains-high-risk-vulnerabilities 这里有个统计,docker官方和个人发布 ...
- iOS之搜索框UISearchController的使用(iOS8.0以后替代UISearchBar+display)
在iOS 8.0以上版本中, 我们可以使用UISearchController来非常方便地在UITableView中添加搜索框. 而在之前版本中, 我们还是必须使用UISearchBar + UISe ...
- Thread多线程(一)
网上关于多线程的讲解有很多,意义也不用过多介绍,相信聪明的你早已知道,下面我们在剖析一下JAVA中的多线程的一些方法. 在JAVA中分别提供了两种方式实现多线程,分别继承Java.lang.Threa ...
- android 修改videoview的宽度和高度
如果直接用android的videoview.他是不允许你随意的修改宽度和高度的,所以我们要重写videoview! package com.hysmarthotel.view; import and ...
- iOS 疑难杂症 — — Swift debugger 无法在控制台 po 变量值的问题
前言 这个问题出现有好几个月了,一直没弄,以为是 Xcode 的问题后面版本升级应该就能好所以就不管了,今天心情好顺便查了一下. 声明 欢迎转载,但请保留文章原始出处:) 博客园:http://w ...
- XML 概述 (可扩展标记语言)
XML:eXtensible Markup Language 可扩展标记语言 概念:可扩展:xml中所有的标签都是自定义的.没有预定义的. 功能: 存储数据 ...
- PHP(第一天)
<?php // $name='lisi'; // $age =18; //$bol =true; //$bol =false; // echo ($bol); //echo ('name is ...
- iOS时间个性化设置设置
现在在很多项目中,不会直接显示时间,很多时候都是显示“刚刚”,”XX分钟前”,等等字样,那么他们是怎么实现的呢 ? .新建一个NSDate的类目:NSDate+XMGExtension NSDate+ ...
- [C#6] 5-自动属性增强
0. 目录 C#6 新增特性目录 1. 老版本代码 internal class Person { public string Name { get; private set; } public in ...