Śledź ruchy wskaźnika i dotyku

Wypróbuj sposób tworzenia wiadomości
Jetpack Compose to zalecany zestaw narzędzi UI na Androida. Dowiedz się, jak w funkcji tworzenia wiadomości używać dotyku i wprowadzania tekstu.

Z tej lekcji dowiesz się, jak śledzić ruch w zdarzeniach dotknięcia.

Nowe onTouchEvent() jest wywoływane ze zdarzeniem ACTION_MOVE za każdym razem, gdy zmieni się bieżąca pozycja kontaktu, nacisk lub rozmiar. Jak opisano w sekcji Wykrywanie typowych gestów, wszystkie te zdarzenia są rejestrowane w parametrze MotionEvent parametru onTouchEvent().

Dotyk palcem nie zawsze jest najdokładniejszą formą interakcji, dlatego wykrywanie dotknięcia często opiera się bardziej na ruchu niż na prostym kontakcie. Aby ułatwić aplikacjom rozróżnianie gestów związanych z ruchem (np. przesuwania) i nieporuszających (np. jedno dotknięcie), Android umożliwia upływ dotyku. Upływ dotyku odnosi się do odległości w pikselach, jaką użytkownik może wykonywać dotyk, zanim gest zostanie zinterpretowany jako gest związany z ruchem. Więcej informacji na ten temat znajdziesz w artykule Zarządzanie zdarzeniami dotknięcia w grupie ViewGroup.

Istnieje kilka sposobów śledzenia ruchu za pomocą gestów w zależności od potrzeb aplikacji. Przykłady:

  • Położenie początkowe i końcowe wskaźnika, np. przesunięcie obiektu na ekranie z punktu A do punktu B.
  • Kierunek, w którym porusza się wskaźnik, określony za pomocą współrzędnych X i Y.
  • Historia. Rozmiar historii gestu możesz sprawdzić, wywołując metodę MotionEvent getHistorySize(). Korzystając z metod getHistorical<Value> zdarzenia ruchu, możesz uzyskać informacje o położeniu, rozmiarze, czasie i siły nacisku każdego zdarzenia historycznego. Historia jest przydatna podczas renderowania śladu palca użytkownika, np. do rysowania dotykowego. Więcej informacji znajdziesz w dokumentacji MotionEvent.
  • Szybkość, z jaką wskaźnik porusza się po ekranie dotykowym.

Zapoznaj się z tymi powiązanymi materiałami:

Prędkość na torze

Możesz użyć gestu związanego z ruchem opartym na odległości lub kierunku pokonywania wskaźnika. Jednak to prędkość często decyduje o jego Aby ułatwić obliczanie szybkości, Android udostępnia klasę VelocityTracker. Funkcja VelocityTracker pomaga śledzić częstotliwość zdarzeń dotknięcia. Przydaje się to przy gestach, których szybkość jest jednym z kryteriów gestu, takich jak gesty.

Oto przykład ilustrujący przeznaczenie metod w interfejsie API VelocityTracker:

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

Używaj przechwytywania wskaźnika

Niektóre aplikacje, takie jak gry, klienty pulpitu zdalnego i wirtualizacji, mogą korzystać z możliwości kontrolowania wskaźnika myszy. Przechwytywanie wskaźnika to funkcja dostępna w Androidzie 8.0 (poziom interfejsu API 26) i nowszych, która zapewnia tę kontrolę przez wyświetlanie wszystkich zdarzeń myszy w wybranym widoku aplikacji.

Poproś o zdjęcie wskaźnika

Widok w aplikacji może poprosić o przechwycenie wskaźnika tylko wtedy, gdy zawiera go hierarchia widoków. Dlatego poproś o przechwytywanie wskaźnika, gdy w widoku istnieje określone działanie użytkownika, np. podczas zdarzenia onClick() lub w module obsługi zdarzeń onWindowFocusChanged() w Twojej aktywności.

Aby poprosić o przechwycenie wskaźnika, wywołaj w widoku metodę requestPointerCapture(). Poniższy przykładowy kod pokazuje, jak zażądać przechwytywania wskaźnika, gdy użytkownik kliknie widok:

Kotlin

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

Java

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

Gdy żądanie przechwycenia wskaźnika zostanie zrealizowane, Android wywoła metodę onPointerCaptureChange(true). System dostarcza zdarzenia myszy do aktywnego widoku aplikacji, o ile znajduje się ona w tej samej hierarchii widoków co widok, z którego przechwycono obraz. Inne aplikacje (w tym zdarzenia ACTION_OUTSIDE) nie odbierają zdarzeń myszy do czasu udostępnienia przechwycenia. Android dostarcza zdarzenia wskaźnika ze źródeł innych niż mysz w zwykły sposób, ale wskaźnik myszy nie jest już widoczny.

Obsługa przechwyconych zdarzeń wskaźnika

Gdy widok uzyska przechwycenie wskaźnika, Android dostarczy zdarzenia myszy. Widok aktywny może obsługiwać zdarzenia, wykonując jedną z tych czynności:

Ten przykładowy kod pokazuje, jak wdrożyć 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;
}

Ten przykładowy kod pokazuje, jak zarejestrować 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;
  }
});

Niezależnie od tego, czy korzystasz z widoku niestandardowego, czy rejestrujesz detektor, Twój widok otrzymuje MotionEvent ze współrzędnymi wskaźnika określającymi ruchy względne, np. delta X lub Y, podobnie jak współrzędne dostarczane przez kulkę. Aby pobrać współrzędne, możesz użyć właściwości getX() i getY().

Zwolnij zapis wskaźnika

Widok w aplikacji może zwolnić przechwycony wskaźnik przez wywołanie metody releasePointerCapture(), jak pokazano w tym przykładowym kodzie:

Kotlin

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

Java

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

System może odebrać przechwycony widok z widoku bez konieczności jawnego wywoływania funkcji releasePointerCapture(). Często dzieje się tak dlatego, że hierarchia widoków danych zawierająca widok, który obejmuje przechwytywanie żądań, traci fokus.