Android 触摸模式(Touch Mode)

什么是焦点?

在非触屏手机时代或电脑上,我们通常需要用键盘、 鼠标、轨迹球(trackball)与界面进行交互,当交互的时候必须使目标控件获得焦点(比如高亮起来),这样用户才会注意到是什么控件接受输入。而如果是在触屏时代,用户可以直接用手指点击控件,这个时候就没必要将目标高亮了(即获取焦点)。这也就是接下来我们要讲的触摸模式(Touch Mode)。

触摸模式

当用户使用方向键或轨迹球导航用户界面时,必须聚焦到可操作项目上(如按钮),以便用户看到将接受输入的对象。 但是,如果设备具有触摸功能且用户开始通过触摸界面与之交互,则不再需要突出显示项目或聚焦到特定视图对象上。 因此,有一种交互模式称为“触摸模式”(Touch Mode)

触摸模式是用户和手机进行交互时view层次结构的一个状态。代表了最近一次的交互是否是通过触摸屏发生的,因为在Android设备上还存在别的交互方式,比如键盘、等等。

对于支持触摸功能的设备,当用户触摸屏幕时,设备会立即进入触摸模式。无论何时,只要用户点击方向键或滚动轨迹球,设备就会退出触摸模式并找到一个视图使其获得焦点。 现在,用户可在不触摸屏幕的情况下继续与用户界面交互。

整个系统(所有窗口和 Activity)都将保持触摸模式状态。要查询当前状态,您可以调用View#isInTouchMode() 来检查设备目前是否处于触摸模式。

焦点和触摸模式

在触摸模式下,没有焦点,没有选择。同样,任何已聚焦的控件当进入触摸模式时都变为未聚焦状态。而当使用轨迹球和键盘时,就会立即离开触摸模式,控件就会变成聚焦的状态。

现在我们知道了焦点不可以存在于触摸模式了吧,但是这并不完全正确,焦点其实是可以一种特殊的方式存在于触摸模式中的,我们称之为聚焦( focusable)。这种特殊的模式是专门为可接收文字输入的控件创建的,比如EditText。这就是为什么用户可以直接输入文字到文本框中,而不必先用手指选择其文本框的原因。

在触摸模式中,任何控件只要是可聚焦(focusable )的状态,当用户点击其控件时,该控件就会接收到其焦点。如果是不可聚焦的,点击控件将不会接收到焦点。如下所示,当用户点击EditText时,EditText会接收到焦点:

image_1b45qt521k4j1uje2us11bdr479.png-51.6kB

对于支持触摸功能的设备,当用户触摸屏幕时,设备会立即进入触摸模式。 自此以后,只有 isFocusableInTouchMode() 为 true 的视图才可聚焦,如文本编辑小部件EditText。其他可触摸的视图(如按钮Button)在用户触摸时不会获得焦点;按下时它们只是触发点击侦听器。

在触摸模式下,只有少部分的控件默认是可聚焦的状态,例如EditText。可以通过setFocusableInTouchMode或xml中android:focusableInTouchMode设置控件是否可聚焦。

setFocusableInTouchMode 和 setFocusable

很多人对这两个方法有疑问其实很简单:

  • setFocusable:设置控件是否能获取焦点。可以通过isFocusable()获取其状态。
  • setFocusableInTouchMode:在触摸模式下,设置控件是否允许聚焦。可以通过isFocusableInTouchMode() 获取其状态。

在使用键盘或轨迹球的情况下,只有setFocusable为true的控件,才可以获取焦点(选中时高亮)。而在触摸模式下,setFocusable为true,并无法保证控件可以获取焦点。setFocusable为true只能保证在非触摸模式下,该控件可以允许获取焦点。如果想在在触摸模式中,改变控件是否允许聚焦,请使用setFocusableInTouchMode进行更改。

从上面我们也可以看出,不管是否在触摸模式下,控件获取焦点的前提是isFocusable()为true。而在触摸模式下,只有isFocusable()和isFocusableInTouchMode()都为ture的情况下,控件才允许聚焦。

下面通过设置setFocusableInTouchMode和setFocusable的先后顺序,查看控件的焦点状态:

button.setFocusableInTouchMode(false);
button.setFocusable(true);
Log.d("cryc","isFocusable  "+button.isFocusable()+" isFocusableInTouchMode "+ button.isFocusableInTouchMode());
//isFocusable  true  isFocusableInTouchMode false 

button.setFocusableInTouchMode(true);
button.setFocusable(false);
Log.d("cryc","isFocusable  "+button.isFocusable()+" isFocusableInTouchMode "+ button.isFocusableInTouchMode());
//isFocusable  false isFocusableInTouchMode false

button.setFocusable(true);
button.setFocusableInTouchMode(false);
Log.d("cryc","isFocusable  "+button.isFocusable()+" isFocusableInTouchMode "+ button.isFocusableInTouchMode());
//isFocusable  true isFocusableInTouchMode false

button.setFocusable(false);
button.setFocusableInTouchMode(true);
Log.d("cryc","isFocusable  "+button.isFocusable()+" isFocusableInTouchMode "+ button.isFocusableInTouchMode());
//isFocusable  true isFocusableInTouchMode true

