Android: Różnica między onInterceptTouchEvent i dispatchTouchEvent?

249

Jaka jest różnica między onInterceptTouchEventi dispatchTouchEventna Androidzie?

Zgodnie z przewodnikiem dla programistów Androida, obydwu metod można użyć do przechwycenia zdarzenia dotyku ( MotionEvent), ale jaka jest różnica?

Jak zrobić onInterceptTouchEvent, dispatchTouchEventi onTouchEventwspółdziałają ze sobą w ramach hierarchii widoki ( ViewGroup)?

Anne Droid
źródło

Odpowiedzi:

272

Najlepszym miejscem do odkrycia tego jest kod źródłowy. Dokumenty są bardzo nieodpowiednie w wyjaśnianiu tego.

dispatchTouchEvent jest faktycznie zdefiniowany w Activity, View i ViewGroup. Pomyśl o tym jak o kontrolerze, który decyduje, jak kierować zdarzenia dotykowe.

Na przykład najprostszym przypadkiem jest View.dispatchTouchEvent, który przekieruje zdarzenie touch do OnTouchListener.onTouch, jeśli jest zdefiniowany, lub do metody rozszerzenia onTouchEvent .

W przypadku ViewGroup.dispatchTouchEvent rzeczy są znacznie bardziej skomplikowane. Musi dowiedzieć się, który z jego widoków potomnych powinien otrzymać zdarzenie (wywołując child.dispatchTouchEvent). Jest to w zasadzie algorytm testowania trafień, w którym określa się, który prostokąt obwiedni widoku potomnego zawiera współrzędne punktu styku.

Ale zanim zdoła wywołać zdarzenie do odpowiedniego widoku potomnego, rodzic może szpiegować i / lub przechwytywać zdarzenia razem. Właśnie po to jest onInterceptTouchEvent . Wywołuje więc tę metodę najpierw przed wykonaniem testu trafień, a jeśli zdarzenie zostało przejęte (przez zwrócenie wartości true z onInterceptTouchEvent), wysyła ACTION_CANCEL do widoków potomnych, aby mogły zrezygnować z przetwarzania zdarzenia dotyku (z poprzednich zdarzeń dotyku), a następnie wszystkie zdarzenia dotykowe na poziomie nadrzędnym są wysyłane do onTouchListener.onTouch (jeśli zdefiniowano) lub onTouchEvent (). Również w takim przypadku onInterceptTouchEvent nigdy nie jest wywoływany ponownie.

Czy chciałbyś nawet zastąpić [Aktywność | Grupa widoków | Widok] .dispatchTouchEvent? O ile nie wykonujesz niestandardowego routingu, prawdopodobnie nie powinieneś.

Głównymi metodami rozszerzenia są ViewGroup.onInterceptTouchEvent, jeśli chcesz szpiegować i / lub przechwytywać zdarzenia dotykowe na poziomie nadrzędnym oraz View.onTouchListener / View.onTouchEvent do obsługi zdarzeń głównych.

Podsumowując, jego zbyt skomplikowana konstrukcja imo, ale Android api bardziej skłania się ku elastyczności niż prostocie.

Numan Salati
źródło
10
To świetna, zwięzła odpowiedź. Bardziej szczegółowy przykład można znaleźć w szkoleniu „Zarządzanie zdarzeniami dotykowymi w grupie widokowej”
TalkLittle,
1
@numan salati: „od tego momentu wszystkie zdarzenia dotykowe na poziomie nadrzędnym są wysyłane do onTouchListener.onTouch” - chcę zastąpić metodę dotykową dispatch w mojej grupie widoków i sprawić, aby wywołała zdarzenia do onTouchListener. Ale nie rozumiem, jak można to zrobić. Nie ma api dla takich jak View.getOnTouchListener (). OnTouch (). Istnieje metoda setOnTouchListener (), ale nie ma metody getOnTouchListener (). Jak to zrobić?
Ashwin,
@Ashwin moje myśli dokładnie, nie ma też setOnInterceptTouchEvent. Możesz zastąpić widok podklasy do użycia w układzie / dodać go do kodu, ale nie możesz zadzierać z fragmentami root / na poziomie działania, ponieważ nie możesz podklasować tych widoków bez podklasy samego fragmentu / działania (jak w większości kompatybilności Implementacje ProgressActivitiy). Interfejs API wymaga setOnInterceptTouchEvent dla uproszczenia. Wszyscy używają interceptTouch na rootViews w pewnym momencie w częściowo złożonej aplikacji
leRobot
244

