本课介绍了如何跟踪触摸事件中的移动操作。
每当当前的触摸接触位置、压力或大小发生变化时,系统都会通过 ACTION_MOVE
事件触发新的 onTouchEvent()
。如检测常用手势中所述,所有这些事件都记录在 onTouchEvent()
的 MotionEvent
参数中。
由于基于手指的触摸并不总是最精确的互动形式,因此检测触摸事件通常更多地基于移动而非简单接触。为了帮助应用区分移动类手势(例如滑动)和非移动手势(例如点按一次),Android 引入了 Touch slop 的概念。Touch slop 是指在系统将手势解读为基于移动的手势之前,用户的轻触手势可以滑动的距离(以像素为单位)。如需详细了解此主题,请参阅在 ViewGroup 中管理轻触事件。
您可以通过多种方式跟踪手势中的移动情况,具体取决于应用的需求。示例如下:
- 指针的起始位置和结束位置,例如将屏幕上的对象从 A 点移动到 B 点。
- 指针的行进方向,由 X 和 Y 坐标确定。
- 历史事件。您可以通过调用
MotionEvent
方法getHistorySize()
来查看手势历史记录的大小。然后,您可以使用动作事件的getHistorical<Value>
方法来获取每个历史事件的位置、大小、时间和压力。在渲染用户手指的轨迹(例如,对于触摸绘制)时,历史记录非常有用。如需了解详情,请参阅MotionEvent
参考文档。 - 指针在触摸屏上移动时的速度。
请参阅以下相关资源:
跟踪速度
您可以采用基于移动的手势,该手势基于指针移动的距离或方向。不过,在跟踪手势特征或确定手势是否发生时,速度通常是决定性因素。为了简化速度计算,Android 提供了 VelocityTracker
类。VelocityTracker
可帮助您跟踪触摸事件的速度。这对于将速度作为手势标准的一部分的手势(例如快滑)非常有用。
以下示例说明了 VelocityTracker
API 中各个方法的用途:
Kotlin
private const val DEBUG_TAG = "Velocity" class MainActivity : Activity() { private var mVelocityTracker: VelocityTracker? = null override fun onTouchEvent(event: MotionEvent): Boolean { when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { // Reset the velocity tracker back to its initial state. mVelocityTracker?.clear() // If necessary, retrieve a new VelocityTracker object to watch // the velocity of a motion. mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain() // Add a user's movement to the tracker. mVelocityTracker?.addMovement(event) } MotionEvent.ACTION_MOVE -> { mVelocityTracker?.apply { val pointerId: Int = event.getPointerId(event.actionIndex) addMovement(event) // When you want to determine the velocity, call // computeCurrentVelocity(). Then, call getXVelocity() and // getYVelocity() to retrieve the velocity for each pointer // ID. computeCurrentVelocity(1000) // Log velocity of pixels per second. It's best practice to // use VelocityTrackerCompat where possible. Log.d("", "X velocity: ${getXVelocity(pointerId)}") Log.d("", "Y velocity: ${getYVelocity(pointerId)}") } } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { // Return a VelocityTracker object back to be re-used by others. mVelocityTracker?.recycle() mVelocityTracker = null } } return true } }
Java
public class MainActivity extends Activity { private static final String DEBUG_TAG = "Velocity"; ... private VelocityTracker mVelocityTracker = null; @Override public boolean onTouchEvent(MotionEvent event) { int index = event.getActionIndex(); int action = event.getActionMasked(); int pointerId = event.getPointerId(index); switch(action) { case MotionEvent.ACTION_DOWN: if(mVelocityTracker == null) { // Retrieve a new VelocityTracker object to watch the // velocity of a motion. mVelocityTracker = VelocityTracker.obtain(); } else { // Reset the velocity tracker back to its initial state. mVelocityTracker.clear(); } // Add a user's movement to the tracker. mVelocityTracker.addMovement(event); break; case MotionEvent.ACTION_MOVE: mVelocityTracker.addMovement(event); // When you want to determine the velocity, call // computeCurrentVelocity(). Then call getXVelocity() and // getYVelocity() to retrieve the velocity for each pointer ID. mVelocityTracker.computeCurrentVelocity(1000); // Log velocity of pixels per second. It's best practice to use // VelocityTrackerCompat where possible. Log.d("", "X velocity: " + mVelocityTracker.getXVelocity(pointerId)); Log.d("", "Y velocity: " + mVelocityTracker.getYVelocity(pointerId)); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: // Return a VelocityTracker object back to be re-used by others. mVelocityTracker.recycle(); break; } return true; } }
使用指针捕获
某些应用(例如游戏、远程桌面和虚拟化客户端)可受益于对鼠标指针的控制。指针捕获是 Android 8.0(API 级别 26)及更高版本中提供的一项功能,通过将所有鼠标事件传递到应用中的聚焦视图来提供这种控制。
请求指针捕获
应用中的视图只有在包含它的视图层次结构获得焦点时才能请求指针捕获。因此,当视图上发生特定用户操作时(例如在 onClick()
事件期间或 activity 的 onWindowFocusChanged()
事件处理程序中),请求指针捕获。
如需请求指针捕获,请对视图调用 requestPointerCapture()
方法。以下代码示例展示了如何在用户点击视图时请求指针捕获:
Kotlin
fun onClick(view: View) { view.requestPointerCapture() }
Java
@Override public void onClick(View view) { view.requestPointerCapture(); }
在捕获指针的请求成功后,Android 会调用 onPointerCaptureChange(true)
。系统会将鼠标事件传递到应用中的聚焦视图,前提是该视图与请求捕获的视图位于同一视图层次结构中。其他应用会停止接收鼠标事件,直到捕获释放为止,包括 ACTION_OUTSIDE
事件。Android 会照常从鼠标以外的其他来源传递指针事件,但鼠标指针不再可见。
处理捕获的指针事件
一旦视图成功获取指针捕获,Android 就会传递鼠标事件。聚焦的视图可以通过执行以下任务之一来处理事件:
- 如果您使用的是自定义视图,请替换
onCapturedPointerEvent(MotionEvent)
。 - 否则,请注册
OnCapturedPointerListener
。
以下代码示例展示了如何实现 onCapturedPointerEvent(MotionEvent)
:
Kotlin
override fun onCapturedPointerEvent(motionEvent: MotionEvent): Boolean { // Get the coordinates required by your app. val verticalOffset: Float = motionEvent.y // Use the coordinates to update your view and return true if the event is // successfully processed. return true }
Java
@Override public boolean onCapturedPointerEvent(MotionEvent motionEvent) { // Get the coordinates required by your app. float verticalOffset = motionEvent.getY(); // Use the coordinates to update your view and return true if the event is // successfully processed. return true; }
以下代码示例展示了如何注册 OnCapturedPointerListener
:
Kotlin
myView.setOnCapturedPointerListener { view, motionEvent -> // Get the coordinates required by your app. val horizontalOffset: Float = motionEvent.x // Use the coordinates to update your view and return true if the event is // successfully processed. true }
Java
myView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() { @Override public boolean onCapturedPointer (View view, MotionEvent motionEvent) { // Get the coordinates required by your app. float horizontalOffset = motionEvent.getX(); // Use the coordinates to update your view and return true if the event is // successfully processed. return true; } });
无论您是使用自定义视图还是注册监听器,您的视图都会收到包含指针坐标的 MotionEvent
,该坐标会指定相对移动(例如 X 或 Y 增量),类似于轨迹球设备传递的坐标。您可以使用 getX()
和 getY()
检索这些坐标。
释放捕获的指针
应用中的视图可以通过调用 releasePointerCapture()
释放指针捕获,如以下代码示例所示:
Kotlin
override fun onClick(view: View) { view.releasePointerCapture() }
Java
@Override public void onClick(View view) { view.releasePointerCapture(); }
系统可以在您不显式调用 releasePointerCapture()
的情况下将捕获的指针从视图中移走,这通常是因为请求捕获的视图所在的视图层次结构会失去焦点。