Android onTouchEvent, onClick及onLongClick的调用机制

在android系统中,我们有2种方法进入界面查看内容,一种是通过按键切换焦点,然后按"确认"键进入,还有一种就是通过触摸或者鼠标点击,这种点击是没有焦点控制的,也不需要焦点控制,

比如现在的触摸屏手机,界面上是没有焦点的,我们点击就进入了,根本没有焦点的概念。

在模拟器上跑也是,比如我们创建含有多个button的一个Activity,某个button的处罚会进入另外一个Activity.

如果我们是通过按键进入的话,退出到第一个Activity时候是有焦点的,但是如果是通过鼠标就不会再有焦点。

同样我们测试ListView,ListActivity也是一样,鼠标点击是没有焦点,根本上说是没有这个概念,我们想进入哪个直接点哪个就OK了,也不需要焦点,但是按键必须有焦点控制我才知道是哪个。

我们可以通过源码分析得到解释:

case MotionEvent.ACTION_UP:下面有讨论

android系统默认就有2种不同的处理方式,当然我们可以根据需要进行相关修改,下面我们就来讨论。

 

一.基本概念:

基本的监听消息

OnClickListener
OnTouchModeChangeListener
OnTouchListener
OnFocusChangeListener

 

监听消息处理函数

onKeyDown 用于相应按键按下;
onKeyMultiple 用于响应按键重复点击;
onKeyUp 用于响应按键释放
onClick 来自View.OnClickListener 当点击这个Item(在触摸模式),或者当光标聚集在这个Item上时按下“确认”键,导航键,或者轨迹球。会被调用
onTouchModeChanged 用于响应触摸屏事件(包括鼠标点击操作)
onTouch 来自View.OnFocusChangeListener 在这个Item的范围内点触的时候,会被调用
onFocusChange 当光标移到或离开这个Item,会被调用

 

状态控制相关函数

setFocusable    --------- isClickable
setClickable    ---------  isFocusable(区别isFocused,一个是能否有焦点,一个是是不是焦点)
setFocusableInTouchMode ---- isFocusableInTouchMode(区别isInTouchMode)
requestFocusFromTouch ------ isInTouchMode

 

参考API解释:

public final boolean requestFocusFromTouch ()
Since: API Level 1 Call this to try to give focus to a specific view or to one of its descendants. This is a special variant of requestFocus() that will allow views that are not focuable in touch mode to request focus when they are touched.

Returns
Whether this view or one of its descendants actually took focus


public boolean isInTouchMode ()
Since: API Level 1 Returns whether the device is currently in touch mode. Touch mode is entered once the user begins interacting with the device by touch, and affects various things like whether focus is always visible to the user.

Returns
Whether the device is in touch mode.

 


public final boolean isFocusableInTouchMode ()
Since: API Level 1 When a view is focusable, it may not want to take focus when in touch mode. For example, a button would like focus when the user is navigating via a D-pad so that the user can click on it, but once the user starts touching the screen, the button shouldn't take focus

Related XML Attributes
android:focusableInTouchMode
Returns
Whether the view is focusable in touch mode.

 

通过isFocusable()这个方法我们可以知道view是否具有接受焦点的资格,
也可以通过setFocusable().来设置view接受焦点的资格,
对应在触摸模式下,你可以调用isFocusableInTouchMode().来获知是否有焦点来响应点触,注意是资格,
也可以通过setFocusableInTouchMode().来设置是否有焦点来响应点触的资格.
区别requestFocusFromTouch和isInTouchMode


系统框架控制焦点移动到另一个组件的算法是在某一方向上邻近的组件,在极个别情况下,默认的算法可能不符合开发者的预想要求,在这种情况下,你可以覆写下列XML属性的布局文件:

nextFocusDown , nextFocusLeft , nextFocusRight ,和nextFocusUp 设置他们的值来明确

 

通常如果你想宣布用户界面具有焦点的资格 (如果这个界面在传统上是没有的),可以在xml布局里去加上的android:focusable的属性,并设置它的值,

您也可以宣布在触摸模式下具有焦点的资格,同样也只在xml里添android:focusableInTouchMode.的属性,并设置它的值.

当用户请求在某个界面聚集焦点时,会调用requestFocus().这个方法监听到焦点活动(获得焦点或失去焦点都会被通知),会调用onFocusChange(),这个方法,这也是上节所讨论的Event Listeners

 

总结:对于UI控件事件的处理,需要设定相应的监听器,并实现相应的事件处理程序。

这儿有两种实现方法:一是定义一个OnClickListener 类的实例,并使用setOnClickListener等绑定监听器;