Ponieważ jest to pierwszy wynik w Google. Chcę podzielić się z Tobą wspaniałą rozmową Dave'a Smitha na Youtube: Opanowanie systemu dotykowego Android, a slajdy są dostępne tutaj . Dzięki temu dobrze zrozumiałem system dotykowy Android:

Jak obsługuje działanie :

  • Activity.dispatchTouchEvent()
    • Zawsze jako pierwszy
    • Wysyła zdarzenie do widoku głównego dołączonego do okna
    • onTouchEvent()
      • Wywoływany, jeśli żadne zdarzenie nie pochłania zdarzenia
      • Zawsze ostatni, aby zostać powołanym

Jak dotykają widoków :

  • View.dispatchTouchEvent()
    • Najpierw wysyła zdarzenie do detektora, jeśli istnieje
      • View.OnTouchListener.onTouch()
    • Jeśli nie zostanie zużyty, przetwarza sam dotyk
      • View.onTouchEvent()

Jak obsługuje się ViewGroup :

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • Sprawdź, czy powinno to zastąpić dzieci
      • Przechodzi ACTION_CANCEL na aktywne dziecko
      • Jeśli zwróci true raz, ViewGroupzużywa wszystkie kolejne zdarzenia
    • Dla każdego widoku potomnego (w odwrotnej kolejności zostały dodane)
      • Jeśli dotyk jest istotny (widok od wewnątrz), child.dispatchTouchEvent()
      • Jeśli nie jest to obsługiwane przez poprzedni, przejdź do następnego widoku
    • Jeśli żadne dziecko nie zajmie się wydarzeniem, słuchacz ma szansę
      • OnTouchListener.onTouch()
    • Jeśli nie ma nasłuchiwacza lub nie jest obsługiwany
      • onTouchEvent()
  • Przechwycone zdarzenia przeskakują krok dziecka

Podaje również przykładowy kod niestandardowego dotyku na github.com/devunwired/ .

Odpowiedź: Zasadniczo dispatchTouchEvent()jest wywoływany na każdej Viewwarstwie, aby ustalić, czy Viewzainteresowany jest w trwającym geście. W sposób ma zdolność do kradzieży zdarzenia dotykowe w swoim -method, zanim byłoby zadzwonić na dzieci. Zatrzyma tylko Dysponowanie jeśli -method zwraca true. Różnicą jest to, że jest dysponowanie i powie czy powinien przechwycić (nie wysyłającej do dzieci) , czy nie (wysyłając do dzieci) .ViewGroupViewGroupdispatchTouchEvent()dispatchTouchEvent()ViewGroupViewGroup onInterceptTouchEvent()dispatchTouchEvent()MotionEventsonInterceptTouchEventMotionEvent

Można sobie wyobrazić kod grupy ViewGroup wykonującej mniej więcej to (bardzo uproszczone):

public boolean dispatchTouchEvent(MotionEvent ev) {
    if(!onInterceptTouchEvent()){
        for(View child : children){
            if(child.dispatchTouchEvent(ev))
                return true;
        }
    }
    return super.dispatchTouchEvent(ev);
}
seb
źródło
64

Dodatkowa odpowiedź

Oto kilka wizualnych uzupełnień innych odpowiedzi. Moja pełna odpowiedź jest tutaj .

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

dispatchTouchEvent()Metoda ciągu ViewGroupzastosowań onInterceptTouchEvent()zdecydować, czy powinien on natychmiast obsłużyć zdarzenia dotykowy (z onTouchEvent()) lub kontynuować powiadamiając dispatchTouchEvent()metod jej dzieci.

Suragch
źródło
Czy onInterceptTouchEvent można wywołać także w działaniu ?? Myślę, że jest to możliwe tylko w grupie ViewGroup, czy się mylę? @Suragch
Federico Rizzo
@FedericoRizzo, masz rację! Dziękuję Ci bardzo! Zaktualizowałem schemat i swoją odpowiedź.
Suragch,
20

Istnieje wiele nieporozumień na temat tych metod, ale tak naprawdę nie jest to tak skomplikowane. Większość zamieszania wynika z tego, że:

  1. Jeśli urządzenie View/ViewGrouplub któregokolwiek z jej dzieci nie zwracają prawdziwe w onTouchEvent, dispatchTouchEventi onInterceptTouchEventbędzie tylko wezwał MotionEvent.ACTION_DOWN. Bez wartości true onTouchEventwidok nadrzędny zakłada, że ​​widok nie potrzebuje MotionEvent.
  2. Gdy żadne z elementów potomnych grupy ViewGroup nie zwróci wartości true w zdarzeniu onTouchEvent, funkcja ONInterceptTouchEvent zostanie WYŁĄCZNIE wywołana MotionEvent.ACTION_DOWN, nawet jeśli grupa ViewGroup zwróci wartość true onTouchEvent.

