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。

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

控件 Focusable FocusableInTouchMode Clickable LongClickable
View false false false false
TextView false false false false
EditText true true true true
Button true false true false
ImageButton true false true false
ImageView false false false false
CheckBox true false true false
RadioButton true false true false
ProgressBar false false false false
LinearLayout false false false false
RelativeLayout false false false false
其他Layout都几乎一样 false false false false

从上面我们可以看出,大部分的控件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

发布了24 篇原创文章 · 获赞 54 · 访问量 4万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览