二是用Activity去实现OnClickListener接口,并作为它的一部分,这样会更方便,省去了加载额外的类和对象的时间。对于支持触摸屏的手机,可以设定触摸模式的UI,可以使用isInTouchMode()来获得触摸模式的状态。UI处理的另一个重点是焦点的设定及其切换。

焦点设置:通过setFocusable或者setFocusableInTouchMode设置可以接受焦点,通过isFocusable或isFocusableInTouchMode获取是否可以接受焦点;

焦点切换:编写XML布局文件的nextFocusDown 等属性设置

 

在Android中,onClick、onLongClick的触发是和ACTION_DOWN及ACTION_UP相关的,
在时序上,如果我们在一个View中同时覆写了onClick、onLongClick及onTouchEvent的话,
onTouchEvent是最先捕捉到ACTION_DOWN和ACTION_UP事件的,其次才可能触发onClick或者onLongClick。

 

在Android平台上,捕获用户在界面上的触发事件有很多种方法,View类就提供这些方法。你在使用各种View视图来布局界面时,会发现几个公用的回调方法来捕捉有用的UI触发事件,当事件在某个View对象上被触发时,这些方法会被系统框架通过这个对象所调用,
例如:当一个View(如一个Button)被点击(被鼠标或者触摸),onTouchEvent()方法会在该对象上被调用,所以为了捕获和处理事件,必须去继承某个类,并重载这些方法,以便自己定义具体的处理逻辑,显然,你更容易明白,为什么在你使用View类时会嵌套带有这些回调方法的接口类,这些接口称为event listeners,它是你去获取UI交互事件的工具在你继承View类,以便建立一个自定义组,也许你想继承Button 你会更普遍使用事件监听来捕捉用户的互动,在种情况下,你可以使用类的event handlers.来预定义事件的处理方法。

 

记住:我们所关注的事件肯定是发生在高亮聚集的焦点,它从总视图(顶级的)被一级一级的向下传递,直到我们想要关注的组件,当焦点聚集在这个视图(或视图中的子视图)时,你能够使用dispatchKeyEvent() 作为一种代替方法,来捕获在视图上的按键事件,你还可以使用onKeyDown()和onKeyUp().来捕获所有事件内的交互活动

如果设备有触摸功能,那么,当用户与界面的交互就不再需要有一个高亮在组件上,或一个焦点在view上,因此,模式的互动名为"触摸模式"。对于一个触摸设备,一旦有用户接触屏幕时,该设备将进入触摸模式.在点触某个View后,只有的它的方法isFocusableInTouchMode()返回为真时,才会有聚集焦点,如文本编辑工具。其他的界面只可以点触,但不会聚集焦点(高亮),如button被点触时就不会聚集焦

 

我们测试代码主要是以Button,ImageButton和EdieText控件作为测试:
各个控件默认的焦点相关属性参考下图:

 

 

当然我们可以在相关控件定义的XML文件中去定义相关属性:
比如我们可以定义Button的XML文件属性 android:focusableInTouchMode="true"设置有焦点来响应点触的资格,和我们在代码中手动调用setFocusableInTouchMode是一样的效果.

 

设定焦点:

我们在一个ViewGroup(比如一个Layout)中手动设定一指定控件为focus焦点,比如如下:
bt2是以普通控件句柄
bt2.setFocusable(true);
bt2.setFocusableInTouchMode(true);
bt2.requestFocus();
实际上我们Button控件默认的isFocusable就是true,所以实际上对于设定一个Button控件为焦点,我们只需要
bt2.setFocusableInTouchMode(true);//这步必须要(不过后来我验证了,这步不要好像也可以设置控件焦点)
bt2.requestFocus();
当然对于EditView来说,默认的isFocusable是true,isFocusableInTouchMode也是true,所以我们指定EditView为焦点就更简单了:
bt2.requestFocus();

 

分析View源码中的onTouchEvent源码处理:(\android_src\frameworks\base\core\java\android\view):
还可以参考下面的一篇博文:
 case MotionEvent.ACTION_UP:
 boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();//设置焦点
                        }
 if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }

知道如果isFocusableInTouchMode是false,就是没有设置获取触摸焦点属性时候,我们的View会主动调用performClick触发onClick()回调,但是如果isFocusableInTouchMode是true,就不会主动触发onClick,而是像按键处理一样去设置焦点。


这就是下面我们测试的其中一种情况,也是DV6300-T主界面出现的问题,后来我们修改了。