Kolejność przetwarzania wygląda następująco:

  1. dispatchTouchEvent jest nazywany.
  2. onInterceptTouchEventjest wywoływany MotionEvent.ACTION_DOWNlub gdy którekolwiek z dzieci grupy ViewGroup zwróciło wartość true onTouchEvent.
  3. onTouchEventjest wywoływany po raz pierwszy przez dzieci grupy ViewGroup, a gdy żadne z dzieci nie zwraca wartości true, jest wywoływane w View/ViewGroup.

Jeśli chcesz wyświetlić podgląd TouchEvents/MotionEventsbez wyłączania wydarzeń na swoich dzieciach, musisz zrobić dwie rzeczy:

  1. Zastąp, dispatchTouchEventaby wyświetlić podgląd zdarzenia i powrócić super.dispatchTouchEvent(ev);
  2. Zastąp onTouchEventi zwróć wartość true, w przeciwnym razie nie otrzymasz żadnych MotionEvent oprócz MotionEvent.ACTION_DOWN.

Jeśli chcesz wykryć jakiś gest, na przykład przesunięcie, bez wyłączania innych zdarzeń na swoich dzieciach, o ile nie wykryłeś tego gestu, możesz to zrobić w następujący sposób:

  1. Wyświetl podgląd MotionEvent zgodnie z powyższym opisem i ustaw flagę po wykryciu gestu.
  2. Zwraca wartość true, onInterceptTouchEventgdy flaga jest ustawiona na anulowanie przetwarzania MotionEvent przez dzieci. Jest to również wygodne miejsce do zresetowania flagi, ponieważ onInterceptTouchEvent nie będzie wywoływany ponownie aż do następnego MotionEvent.ACTION_DOWN.

Przykład przesłonięcia w FrameLayout(mój przykład w to C #, ponieważ programuję w Xamarin Android, ale logika jest taka sama w Javie):

public override bool DispatchTouchEvent(MotionEvent e)
{
    // Preview the touch event to detect a swipe:
    switch (e.ActionMasked)
    {
        case MotionEventActions.Down:
            _processingSwipe = false;
            _touchStartPosition = e.RawX;
            break;
        case MotionEventActions.Move:
            if (!_processingSwipe)
            {
                float move = e.RawX - _touchStartPosition;
                if (move >= _swipeSize)
                {
                    _processingSwipe = true;
                    _cancelChildren = true;
                    ProcessSwipe();
                }
            }
            break;
    }
    return base.DispatchTouchEvent(e);
}

public override bool OnTouchEvent(MotionEvent e)
{
    // To make sure to receive touch events, tell parent we are handling them:
    return true;
}

public override bool OnInterceptTouchEvent(MotionEvent e)
{
    // Cancel all children when processing a swipe:
    if (_cancelChildren)
    {
        // Reset cancel flag here, as OnInterceptTouchEvent won't be called until the next MotionEventActions.Down:
        _cancelChildren = false;
        return true;
    }
    return false;
}
Marcel W.
źródło
3
Nie wiem, dlaczego to nie ma więcej pozytywnych opinii. To dobra odpowiedź (IMO) i uważam ją za bardzo pomocną.
Mark Ormesher,
8

Natknąłem się na bardzo intuicyjne wyjaśnienia na tej stronie http://doandroids.com/blogs/tag/codeexample/ . Zaczerpnięty z:

  • boolean onTouchEvent (MotionEvent ev) - wywoływany za każdym razem, gdy zostanie wykryte zdarzenie dotykowe z tym widokiem jako celem
  • boolean onInterceptTouchEvent (MotionEvent ev) - wywoływany za każdym razem, gdy zostanie wykryte zdarzenie dotykowe w tej grupie ViewGroup lub jej obiekcie podrzędnym jako celu. Jeśli ta funkcja zwróci wartość true, obiekt MotionEvent zostanie przechwycony, co oznacza, że ​​nie zostanie przekazany do elementu podrzędnego, ale raczej do elementu onTouchEvent tego widoku.
Krzysztow
źródło
2
pytanie dotyczy onInterceptTouchEvent i dispatchTouchEvent. Oba są wywoływane przed onTouchEvent. Ale w tym przykładzie nie można zobaczyć dispatchTouchEvent.
Dayerman
8

Uchwyty dispatchTouchEvent przed onInterceptTouchEvent.

Korzystając z tego prostego przykładu:

   main = new LinearLayout(this){
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            System.out.println("Event - onInterceptTouchEvent");
            return super.onInterceptTouchEvent(ev);
            //return false; //event get propagated
        }
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            System.out.println("Event - dispatchTouchEvent");
            return super.dispatchTouchEvent(ev);
            //return false; //event DONT get propagated
        }
    };

    main.setBackgroundColor(Color.GRAY);
    main.setLayoutParams(new LinearLayout.LayoutParams(320,480));    


    viewA = new EditText(this);
    viewA.setBackgroundColor(Color.YELLOW);
    viewA.setTextColor(Color.BLACK);
    viewA.setTextSize(16);
    viewA.setLayoutParams(new LinearLayout.LayoutParams(320,80));
    main.addView(viewA);

    setContentView(main);

