Śledź ruchy wskaźnika i dotyku

Wypróbuj metodę Compose
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. Dowiedz się, jak korzystać z dotyku i wpisywania w Compose.

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

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

Dotyk palcem nie zawsze jest najdokładniejszą formą interakcji, dlatego wykrywanie zdarzeń dotykowych często opiera się bardziej na ruchu niż na prostym kontakcie. Aby pomóc aplikacjom w odróżnianiu gestów opartych na ruchu (takich jak przesunięcie) od gestów nieopartych na ruchu (takich jak pojedyncze kliknięcie), Android wprowadza pojęcie odchylenia dotyku. Tolerancja dotyku to odległość w pikselach, o którą może się przesunąć dotyk użytkownika, zanim gest zostanie zinterpretowany jako gest oparty na ruchu. Więcej informacji na ten temat znajdziesz w artykule Zarządzanie zdarzeniami dotykowymi w obiekcie ViewGroup.

Ruch w geście można śledzić na kilka sposobów, w zależności od potrzeb aplikacji. Przykłady:

  • pozycję początkową i końcową wskaźnika, np. podczas przenoszenia obiektu na ekranie z punktu A do punktu B;
  • Kierunek, w którym porusza się wskaźnik, określony przez współrzędne X i Y.
  • Historia. Rozmiar historii gestów możesz sprawdzić, wywołując metodę MotionEvent getHistorySize(). Następnie możesz uzyskać pozycje, rozmiary, czas i nacisk każdego z wydarzeń historycznych, korzystając z metod getHistorical<Value> zdarzenia ruchu. Historia jest przydatna podczas renderowania śladu palca użytkownika, np. podczas rysowania za pomocą dotyku. Szczegółowe informacje znajdziesz w MotionEvent.
  • Szybkość przesuwania wskaźnika po ekranie dotykowym.

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

Prędkość ścieżki

Możesz używać gestów opartych na ruchu, które zależą od odległości lub kierunku, w jakim przesuwa się wskaźnik. Szybkość jest jednak często czynnikiem decydującym o śledzeniu charakterystyki gestu lub o tym, czy gest został wykonany. Aby ułatwić obliczanie prędkości, Android udostępnia klasę VelocityTracker. VelocityTracker pomaga śledzić szybkość zdarzeń dotyku. Jest to przydatne w przypadku gestów, w których prędkość jest częścią kryteriów, np. w przypadku szybkiego przesunięcia.

Oto przykład, który ilustruje przeznaczenie metod w interfejsie 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;
    }
}

Używanie przechwytywania wskaźnika

Niektóre aplikacje, takie jak gry, klienty pulpitu zdalnego i wirtualizacji, korzystają z możliwości sterowania wskaźnikiem myszy. Przechwytywanie wskaźnika to funkcja dostępna w Androidzie 8.0 (interfejs API na poziomie 26) i nowszych, która zapewnia tę kontrolę, przekazując wszystkie zdarzenia myszy do widoku w aplikacji, na którym jest fokus.

Żądanie przechwycenia wskaźnika

Widok w aplikacji może poprosić o przechwycenie wskaźnika tylko wtedy, gdy hierarchia widoków, która go zawiera, jest aktywna. Z tego powodu poproś o przechwycenie wskaźnika, gdy w widoku wystąpi określone działanie użytkownika, np. podczas zdarzenia onClick() lub w procedurze obsługi zdarzenia onWindowFocusChanged() w aktywności.

Aby poprosić o przechwycenie wskaźnika, wywołaj metodę requestPointerCapture() w widoku. Poniższy przykład kodu pokazuje, jak wysłać żądanie 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 zakończy się powodzeniem, Android wywoła funkcję onPointerCaptureChange(true). System dostarcza zdarzenia myszy do widoku, na którym jest fokus, w aplikacji, o ile znajduje się on w tej samej hierarchii widoków co widok, który zażądał przechwytywania. Inne aplikacje przestają otrzymywać zdarzenia myszy do momentu zwolnienia przechwytywania, w tym zdarzenia ACTION_OUTSIDE. Android dostarcza zdarzenia wskaźnika z innych źródeł niż mysz w normalny sposób, ale wskaźnik myszy nie jest już widoczny.

Obsługa przechwyconych zdarzeń wskaźnika

Gdy widokowi uda się przejąć przechwytywanie wskaźnika, Android dostarczy zdarzenia myszy. Widok skupiony może obsługiwać zdarzenia, wykonując jedno z tych zadań:

Poniższy przykład kodu pokazuje, jak zaimplementować 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ład kodu 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 używasz widoku niestandardowego, czy rejestrujesz odbiornik, Twój widok otrzymuje obiekt MotionEvent ze współrzędnymi wskaźnika, które określają ruchy względne, takie jak różnice w wartościach X lub Y, podobnie jak współrzędne dostarczane przez trackball. Współrzędne możesz pobrać za pomocą funkcji getX() i getY().

Zwalnianie przechwytywania wskaźnika

Widok w aplikacji może zwolnić przechwytywanie wskaźnika, wywołując funkcję releasePointerCapture(), jak pokazano w tym przykładzie kodu:

Kotlin

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

Java

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

System może usunąć przechwytywanie z widoku bez wyraźnego wywołania przez Ciebie funkcji releasePointerCapture(), zwykle dlatego, że hierarchia widoków zawierająca widok, który prosi o przechwytywanie, traci fokus.