void onFocusChange(View v, boolean hasFocus)回调处理:
实际上通过打印图片我们就知道了,在按下按键或者触摸点击时候切换焦点,
一般是会调用onFocusChange回调2次,一次是取消原来的焦点View,这个时候hasFocus是false,并且调用getCurrentFocus返回的null然后就会再调用一次,设置当前焦点View,这时候hasFocus是true,调用getCurrentFocus返回的就是传入的参数v,也就是当前焦点控件

 

当我们的控件(比如Button)设置了相关属性:
setFocusable(true)
setFocusableInTouchMode(true)
那么当我们按键处理的时候肯定是正常的,但是当我们点击该控件时候,肯定会调用onFocusChange回调,前面我们分析了是不会触发onClick回调的
也就是说,这种情况下我们只是把焦点移到该控件,需要我们再在该控件点击一下,才会触发onClick事件.
所以6300-T的主界面我们做了一个处理,达到的效果就是我们用鼠标点击控件时候,不仅焦点移动到该控件,还会触发onClick事件:

认没有设置setFocusableInTouchMode时候是直接会触发onClick事件,而且是没有焦点的概念。

setFocusableInTouchMode(true)后会设置焦点,但是不会触发onClick事件。
我们的做法就是在onFocusChange实现我们自己的处理,如下:
public void onFocusChange(View v, boolean hasFocus) {
   if (hasFocus) {  
    if (v.isInTouchMode())//判断是否是触摸,鼠标点击来切换焦点
    {  
     ((Button)v).performClick();  
    }  
   }
 }
}
调用performClick方法主动触发控件的onClick事件(和View源码onTouchEvent的MotionEvent.ACTION_UP一样处理)。实际上我们也可以利用performClick方法来模拟任何控件的鼠标点击处理等(一般不需要我们这样做而已)
可以参考下面博文:

 

在实现上面效果时候,如果用v.isFocusableInTouchMode替代v.isInTouchMode会有问题,会影响按键处理,比如按上下按键只是切换焦点,但是
却任然会触发onClick事件.
原因就是如果控件设置了setFocusableInTouchMode为true,那么isFocusableInTouchMode永远是true,除非是重新设置了setFocusableInTouchMode
这就是isFocusableInTouchMode和isInTouchMode的区别
setFocusableInTouchMode ---- isFocusableInTouchMode(Whether the view is focusable in touch mode)
requestFocusFromTouch ------ isInTouchMode   (判断Whether the device is in touch mode)
对应关系:
isFocusableInTouchMode只是判断是否有焦点来响应点触的资格
isInTouchMode就是判断是否在TouchMode为true下点击了该控件(这是我自己的理解,可能有误,最好还是参考API的英文解释)

 

参考源码就在:

E:\JAVA\Android\DV6300-T测试代码\Touch_Focus_Test

 

时序调用图如下:

基本上下按键切换焦点onFocusChange



在onFocusChange中利用isFocusedInTouchMode作为是否调用performClick接口的条件,出现问题,上下按键时候实际上只是切换焦点,结果却因为调用performClick而触发了控件的onClick


在onFocusChange中利用isInTouchMode作为是否调用performClick接口的条件,上下按键正常,只是单纯切换焦点



在onFocusChange中利用isInTouchMode作为是否调用performClick接口的条件,并且用鼠标单击控件,会切换焦点,并且在onFocusChange中isInTouchMode处理,触发控件的onClick


设置控件的触摸焦点控制为false,setFocusableInTouchMode(false),单击控件,是不会触发onFocusChange接口的,但是上面分析anroid源码知道,会去利用performClick触发onClick,并且会导致目前没有任何控件是焦点


没有在onFocusChange增加相关的条件判断,只是打印相关信息,正常鼠标点击控件,会切换焦点到该控件



没有在onFocusChange增加相关的条件判断,和上面一种情况一样,也就是说鼠标单击控件第一次只是切换焦点,然后在点击一次,才会去触发控件的onClick。

这是在onFocusChange中没做任何处理正常情况,而我们DV6300-T要求的事一次鼠标点击就要触发onClick,所以我们手动又在onFocusChange中增加了isInTouchMode作为是否调用performClick接口的条件,这样来手动触发



参考本地网络资料:

E:\JAVA\Android\资料\网络资料\android焦点控制

 

参考博文:

Android onTouchEvent, onClick及onLongClick的调用机制

Android UI事件处理

android 控件焦点问题

Android开发之《Android应用开发揭秘》UI事件汇总

Android学习笔记--UI事件监听器及其处理程序

 

onInterceptTouchEvent和onTouchEvent调用时序

Android onTouchEvent, onClick及onLongClick的调用机制

发布了22 篇原创文章 · 获赞 19 · 访问量 23万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章