Отслеживание касаний и движений указателя

Попробуйте способ создания
Jetpack Compose — рекомендуемый набор инструментов пользовательского интерфейса для Android. Узнайте, как использовать сенсорный ввод и ввод в Compose.

В этом уроке описывается, как отслеживать движение в событиях касания.

Новый onTouchEvent() запускается с помощью события ACTION_MOVE всякий раз, когда изменяется текущее положение сенсорного контакта, давление или размер. Как описано в разделе «Обнаружение распространенных жестов» , все эти события записываются в параметре MotionEvent метода onTouchEvent() .

Поскольку прикосновение пальцами не всегда является наиболее точной формой взаимодействия, обнаружение событий прикосновения часто основано больше на движении, чем на простом контакте. Чтобы помочь приложениям различать жесты, основанные на движении (например, смахивание), и жесты, не связанные с движением (например, одно касание), Android включает понятие касания . Наклон касания — это расстояние в пикселях, на которое может переместиться прикосновение пользователя, прежде чем жест будет интерпретирован как жест, основанный на движении. Дополнительные сведения по этой теме см. в разделе Управление событиями касания в ViewGroup .

Существует несколько способов отслеживания движения с помощью жеста, в зависимости от потребностей вашего приложения. Ниже приведены примеры:

  • Начальное и конечное положение указателя, например перемещение объекта на экране из точки А в точку Б.
  • Направление, в котором движется указатель, определенное координатами X и Y.
  • История. Вы можете узнать размер истории жестов, вызвав метод MotionEvent getHistorySize() . Затем вы можете получить положения, размеры, время и давление каждого из исторических событий, используя методы getHistorical <Value> события движения. История полезна при визуализации следа пальца пользователя, например, при рисовании касанием. Подробности см. в справочнике MotionEvent .
  • Скорость указателя при его перемещении по сенсорному экрану.

Обратитесь к следующим соответствующим ресурсам:

Скорость отслеживания

Вы можете использовать жест, основанный на движении, который зависит от расстояния или направления перемещения указателя. Однако скорость часто является определяющим фактором при отслеживании характеристик жеста или принятии решения о том, произошел ли этот жест. Чтобы упростить расчет скорости, Android предоставляет класс VelocityTracker . VelocityTracker помогает отслеживать скорость событий касания. Это полезно для жестов, в которых скорость является частью критерия жеста, например, для броска.

Вот пример, иллюстрирующий назначение методов API VelocityTracker :

Котлин

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
    }
}

Ява

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() в представлении. В следующем примере кода показано, как запросить захват указателя, когда пользователь щелкает представление:

Котлин

fun onClick(view: View) {
    view.requestPointerCapture()
}

Ява

@Override
public void onClick(View view) {
    view.requestPointerCapture();
}

Как только запрос на захват указателя будет успешным, Android вызывает onPointerCaptureChange(true) . Система доставляет события мыши в сфокусированное представление вашего приложения, если оно находится в той же иерархии представлений, что и представление, запросившее захват. Другие приложения перестают получать события мыши до тех пор, пока не будет снят захват, включая события ACTION_OUTSIDE . Android обычно доставляет события указателя из источников, отличных от мыши, но указатель мыши больше не виден.

Обработка захваченных событий указателя

Как только представление успешно захватит указатель, Android доставляет события мыши. Ваше сфокусированное представление может обрабатывать события, выполняя одну из следующих задач:

В следующем примере кода показано, как реализовать onCapturedPointerEvent(MotionEvent) :

Котлин

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
}

Ява

@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 :

Котлин

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
}

Ява

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() , как показано в следующем примере кода:

Котлин

override fun onClick(view: View) {
    view.releasePointerCapture()
}

Ява

@Override
public void onClick(View view) {
    view.releasePointerCapture();
}

Система может забрать захват из представления без явного вызова releasePointerCapture() , обычно потому, что иерархия представлений, содержащая представление, которое запрашивает захват, теряет фокус.