跟踪触摸和指针移动

试用 Compose 方式
Jetpack Compose 是推荐在 Android 设备上使用的界面工具包。了解如何在 Compose 中使用触控和输入功能。
<ph type="x-smartling-placeholder"></ph> 手势 →

本课介绍了如何跟踪触摸事件中的移动操作。

一种新的 onTouchEvent() 触发 ACTION_MOVE事件 只要当前触摸接触位置、压力或大小发生变化。如 检测常用手势中的说明,所有 这些事件都会记录在 MotionEvent 参数(属于 onTouchEvent()

因为基于手指的触摸并不总是最精确的互动形式, 触摸事件的检测通常更基于移动,而非简单接触。 帮助应用区分基于移动的手势(例如滑动)和 非移动手势(例如点按一次),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()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):

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(),这通常是因为视图层次结构 包含请求捕获的视图会失去焦点。