通过上面我们发现如下的规律:
setFocusableInTouchMode为true,会使isFocusable也变为true,而setFocusableInTouchMode为false并不影响isFocusable。
setFocusable为false,会使isFocusableInTouchMode变为false,而setFocusable为true并不影响isFocusableInTouchMode。

下面我列出了各种常用控件的默认初始状态:

控件FocusableFocusableInTouchModeClickableLongClickable
Viewfalsefalsefalsefalse
TextViewfalsefalsefalsefalse
EditTexttruetruetruetrue
Buttontruefalsetruefalse
ImageButtontruefalsetruefalse
ImageViewfalsefalsefalsefalse
CheckBoxtruefalsetruefalse
RadioButtontruefalsetruefalse
ProgressBarfalsefalsefalsefalse
LinearLayoutfalsefalsefalsefalse
RelativeLayoutfalsefalsefalsefalse
其他Layout都几乎一样falsefalsefalsefalse

从上面我们可以看出,大部分的控件FocusableInTouchMode属性都为false。只有类似EditText这种控件才为true,因为EditText需要提供在没用户点击的条件下,弹出一个软键盘进行输入的功能。

控件可聚焦的注意点

我们现在有如下的布局,设置如下的两个Button都是可聚焦的:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.mytesttwo.MainActivity">
    <Button
        android:layout_width="match_parent"
        android:text="one"
        android:focusableInTouchMode="true"
        android:id="@+id/btnOne"
        android:layout_height="wrap_content" />
    <Button
        android:layout_width="match_parent"
        android:text="two"
        android:focusableInTouchMode="true"
        android:id="@+id/btnTwo"
        android:layout_height="wrap_content" />
</LinearLayout>

并设置了两个button点击和焦点的监听事件:

btnOne.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                  Log.d("cryc","btnOne onClick");
            }
        });
        btnOne.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                Log.d("cryc","btnOne onFocusChange "+ hasFocus);
            }
        });
        btnTwo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("cryc","btnTwo onClick");
            }
        });
        btnTwo.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                Log.d("cryc","btnTwo onFocusChange "+hasFocus);
            }
        });

当首次进后,应用界面是下面这样的
image_1b45rk6cod691q12nsfqae1t1r13.png-39.3kB
这时候焦点在按钮one上,使按钮的背景变成了橘黄色,Log也打印出了信息。

btnOne onFocusChange true 

这时候当我们点击按钮two的时候,焦点切换到按钮two上:

image_1b45rlg4kov1u291r5617gg12vq1t.png-35.1kB

btnOne onFocusChange false
btnTwo onFocusChange true

注意Log打印出的信息,当用户点击button two的时候,点击事件并没有响应,只有焦点事件响应了。这时候有的开发者会觉得很奇怪?通常我们不是点击一下就可以响应按钮的点击事件吗? 因为当前的按钮处于可聚焦的状态,要让按钮响应事件必须点击两下:

  • 第一下,使焦点聚焦于button上。
  • 第二下,才是真正响应点击的事件。

从上面我们也可以看出有时候设置focusableInTouchMode为true真的不能乱用,不然可能导致上面的情况发生。

在默认拥有可聚焦属性的EditText也是如此,当进入带有EditText控件的页面时,EditText会自动获取焦点(黄色边框+光标闪烁),但此时输入法软键盘是不会自动弹起的(除非你特殊设置),也是需要用户点击EditText才会弹起输入法:
image_1b45rpb4fjvvc0g772gil1hjn2n.png-20.8kB
java EditTextOne onFocusChange true
但是当我们已经处在页面中时,用户只要点击一次第二个的输入框时,软键盘就会弹起。不要以为第二个的输入框接收了点击事件(onClick),它接收的还是焦点事件,之所以会弹起,因为系统内部做了处理。

EditTextOne  onFocusChange false
EditTextTwo  onFocusChange true

那系统内部如何做处理的呢?我认为可能是在onTouchEvent方法中做处理,注意了只要有触摸控件的操作都会触发onTouchEvent方法,当手指接触控件的那一刻先响应的是onTouchEvent方法,然后触发的才是onFocusChange方法或onClick方法。

注意:要想让控件不触发onTouchEvent方法,设置控件disable是没有效果的,控件的CLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE这三个状态必须都为false才行。还有一个方法就是可以通过父控件进行截获输入事件。

在项目中,一进入一个页面, EditText默认就会自动获取焦点。那么如何取消这个默认行为呢?有人在EditText的父级控件中找一个,设置成可聚焦的状态:android:focusableInTouchMode=”true” 把EditText的焦点进行截获,使焦点转移到父控件上:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:focusableInTouchMode="true"
   >
    <EditText
       android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

虽然解决了当前的问题,但是这里却有个隐含的风险,在有些低版本的系统中,当你把焦点转移到父控件的身上时,如果从后台切换到前台,会导致整个界面发送抖动。而这个bug通常很少人会得出原因,因此在转移焦点的时候要特别注意。

由于设置了focusableInTouchMode属性后会引起和android正常交互行为的不一致,所以android建议我们保守地使用这个属性,在你确定要用它之前最好三思而后行。

参考:
http://android-developers.blogspot.com/2008/12/touch-mode.html
https://developer.android.com/guide/topics/ui/ui-events.html
http://jakend.iteye.com/blog/764521
http://www.cnblogs.com/xiaoweiz/p/3833079.html

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值