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

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

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

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

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

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

  • Начальное и конечное положение указателя, например, перемещение объекта на экране из точки А в точку В.
  • Направление движения указателя определяется координатами X и Y.
  • История. Размер истории жестов можно узнать, вызвав метод getHistorySize() объекта MotionEvent . Затем, используя методы getHistorical <Value> объекта `MotionEvent`, можно получить координаты, размеры, время и силу нажатия каждого из событий в истории. История полезна при отрисовке следа от пальца пользователя, например, при рисовании касанием. Подробнее см. справочник по 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
    }
}

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

Котлин

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

Java

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

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 :

Котлин

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

Котлин

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

Java

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

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