Widać, że dziennik będzie wyglądał następująco:

I/System.out(25900): Event - dispatchTouchEvent
I/System.out(25900): Event - onInterceptTouchEvent

Więc jeśli pracujesz z tymi dwoma modułami obsługi, użyj dispatchTouchEvent do obsługi zdarzenia w pierwszej instancji, które przejdzie do onInterceptTouchEvent.

Inna różnica polega na tym, że jeśli dispatchTouchEvent zwróci „fałsz”, zdarzenie nie zostanie propagowane do dziecka, w tym przypadku EditText, natomiast jeśli zwrócisz false w onInterceptTouchEvent, zdarzenie nadal zostanie wysłane do EditText

Dayerman
źródło
5

Krótka odpowiedź: dispatchTouchEvent() zostanie wywołana przede wszystkim.

Krótka rada: nie powinna zastępować, dispatchTouchEvent()ponieważ jest trudna do kontrolowania, czasami może spowolnić działanie. IMHO, sugeruję zastąpienie onInterceptTouchEvent().


Ponieważ większość odpowiedzi wspomina dość wyraźnie o zdarzeniu przepływu dotykowego w działaniu / grupie widoków / widoku, dodaję tylko więcej szczegółów na temat kodu tych metod w ViewGroup(ignorując dispatchTouchEvent()):

onInterceptTouchEvent()zostanie wywołany jako pierwszy, zdarzenie ACTION zostanie wywołane odpowiednio w dół -> ruch -> w górę. Istnieją 2 przypadki:

  1. Jeśli zwrócisz fałsz w 3 przypadkach (ACTION_DOWN, ACTION_MOVE, ACTION_UP), uzna, że rodzic nie będzie potrzebował tego zdarzenia dotyku , więc onTouch()rodzice nigdy nie onTouch()dzwonią , ale dzieci dzwonią ; jednak proszę zauważyć:

    • onInterceptTouchEvent()Nadal otrzymywać zdarzenia dotykowego, tak długo, jak jej dzieci nie zadzwonić requestDisallowInterceptTouchEvent(true).
    • Jeśli nie ma dzieci otrzymujących to zdarzenie (może się to zdarzyć w 2 przypadkach: brak dzieci w pozycji, której dotykają użytkownicy, lub są dzieci, ale zwraca wartość false w ACTION_DOWN), rodzice odeślą to zdarzenie z powrotem do onTouch()rodziców.
  2. Odwrotnie, jeśli zwrócisz wartość true , rodzic natychmiast wykradnie to zdarzenie dotykowe i onInterceptTouchEvent()natychmiast się zatrzyma, zamiast onTouch()wezwać rodziców, a wszystkie onTouch()dzieci otrzymają ostatnie zdarzenie akcji - ACTION_CANCEL (oznacza to, że rodzice ukradł zdarzenie dotykowe, a dzieci nie mogą sobie z tym poradzić). Przepływ onInterceptTouchEvent()zwracanej wartości false jest normalny, ale istnieje niewielkie zamieszanie w przypadku zwracanej wartości true, więc wymienię to tutaj:

    • Powrót prawda ACTION_DOWN, onTouch()z rodziców otrzyma ACTION_DOWN ponownie i następujące działania (ACTION_MOVE, ACTION_UP).
    • Zwróć wartość true w ACTION_MOVE, onTouch()a rodzice otrzymają kolejne ACTION_MOVE (nie to samo ACTION_MOVE w onInterceptTouchEvent()) i kolejne działania (ACTION_MOVE, ACTION_UP).
    • Zwróć wartość true w ACTION_UP, onTouch()ponieważ rodzice NIE zadzwonią wcale, ponieważ jest za późno, aby rodzice wykradli zdarzenie dotykowe.

