to top

Input Events (原文链接)

翻译:汉尼拔萝卜(https://github.com/gaojian3301)

在 Android 中,有多种方式来拦截应用程序中的 UI 事件。当设计应用中的 UI 事件时,应该捕获用户与之交互View对象的 UI 事件。View 类提供了这么做的方法。

在你使用各种 View 类来布局时,会发现一些看起来非常有用的 UI 事件回调方法。这些接口在用户与 View 发生互动时被 Android 系统调用。比如当一个 View(Button等)被触碰时,该View的onTouchEvent() 方法就会被调用,要想拦截这个事件,就绪要继承 View 类,并且覆盖这个方法,但是每一个控件在使用时都需要继承 View 类是很二的,所以View类中定义了一些列方便使用的回调接口,这些加口被称为 event listeners,通过它们可以轻松实现与 UI 界面的互动。

大多数情况下我们使用这些事件监听器来监听 UI 控件,但是有时候我们会需要去继承 View 类来自定义一个控件。可能我们需要通过继承 Button 类来实现更多的功能,这种情况下可以使用 event handlers 为自定义的类定义自己的事件行为。

Event Listeners

事件监听器是 View 类中只包含一个回调方法的接口。当一个控件注册了事件监听器并且发生了相应的 UI 事件,这个方法就会被 Android 系统调用。

监听器中包括一下回调方法:

onClick()
From View.OnClickListener. 这个方法在触摸模式下,触碰到就会被触发;使用导航键或者轨迹球是这个 View 获取焦点,然后点击"Enter"或者按下轨迹球,这个方法也会被触发。
onLongClick()
From View.OnLongClickListener. 在触摸模式下,用户长按着这个控件,将会调用这个方法;通过导航键或者轨迹球定位到这个控件,然后长按“Enter”键或者按住轨迹球(一秒中),这个方法将会被调用。
onFocusChange()
From View.OnFocusChangeListener. 当用户使用导航键或者轨迹球,将焦点由这个控件移向其他控件时,这个方法会被调用。
onKey()
From View.OnKeyListener. 当这个控件获得焦点时,用户按下或者松开设备的物理按键,这个方法就会被调用。
onTouch()
From View.OnTouchListener. 发生在这个控件上(在控件的边界之内)的移动、按下、松开等操作都会调用这个方法。
onCreateContextMenu()
From View.OnCreateContextMenuListener. 当 Context Menu 被创建时调用(比如我们在长按某个控件时,会弹出菜单来让用户做一些选择)。这一点将会在 Menus 文章中详细介绍。

这些方法都是它们对应接口中的唯一方法,要使用这些方法来处理UI事件,可以让 Activity 实现接口或者定义一个匿名的类。然后将实例传给对应 View 的 View.set...Listener()方法。比如说将 OnClickListener 的实例传给 setOnClickListener() 方法。

下面实例展示如何给一个 Button 注册一个单击事件.

// Create an anonymous implementation of OnClickListener
private OnClickListener mCorkyListener = new OnClickListener() {
    public void onClick(View v) {
      // do something when the button is clicked
    }
};

protected void onCreate(Bundle savedValues) {
    ...
    // Capture our button from layout
    Button button = (Button)findViewById(R.id.corky);
    // Register the onClick listener with the implementation above
    button.setOnClickListener(mCorkyListener);
    ...
}

也可以让 Activity 来实现 OnClickListener 接口,这样做的好处时不许要加载额外的类:

public class ExampleActivity extends Activity implements OnClickListener {
    protected void onCreate(Bundle savedValues) {
        ...
        Button button = (Button)findViewById(R.id.corky);
        button.setOnClickListener(this);
    }

    // Implement the OnClickListener callback
    public void onClick(View v) {
      // do something when the button is clicked
    }
    ...
}

可以注意到 onClick() 方法是没有返回值的,但是一些其他的监听起方法是必须要有 boolean 类型的返回值的。有没有返回值取决与UI 事件,下面是原因:

  • onLongClick() - boolean 类型的返回值来表示本次事件是否被消耗掉、应不应该继续向下传递。也就是说,返回 true 表示本次事件已经处理完成,应该停止;返回 false 则表示本次事件应该继续被其他监听器捕获处理。
  • onKey() - boolean 类型的返回值来表示本次事件是否被消耗掉、应不应该继续向下传递。也就是说,返回 true 表示本次事件已经处理完成,应该停止;返回 false 则表示本次事件应该继续被其他监听器捕获处理。
  • onTouch() - 用来表示本次事件有没有被消耗掉。这个事件可能会有很多的后续动作,因此如果当你接受到 down 事件时返回 false,就表明没有消耗掉这个事件并且对这个事件的后续动作不感兴趣。因此这个事件中你将不能调用其他事件,比如手势和其他的最终事件。

物理按键事件总是传递给获取当前焦点的控件,它们从视图层次的最顶端开始,一层层向下直到传给对应的控件。如果你的当前取得焦点,你可以通过 dispatchKeyEvent() 看到事件的传递。通过 onKeyDown()onKeyUp() 方法可以捕捉到 Activity 当中所有控的件物理按键事件。

当设计应用程序中的文本输入时,要记住许多设备只有软件盘输入。一些方法不需要物理键盘;可能使用语音输入、手写等等。即使软件盘输入法外观跟物理键盘一样,但是并不会触发 onKeyDown() 这一系列事件。千万别依赖物理按键来构建 UI,除非你只想你的应用在那些带有物理键盘的手机上运行。在用户点击返回时,不用使用这些方法来验证输入的数据,应该使用 IME_ACTION_DONE 来通知输入法应该做何操作,这样做很很有校的改变UI. 不要去猜测软件输入法如何工作,只要相信它可以提供格式良好的文本即可。

Note: Android 系统首先会调用事件处理器,然后才是类中定义的默认处理器。因此,时间监听器返回 true 将会中断事件的继续传播,并且阻塞了这个控件的其他事件处理器的回调。所以只有当你确定要在此时结束事件的传递,才能返回 true.

Event Handlers

在通过继承 View 来自定义控件时,可以定义几个回调方法来当作默认的事件处理器。在 Custom Components 中,你将学到一些常用来处理事件的回调方法,包括:

还有其他需要注意的方法,它们并不是 View 类的一部分,但是能够对事件的处理产生直接影响。因此,在管理布局中复杂的事件处理时,考虑下面几个方法:

Touch Mode(现在基本没有带键盘的android手机了,所以手机永远是处于 touch mode 状态)

当用户使用按键或轨迹球与UI进行交互的时候, 必须先使目标控件获取焦点(比如按钮),这样用户才会注意到是什么控件接收输入。但是如果设备支持触摸的话, 再去高亮这个控件或者给它焦点了。因此产生了一种交互“Touch Mode”的交互模式。

一旦用户触摸屏幕,设备进进入触摸模式,只有 isFocusableInTouchMode() 方法返回值为 true 的控件才能获取焦点,比如 EditText控件;其它控件(Button等)就不会获取焦点,他们只是简单的执行点击事件而已。

用户点击按键或者使用了轨迹球,设备将会退出触摸模式,此时会找到一个控件聚焦,用户可以不是用触摸模式了。

触摸模式的状态在系统运行期间都是在维护的,想要获取当前状态,调用 isInTouchMode() 就可一判断系统是否处于触摸模式。

Handling Focus

framework 处理焦点的移动来响应用户输入,当控件被移除、隐藏或者新的控件变得可见时改它们的焦点。控件可以同过 isFocusable() 方法来表明自己可不可以获得焦点,也可以通过 setFocusable() 来改变它可不可以获取焦点。在触摸模式下,可以通过 isFocusableInTouchMode() 的返回值来判断这个控件是否可以聚焦,可以通过 setFocusableInTouchMode() 方法来改变。

焦点移动的算法是找到给定方向上最近的 View,在一些情况下,默认算法不符合开发的需求,可以在 layout 文件中添加这些 XML 属性:nextFocusDownnextFocusLeftnextFocusRightnextFocusUp. 将其中一个属性添加给将要失去焦点的控件,并且将这个属性的值设为想要聚焦的控件 id ,举个例子:

<LinearLayout
    android:orientation="vertical"
    ... >
  <Button android:id="@+id/top"
          android:nextFocusUp="@+id/bottom"
          ... />
  <Button android:id="@+id/bottom"
          android:nextFocusDown="@+id/top"
          ... />
</LinearLayout>

默认情况下,在竖直布局中,从第一个 button 向上导航,第二个 button 向下导航,将会哪儿也到不了。现在 top 按钮已经将 bottom 按钮作为向上导航的控件,导航的焦点将会在这两个 Button 上循环。

如果你想要在申明 UI 中的控件是可以聚焦的(默认不是),可以给这个控件添加 android:focusable 属性。你也可以通过将 android:focusableInTouchMode 的值设为 true 来申明控件在触摸模式下可以获取焦点。

可以调用 requestFocus() 来让一个控件获取焦点。

可以通过 Event Listeners 中提到的 onFocusChange() 方法来监听焦点变化。