Ś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 korzystać z dotyku i wprowadzania w sekcji Utwórz

Z tej lekcji dowiesz się, jak śledzić ruch w przypadku zdarzeń dotyku.

Za każdym razem, gdy zmienia się bieżąca pozycja kontaktu, naciskanie lub rozmiar kontaktu dotykowego, wywoływany jest nowy obiekt onTouchEvent() wraz ze zdarzeniem ACTION_MOVE. Jak opisano w sekcji Wykrywanie typowych gestów, wszystkie te zdarzenia są rejestrowane w parametrze MotionEvent onTouchEvent().

Ponieważ dotyk palców nie zawsze jest najbardziej precyzyjną formą interakcji, wykrywanie zdarzeń dotknięcia często opiera się na ruchu, a nie na prostym kontakcie. Aby ułatwić aplikacjom odróżnienie gestów opartych na ruchu (np. przesuwania) od gestów niezwiązanych z ruchem (np. pojedynczego kliknięcia), Android stosuje m.in. pojęcie płynięcia dotykiem. Sygnalizowanie dotknięcia to odległość wyrażona w pikselach, na której może się przemieszczać użytkownik dotknięcia, 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.

W zależności od potrzeb aplikacji można śledzić ruch na kilka sposobów. Oto przykłady:

  • Pozycja początkowa i końcowa wskaźnika, na przykład 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> zdarzeń ruchu, możesz następnie ustalić położenie, rozmiar, czas i siłę nacisku każdego zdarzenia historycznego. Historia jest przydatna podczas renderowania śladu palca użytkownika, na przykład przy rysowaniu dotykiem. Szczegółowe informacje znajdziesz w dokumentacji MotionEvent.
  • Prędkość wskaźnika poruszającego się po ekranie dotykowym.

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

Śledź prędkość

Możesz wykonać gest oparty na ruchu oparty na odległości lub kierunku, którym porusza się wskaźnik. Jednak często jest ona czynnikiem decydującym o śledzeniu właściwości gestu lub podejmowaniu decyzji, czy został on wykonany. Aby ułatwić obliczanie szybkości, Android udostępnia klasę VelocityTracker. VelocityTracker pomaga śledzić szybkość zdarzeń dotyku. Przydaje się to w przypadku gestów, w przypadku których jednym z kryteriów gestu jest prędkość, np. rzut.

Oto przykład, który ilustruje 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żyj przechwytywania wskaźnika

Niektóre aplikacje, takie jak gry, pulpit zdalny i klienty wirtualizacji, czerpią korzyści z kontroli nad wskaźnikiem myszy. Przechwytywanie wskaźników to funkcja dostępna w Androidzie 8.0 (poziom interfejsu API 26) i nowszych, która zapewnia tę kontrolę, wyświetlając wszystkie zdarzenia myszy w widocznym obszarze aplikacji.

Przechwytywanie wskaźnika żądania

Widok w aplikacji może żądać przechwytywania wskaźnika tylko wtedy, gdy hierarchia widoków, która go zawiera, jest zaznaczona. Dlatego wysyłaj żądanie przechwytywania wskaźnika, gdy w widoku występuje określone działanie użytkownika, np. podczas zdarzenia onClick() lub w module obsługi zdarzeń onWindowFocusChanged() w Twojej aktywności.

Aby zażądać przechwycenia 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 widoku aktywnego w aplikacji, o ile znajduje się on w tej samej hierarchii widoków co widok, który zażądał przechwycenia. Do czasu opublikowania zapisu inne aplikacje, w tym zdarzenia ACTION_OUTSIDE, przestaną otrzymywać zdarzenia myszy. Android w zwykły sposób przesyła zdarzenia wskaźnika ze źródeł innych niż mysz, ale wskaźnik myszy nie jest już widoczny.

Obsługa zarejestrowanych zdarzeń wskaźnika

Gdy widok uzyska przechwycony wskaźnik, Android dostarczy zdarzenia myszy. Zaznaczony widok może obsługiwać zdarzenia, wykonując jedno z tych zadań:

Poniższy 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;
}

Poniższy 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, które określają ruchy względne, takie jak delta X lub Y, podobne do współrzędnych dostarczanych przez kulkę. Współrzędne możesz pobrać za pomocą funkcji getX() i getY().

Przechwytywanie wskaźnika wersji

Widok w aplikacji może uruchamiać przechwytywanie wskaźnika, wywołując metodę 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 cofnąć przechwycony widok bez Twojego wywołania releasePointerCapture(). Zwykle jest to spowodowane tym, że hierarchia widoków zawierająca widok, który przechwytywano żądania, przestaje być aktywny.