Jeszcze jedna ważna rzecz to ACTION_DOWN zdarzenia, onTouch()które określi, czy widok chce otrzymać więcej akcji z tego wydarzenia, czy nie. Jeśli widok zwróci wartość true przy ACTION_DOWN w onTouch(), oznacza to, że widok jest skłonny otrzymać więcej akcji z tego zdarzenia. W przeciwnym razie wartość false przy ACTION_DOWN w onTouch()oznacza, że ​​widok nie otrzyma żadnej akcji z tego zdarzenia.

Nguyen Tan Dat
źródło
3

Poniższy kod w podklasie ViewGroup zapobiegnie odbiorze zdarzeń dotykowych przez kontenery nadrzędne:

  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    // Normal event dispatch to this container's children, ignore the return value
    super.dispatchTouchEvent(ev);

    // Always consume the event so it is not dispatched further up the chain
    return true;
  }

Użyłem tego z niestandardową nakładką, aby zapobiec reagowaniu widoków tła na zdarzenia dotykowe.

James Wald
źródło
1

Podstawowa różnica :

• Activity.dispatchTouchEvent (MotionEvent) - Pozwala to Twojemu działaniu przechwytywać wszystkie zdarzenia dotykowe, zanim zostaną wysłane do okna.
• ViewGroup.onInterceptTouchEvent (MotionEvent) - Pozwala to ViewGroup oglądać zdarzenia, które są wysyłane do widoków potomnych.

ChapMic
źródło
1
Tak, znam te odpowiedzi z przewodnika dla programistów Androida - jednak niejasne. Metoda dispatchTouchEvent istnieje również dla grupy ViewGroup nie tylko dla działania. Moje pytanie dotyczyło tego, w jaki sposób trzy metody dispatchTouchEvent, onInterceptTouchEvent i onTouchEvent oddziałują ze sobą w ramach hierarchii grup ViewGroups, np. RelativeLayouts.
Anne Droid,
1

ViewGroup's onInterceptTouchEvent()jest zawsze punktem wejścia dla ACTION_DOWNzdarzenia, które jest pierwszym zdarzeniem, które ma miejsce.

Jeśli chcesz, aby ViewGroup przetworzyło ten gest, zwróć true z onInterceptTouchEvent(). Po powrocie prawda, ViewGroup na onTouchEvent()otrzyma wszystkie kolejne wydarzenia aż do następnego ACTION_UPlub ACTION_CANCEL, w większości przypadków, zdarzeń dotykowych pomiędzy ACTION_DOWNi ACTION_UPczy ACTION_CANCELto ACTION_MOVE, co zazwyczaj uznawane za przewijanie / rzucać gesty.

Jeśli zwrócisz false z onInterceptTouchEvent(), widok docelowy onTouchEvent()zostanie wywołany. Będzie powtarzane dla kolejnych wiadomości, dopóki nie zwrócisz wartości true onInterceptTouchEvent().

Źródło: http://neevek.net/posts/2013/10/13/implementing-onInterceptTouchEvent-and-onTouchEvent-for-ViewGroup.html

wipsy
źródło
0

Zarówno działanie, jak i widok mają metody dispatchTouchEvent () i onTouchEvent. ViewGroup ma również te metody, ale ma inną metodę o nazwie onInterceptTouchEvent. Typy zwracane przez te metody są logiczne, można kontrolować trasę wysyłki za pomocą wartości zwracanej.

Wysłanie zdarzenia w Androidzie zaczyna się od Activity-> ViewGroup-> View.

Ryan
źródło
0
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume =false;
    if(onInterceptTouchEvent(ev){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
}
Mc_fool_himself
źródło
1
Czy możesz dodać jakieś wyjaśnienie?
Paul Floyd,
3
Ten fragment kodu może rozwiązać pytanie, ale wyjaśnienie naprawdę pomaga poprawić jakość posta. Pamiętaj, że w przyszłości odpowiadasz na pytanie dla czytelników, a ci ludzie mogą nie znać przyczyn Twojej sugestii kodu.
Rosário Pereira Fernandes
-2

Mała odpowiedź:

OnInterceptTouchEvent występuje przed setOnTouchListener.

sagits
źródło