在Android上,有多种方法可以拦截用户与应用程序交互的事件。在考虑用户界面中的事件时,方法是从用户交互的特定View对象捕获事件。View类提供了这样做的方式。
在你用来组合布局的各种View类中,你可能会注意到一些对UI事件很有用的公共回调方法。当在该对象上发生相应的操作时,这些方法由Android框架调用。例如,当触摸一个View(例如Button)时,会在该对象上调用onTouchEvent()
方法。但是,为了拦截它,你必须扩展该类并重写该方法。然而,为了处理这样的事件而扩展每一个View对象是不切实际的。这就是为什么View类还包含一系列嵌套接口,这些接口包含你可以更容易定义的回调。这些接口,称为事件监听器,是你捕获用户与UI交互的关键。
虽然你更常用事件监听器来监听用户交互,但有时你可能确实想要扩展一个View类,以便构建一个自定义组件。也许你想扩展Button
类来制作更花哨的东西。在这种情况下,你可以使用类事件处理器来定义类的默认事件行为。
事件监听器
事件监听器是View
类中的一个接口,它包含一个单一的回调方法。当向其注册监听器的View被用户与UI中项目交互触发时,这些方法将由Android框架调用。
事件监听器接口中包含以下回调方法:
onClick()
- 来自
View.OnClickListener
。当用户触摸项目(在触摸模式下),或使用导航键或轨迹球聚焦项目并按下合适的“Enter”键或按下轨迹球时,就会调用此方法。 onLongClick()
- 来自
View.OnLongClickListener
。当用户触摸并按住项目(在触摸模式下),或使用导航键或轨迹球聚焦项目并按住合适的“Enter”键或按住轨迹球(一秒钟)时,就会调用此方法。 onFocusChange()
- 来自
View.OnFocusChangeListener
。当用户使用导航键或轨迹球导航到项目或离开项目时,就会调用此方法。 onKey()
- 来自
View.OnKeyListener
。当用户聚焦于项目并按下或释放设备上的硬件键时,就会调用此方法。 onTouch()
- 来自
View.OnTouchListener
。当用户执行被视为触控事件的操作时,就会调用此方法,包括按下、释放或屏幕上(在项目范围内)的任何移动手势。 onCreateContextMenu()
- 来自
View.OnCreateContextMenuListener
。当构建上下文菜单时(作为持续“长按”的结果),就会调用此方法。请参阅菜单开发者指南中关于上下文菜单的讨论。
这些方法是各自接口中唯一的成员。要定义这些方法之一并处理你的事件,请在你的Activity中实现嵌套接口或将其定义为匿名类。然后,将你的实现实例传递给相应的View.set...Listener()
方法。(例如,调用
并传递你的setOnClickListener()
OnClickListener
实现。)
下面的示例演示如何为Button注册一个单击监听器。
Kotlin
protected void onCreate(savedValues: Bundle) { ... val button: Button = findViewById(R.id.corky) // Register the onClick listener with the implementation above button.setOnClickListener { view -> // do something when the button is clicked } ... }
Java
// Create an anonymous implementation of OnClickListener private OnClickListener corkyListener = 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(corkyListener); ... }
你可能还会发现将OnClickListener作为Activity的一部分来实现更方便。这将避免额外的类加载和对象分配。例如:
Kotlin
class ExampleActivity : Activity(), OnClickListener { protected fun onCreate(savedValues: Bundle) { val button: Button = findViewById(R.id.corky) button.setOnClickListener(this) } // Implement the OnClickListener callback fun onClick(v: View) { // do something when the button is clicked } }
Java
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()
回调没有返回值,但其他一些事件监听器方法必须返回布尔值。原因取决于事件。对于少数确实需要返回值的事件,原因如下:
- 此方法返回一个布尔值,指示你是否已使用该事件,并且不应将其继续传递。也就是说,返回true 表示你已处理该事件,此处应停止;返回false 表示你未处理该事件和/或事件应继续传递到任何其他单击监听器。onLongClick()
- 此方法返回一个布尔值,指示你是否已使用该事件,并且不应将其继续传递。也就是说,返回true 表示你已处理该事件,此处应停止;返回false 表示你未处理该事件和/或事件应继续传递到任何其他按键监听器。onKey()
- 此方法返回一个布尔值,指示你的监听器是否使用此事件。重要的是,此事件可能包含多个相互关联的操作。因此,如果在接收到按下操作事件时返回false,则表示你未使用该事件,并且也不对该事件中的后续操作(例如手指手势或最终的抬起操作事件)感兴趣。因此,对于事件中的任何其他操作,都不会调用你。onTouch()
请记住,硬件按键事件始终传递给当前处于焦点的视图。它们从视图层次结构的顶部开始分派,然后向下,直到到达相应的目标。如果您的视图(或您视图的子视图)当前具有焦点,则您可以看到事件通过
方法进行传递。作为通过您的视图捕获按键事件的替代方法,您也可以使用dispatchKeyEvent()
和onKeyDown()
在您的 Activity 中接收所有事件。onKeyUp()
此外,在考虑应用程序的文本输入时,请记住许多设备只有软件输入法。此类方法不需要基于按键;有些可能使用语音输入、手写输入等等。即使输入法呈现类似键盘的界面,它通常也不会触发
系列事件。除非您想将应用程序限制在具有硬件键盘的设备上,否则您绝不应构建需要特定按键才能控制的 UI。特别是,当用户按下回车键时,不要依赖这些方法来验证输入;相反,请使用诸如onKeyDown()
IME_ACTION_DONE
之类的操作来向输入法发出信号,说明您的应用程序期望如何React,以便它可以以有意义的方式更改其 UI。避免对软件输入法的工作方式做出假设,只需相信它会向您的应用程序提供已格式化的文本。
注意:Android 将首先调用事件处理程序,然后调用类定义中的相应默认处理程序。因此,从这些事件监听器返回 *true* 将阻止事件传播到其他事件监听器,并将阻止对 View 中的默认事件处理程序的回调。因此,请确保您希望在返回 *true* 时终止事件。
事件处理程序
如果您正在从 View 构建自定义组件,那么您将能够定义用作默认事件处理程序的多个回调方法。自定义视图组件 文档中,您将学习一些用于事件处理的常用回调,包括
- 当发生新的按键事件时调用。onKeyDown(int, KeyEvent)
- 当发生按键抬起事件时调用。onKeyUp(int, KeyEvent)
- 当发生轨迹球运动事件时调用。onTrackballEvent(MotionEvent)
- 当发生触摸屏运动事件时调用。onTouchEvent(MotionEvent)
- 当视图获得或失去焦点时调用。onFocusChanged(boolean, int, Rect)
还有一些您应该注意的方法,它们不是 View 类的组成部分,但可以直接影响您处理事件的方式。因此,在管理布局内的更复杂的事件时,请考虑以下其他方法
- 这允许您的Activity.dispatchTouchEvent(MotionEvent)
Activity
在事件分派到窗口之前拦截所有触摸事件。
- 这允许ViewGroup.onInterceptTouchEvent(MotionEvent)
ViewGroup
在事件分派到子视图时观察这些事件。
- 在父视图上调用此方法以指示它不应使用ViewParent.requestDisallowInterceptTouchEvent(boolean)
拦截触摸事件。onInterceptTouchEvent(MotionEvent)
触摸模式
当用户使用方向键或轨迹球浏览用户界面时,需要将焦点赋予可操作项目(如按钮),以便用户可以看到哪些项目可以接受输入。但是,如果设备具有触摸功能,并且用户开始通过触摸设备与界面交互,则不再需要突出显示项目或将焦点赋予特定视图。因此,存在一种名为“触摸模式”的交互模式。
对于具有触摸功能的设备,一旦用户触摸屏幕,设备将进入触摸模式。从那时起,只有isFocusableInTouchMode()
为 true 的视图才可聚焦,例如文本编辑小部件。其他可触摸的视图(如按钮)在被触摸时不会获得焦点;它们只会按下时触发其 onclick 监听器。
任何时候用户按下方向键或使用轨迹球滚动,设备都会退出触摸模式,并找到一个视图来获取焦点。现在,用户可以在不触摸屏幕的情况下继续与用户界面交互。
触摸模式状态在整个系统(所有窗口和活动)中都得到维护。要查询当前状态,您可以调用isInTouchMode()
来查看设备当前是否处于触摸模式。
处理焦点
框架将处理响应用户输入的常规焦点移动。这包括在移除或隐藏视图时,或在出现新视图时更改焦点。视图通过
方法指示其获取焦点的意愿。要更改视图是否可以获取焦点,请调用isFocusable()
。处于触摸模式时,您可以使用setFocusable()
查询视图是否允许焦点。您可以使用isFocusableInTouchMode()
更改此设置。setFocusableInTouchMode()
在运行 Android 9(API 级别 28)或更高版本的设备上,活动不会分配初始焦点。如有需要,您必须显式请求初始焦点。
焦点移动基于一种算法,该算法在给定方向上查找最近的邻居。在极少数情况下,默认算法可能与开发人员的预期行为不符。在这种情况下,您可以在布局文件中使用以下 XML 属性提供显式覆盖:nextFocusDown、nextFocusLeft、nextFocusRight 和 nextFocusUp。将这些属性之一添加到焦点即将离开的视图中。将属性的值定义为应赋予焦点的视图的 ID。例如
<LinearLayout android:orientation="vertical" ... > <Button android:id="@+id/top" android:nextFocusUp="@+id/bottom" ... /> <Button android:id="@+id/bottom" android:nextFocusDown="@+id/top" ... /> </LinearLayout>
通常情况下,在此垂直布局中,从第一个按钮向上导航不会转到任何地方,从第二个按钮向下导航也不会转到任何地方。现在,顶部按钮已将底部按钮定义为 nextFocusUp(反之亦然),导航焦点将从上到下和从下到上循环。
如果要在 UI 中声明视图可聚焦(传统上不可聚焦),请将android:focusable
XML 属性添加到布局声明中的视图。将值设置为true。您还可以使用android:focusableInTouchMode
在触摸模式下将视图声明为可聚焦。
要请求特定视图获取焦点,请调用
。requestFocus()
要监听焦点事件(在视图接收或失去焦点时收到通知),请使用
,如事件监听器 部分所述。onFocusChange()