Android: onInterceptTouchEvent ve dispatchTouchEvent arasındaki fark nedir?


249

Arasındaki fark nedir onInterceptTouchEventve dispatchTouchEventAndroid'de?

Android geliştirici kılavuzuna göre, bir dokunma olayını ( MotionEvent) durdurmak için her iki yöntem de kullanılabilir , ancak fark nedir?

Nasıl onInterceptTouchEvent, dispatchTouchEventve onTouchEventGörünümler bir hiyerarşi (içinde etkileşimde birlikte ViewGroup)?

Yanıtlar:


272

Bunu demystify için en iyi yer kaynak kodudur. Belgeler bunu açıklamak konusunda ne yazık ki yetersizdir.

dispatchTouchEvent aslında Etkinlik, Görünüm ve Görünüm Grubu'nda tanımlanır. Dokunma olaylarını nasıl yönlendireceğine karar veren bir denetleyici olarak düşünün.

Örneğin, en basit durumda bunun View.dispatchTouchEvent birine dokunma olayı güzergahın OnTouchListener.onTouch o tanımlanmış veya uzatma yöntemi için eğer onTouchEvent .

İçin ViewGroup.dispatchTouchEvent şeyler yol daha karmaşık bulunmaktadır. Alt görünümlerinden hangisinin olayı alması gerektiğini bulması gerekir (child.dispatchTouchEvent öğesini çağırarak). Bu temel olarak, hangi alt görünümün sınırlayıcı dikdörtgeninin temas noktası koordinatlarını içerdiğini anladığınız bir çarpma testi algoritmasıdır.

Ancak, olayı uygun çocuk görünümüne göndermeden önce, ebeveyn olayı hep birlikte gözetleyebilir ve / veya araya alabilir. Bu nedir onInterceptTouchEvent için vardır. Bu nedenle, isabet testini yapmadan önce bu yöntemi çağırır ve olay kaçırılırsa (onInterceptTouchEvent'ten true değerini döndürerek) , çocuk olaylarına bir ACTION_CANCEL gönderir , böylece dokunma olayı işlemlerini (önceki dokunma olaylarından) ve daha sonra geri gönderebilirler. üst düzeydeki tüm dokunma olayları onTouchListener.onTouch (tanımlanmışsa) veya onTouchEvent () öğesine gönderilir . Ayrıca bu durumda, onInterceptTouchEvent bir daha asla çağrılmaz.

[Etkinlik | ViewGrup | Görünüm] .dispatchTouchEvent'i geçersiz kılmak ister misiniz? Eğer bazı özel yönlendirme yapmıyorsanız muhtemelen yapmamalısınız.

Üst düzeyde dokunma olayını casusluk ve / veya durdurmak istiyorsanız ana uzantı yöntemleri ViewGroup.onInterceptTouchEvent ve ana olay işleme için View.onTouchListener / View.onTouchEvent yöntemidir.

Sonuçta aşırı karmaşık tasarım imo ama android apis basitlikten daha fazla esnekliğe doğru eğilir.


10
Bu harika ve kısa bir cevap. Daha ayrıntılı bir örnek için, "Bir Görüntüleme Grubundaki Dokunma Olaylarını Yönetme"
TalkLittle

1
@numan salati: "Bundan sonra üst düzeydeki tüm dokunma olayları onTouchListener.onTouch'a gönderilir" - Görünüm grubumdaki gönderme dokunma yöntemini geçersiz kılmak ve olayları onTouchListener'a göndermesini istiyorum. Ama bunun nasıl yapılabileceğini anlamıyorum. Bunun için View.getOnTouchListener (). OnTouch () gibi bir api yoktur. Bir setOnTouchListener () yöntemi var, ancak getOnTouchListener () yöntemi yok. O zaman nasıl yapılabilir?
Ashwin

@ Düşüncelerim tam olarak, setOnInterceptTouchEvent de yok. Bir mizanpajda kullanmak / kodda eklemek için bir alt sınıf görünümünü geçersiz kılabilirsiniz, ancak parçayı / etkinliği kendi başına alt sınıfa ayırmadan bu görünümleri alt sınıflandıramayacağınız için, parça / etkinlik düzeyinde rootView'lerle uğraşamazsınız (çoğu uyumlulukta olduğu gibi) İlerleme Etkinliği uygulamaları). API, basitlik için bir setOnInterceptTouchEvent'e ihtiyaç duyar. Herkes yarı karmaşık bir uygulamada rootViews üzerinde interceptTouch kullanır
leRobot

244

Çünkü bu Google'daki ilk sonuç. Sizinle Dave Smith'ten Youtube'da harika bir Konuşma paylaşmak istiyorum : Android Dokunmatik Sisteminde ustalaşmak ve slaytları burada bulabilirsiniz . Bana Android Dokunmatik Sistemi hakkında derinlemesine bir anlayış kazandırdı:

Nasıl Etkinlik kolları dokunma:

  • Activity.dispatchTouchEvent()
    • Her zaman ilk aranan
    • Olayı, Pencereye bağlı kök görünümüne gönderir
    • onTouchEvent()
      • Etkinliği hiçbir görünüm tüketmezse çağrılır
      • Her zaman aranacak son kişi

Nasıl Görüntüle kolları dokunma:

  • View.dispatchTouchEvent()
    • Varsa, önce dinleyiciye etkinlik gönderir
      • View.OnTouchListener.onTouch()
    • Tükenmezse, dokunmanın kendisini işler
      • View.onTouchEvent()

Bir ViewGroup'un dokunuşları nasıl ele alınır:

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • Çocukların yerine geçip geçmeyeceğini kontrol edin
      • ACTION_CANCEL Aktif çocuğa geçer
      • Bir kez true değerini döndürürse, ViewGroupsonraki tüm olayları tüketir
    • Her alt görünüm için (ters sırada eklendiler)
      • Dokunma alakalı ise (iç görünüm), child.dispatchTouchEvent()
      • Bir önceki tarafından ele alınmazsa, bir sonraki görünüme gönder
    • Etkinliği hiçbir çocuk ele almazsa, dinleyici bir şans yakalar
      • OnTouchListener.onTouch()
    • Dinleyici yoksa veya ele alınmadıysa
      • onTouchEvent()
  • Yakalanan olaylar çocuk adımının üstünden atlar

Ayrıca, github.com/devunwired/ adresinde özel dokunma kodu örneği de sunar .

Yanıt: Temel olarak, a'nın devam eden bir jestle ilgilenip ilgilenmediğini belirlemek dispatchTouchEvent()için her Viewkatmanda çağrılır View. Bir ViewGroupde ViewGroup, çocuklara dispatchTouchEvent()çağrı yapmadan önce , yönteminde, dokunma olaylarını çalma yeteneğine sahiptir dispatchTouchEvent(). ViewGroupKeşke gönderilmesini dururdu ViewGroup onInterceptTouchEvent()kullanılan yöntem döner doğru. Fark yani dispatchTouchEvent()sevk edilir MotionEventsve onInterceptTouchEventbunun yolunu kesmek gerekiyorsa söyler (sevk değil MotionEventçocuklara) veya (çocuklara sevk) .

Bir ViewGroup kodunun az çok bunu yaptığını hayal edebilirsiniz (çok basitleştirilmiş):

public boolean dispatchTouchEvent(MotionEvent ev) {
    if(!onInterceptTouchEvent()){
        for(View child : children){
            if(child.dispatchTouchEvent(ev))
                return true;
        }
    }
    return super.dispatchTouchEvent(ev);
}

64

Tamamlayıcı Cevap

İşte diğer cevaplara bazı görsel ekler. Tam cevabım burada .

resim açıklamasını buraya girin

resim açıklamasını buraya girin

dispatchTouchEvent()Bir yöntem ViewGroupkullanımları onInterceptTouchEvent()hemen (ile dokunmatik olayını ele gerekip gerekmediğini seçmek için onTouchEvent()) ya da Bildirimde devam dispatchTouchEvent()alt öğelerinden yöntemlerle.


OnInterceptTouchEvent etkinlikte de çağrılabilir mi? Bence sadece bir ViewGroup'ta mümkün mü yoksa yanılıyorum? Suragch
Federico Rizzo

@FedericoRizzo, Haklısın! Çok teşekkür ederim! Diyagramı ve cevabımı güncelledim.
Suragch

20

Bu yöntemler hakkında çok fazla kafa karışıklığı var, ama aslında o kadar karmaşık değil. Karışıklıkların çoğu çünkü:

  1. Eğer senin View/ViewGroupalt öğelerinden veya herhangi gerçek in dönmek yok onTouchEvent, dispatchTouchEventve onInterceptTouchEventSADECE için çağrılır MotionEvent.ACTION_DOWN. Doğru bir onTouchEventgörünüm olmadan , üst görünümde görünümünüzün MotionEvents gerekmez.
  2. Bir ViewGroup öğesinin hiçbiri onTouchEvent öğesinde true değerine dönmediğinde, ViewGroup MotionEvent.ACTION_DOWNöğeniz true değerine dönse bile SADECE onInterceptTouchEvent çağrılır onTouchEvent.

Sipariş işleme şu şekildedir:

  1. dispatchTouchEvent denir.
  2. onInterceptTouchEventiçin MotionEvent.ACTION_DOWNveya ViewGroup öğelerinden herhangi biri true değerine döndüğünde çağrılır onTouchEvent.
  3. onTouchEventilk olarak ViewGroup'un alt öğelerinde çağrılır ve hiçbir çocuk doğru olmadığında bu öğeye View/ViewGroup.

TouchEvents/MotionEventsÇocuklarınızdaki olayları devre dışı bırakmadan önizleme yapmak istiyorsanız, iki şey yapmanız gerekir:

  1. dispatchTouchEventEtkinliği önizlemek ve geri dönmek için geçersiz kıl super.dispatchTouchEvent(ev);
  2. Geçersiz kılın onTouchEventve true değerini MotionEvent döndürün MotionEvent.ACTION_DOWN;

Kaydırma olayı gibi bazı hareketleri, hareketi algılamadığınız sürece çocuklarınızdaki diğer olayları devre dışı bırakmadan tespit etmek istiyorsanız, bunu şu şekilde yapabilirsiniz:

  1. MotionEvents'ı yukarıda açıklandığı gibi önizleyin ve hareketinizi algıladığınızda bir bayrak ayarlayın.
  2. onInterceptTouchEventBayrağınız, çocuklarınız tarafından MotionEvent işlemeyi iptal edecek şekilde ayarlandığında true değerini döndürün. Bu ayrıca bayrağınızı sıfırlamak için de uygun bir yerdir, çünkü onInterceptTouchEvent bir sonrakine kadar tekrar çağrılmaz MotionEvent.ACTION_DOWN.

Bir geçersiz kılmalara FrameLayoutörnek (Xamarin Android ile programlama yaptığım için C # örneğim, ancak Java'da mantık aynıdır):

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

3
Bunun neden daha fazla oyu olduğunu bilmiyorum. Bu iyi bir cevap (IMO) ve çok yararlı buldum.
Mark Ormesher

8

Bu web sayfasında http://doandroids.com/blogs/tag/codeexample/ çok sezgisel bir açıklama geldi . Oradan alınan:

  • boolean onTouchEvent (MotionEvent ev) - hedef olarak bu Görünüm ile bir dokunma olayı algılandığında çağrılır
  • boolean onInterceptTouchEvent (MotionEvent ev) - bu ViewGroup veya bunun bir alt öğesi ile hedef olarak bir dokunma olayı algılandığında çağrılır. Bu işlev true değerini döndürürse, MotionEvent durdurulur, yani alt öğeye iletilmez, bu görünümün onTouchEvent öğesine aktarılır.

2
soru onInterceptTouchEvent ve dispatchTouchEvent ile ilgilidir. Her ikisi de onTouchEvent öğesinden önce çağrılır. Ancak bu örnekte dispatchTouchEvent öğesini göremezsiniz.
Dayerman

8

dispatchTouchEvent, onInterceptTouchEvent öğesinden önce işler.

Bu basit örneği kullanarak:

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

Günlüğün aşağıdaki gibi olacağını görebilirsiniz:

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

Bu nedenle, bu 2 işleyiciyle çalışmanız durumunda, onInterceptTouchEvent öğesine gidecek olayı ilk kez işlemek için dispatchTouchEvent kullanın.

Başka bir fark, dispatchTouchEvent 'false' döndürürse, olayın alt öğeye yayılmaması, bu durumda EditText, onInterceptTouchEvent öğesinde false döndürürseniz, olay yine de EditText'e gönderilir


5

Kısa cevap: dispatchTouchEvent() adı verilecek ilk hepsinden.

Kısa öneri:dispatchTouchEvent() Kontrol edilmesi zor olduğu için geçersiz kılmamalı , bazen performansınızı yavaşlatabilir. IMHO, geçersiz kılmayı öneririm onInterceptTouchEvent().


Çoğu yanıt etkinlik / görünüm grubu / görünümündeki akış dokunma olayı hakkında oldukça açık bir şekilde bahsettiğinden, yalnızca ViewGroup(yoksayılıyor dispatchTouchEvent()) bu yöntemlerle ilgili kod hakkında daha fazla ayrıntı ekliyorum :

onInterceptTouchEvent()önce çağrılırsa, ACTION olayı sırasıyla aşağı -> taşı -> yukarı çağrılacaktır. 2 vaka var:

  1. Eğer varsa return false 3 olguda (ACTION_DOWN, ACTION_MOVE, ACTION_UP) 'de, o kadar dikkate alacaktır ebeveyn bu dokunmatik olayı gerekmez böylece, onTouch()asla çağırır ebeveynlerin , ancak onTouch()çocukların yerine arayacak ; ancak lütfen dikkat edin:

    • onInterceptTouchEvent()Hala sürece onun çocukları demiyorlar olarak, dokunmatik olayını almaya devam requestDisallowInterceptTouchEvent(true).
    • Bu etkinliği alan çocuk yoksa (2 durumda olabilir: kullanıcıların dokunduğu konumda hiçbir çocuk yoktur veya çocuklar yoktur, ancak ACTION_DOWN'da yanlış döner), ebeveynler bu etkinliği ebeveynlerine geri gönderir onTouch().
  2. Tam tersine, doğruya dönerseniz , ebeveyn bu dokunma etkinliğini hemen çalacak ve onInterceptTouchEvent()hemen duracak onTouch(), ebeveynler yerine çağrılacak ve tüm onTouch()çocuklar son eylem etkinliğini alacak - ACTION_CANCEL (böylece ebeveynler anlamına gelir) dokunma olayını çaldıysanız ve çocuklar bundan sonra başa çıkamaz). onInterceptTouchEvent()Dönüş yanlış akışı normaldir, ancak dönüş gerçek durumu ile küçük bir karışıklık vardır, bu yüzden burada listeliyorum:

    • onTouch()Ebeveynlerden ACTION_DOWN tarihinde true değerini döndürün, ebeveynlerden tekrar ACTION_DOWN ve sonraki işlemleri (ACTION_MOVE, ACTION_UP) alacaksınız.
    • onTouch()Ebeveynlerin ACTION_MOVE öğesinde true değerini döndürmek, ebeveynlerin bir sonraki ACTION_MOVE (aynı ACTION_MOVE girişi değil onInterceptTouchEvent()) ve sonraki işlemleri (ACTION_MOVE, ACTION_UP) alacaktır.
    • , ACTION_UP doğruydu Return onTouch()veliler edecek ait DEĞİL çok geç anne dokunmatik olayını çalmak için olduğundan hiç aradı.

Önemli olan bir şey daha onTouch(), görünümün o olaydan daha fazla işlem almak isteyip istemediğini belirleyecek etkinliğin ACTION_DOWN . Görünüm ACTION_DOWN konumunda doğru döndürülürse onTouch(), görünüm o olaydan daha fazla işlem almak istediğiniz anlamına gelir. Aksi takdirde, ACTION_DOWN konumunda false döndür onTouch(), görünümün bu etkinlikten başka işlem gerçekleştirmeyeceği anlamına gelir.



3

ViewGroup alt sınıfındaki aşağıdaki kod, üst kapsayıcılarının dokunma olaylarını almasını önler:

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

Arka plan görünümlerinin dokunma olaylarına yanıt vermesini önlemek için bunu özel bir kaplama ile kullandım.


1

Ana fark :

• Activity.dispatchTouchEvent (MotionEvent) - Bu, Etkinliğinizin tüm dokunma olaylarını pencereye gönderilmeden önce kesmesini sağlar.
• ViewGroup.onInterceptTouchEvent (MotionEvent) - Bu, ViewGroup'un olayları alt Views'a gönderildikçe izlemesini sağlar.


1
Evet, bu cevapları android dev rehberinden biliyorum - yine de belirsiz. DispatchTouchEvent yöntemi, yalnızca bir Etkinlik için değil, bir ViewGroup için de mevcuttur. Benim sorum, üç yöntem dispatchTouchEvent, onInterceptTouchEvent ve onTouchEvent, ViewGroups hiyerarşisi içinde, örneğin RelativeLayouts içinde nasıl etkileşime girdiği idi.
Anne Droid

1

ViewGroup's onInterceptTouchEvent()her zaman gerçekleşen ACTION_DOWNilk olayın giriş noktasıdır .

ViewGroup'un bu hareketi işlemesini istiyorsanız, true değerinden dönün onInterceptTouchEvent(). Gerçek dönen, ViewGroup en onTouchEvent()sonraki dek sonraki tüm olayları alacak ACTION_UPya ACTION_CANCELve de çoğu durumda, aralarında dokunmatik olayları ACTION_DOWNve ACTION_UPya ACTION_CANCELvardırACTION_MOVE normalde hareketleri fırlatmak / kaydırma olarak kabul edilecektir.

Eğer false değerinden döndürürseniz onInterceptTouchEvent(), hedef görünüm onTouchEvent()çağrılacaktır. Doğru iletiye dönene kadar sonraki iletiler için tekrarlanır onInterceptTouchEvent().

Kaynak: http://neevek.net/posts/2013/10/13/implementing-onInterceptTouchEvent-and-onTouchEvent-for-ViewGroup.html


0

Hem Activity hem de View yönteminin dispatchTouchEvent () ve onTouchEvent yöntemlerini vardır. ViewGroup da bu yöntemlere sahiptir, ancak onInterceptTouchEvent adlı başka bir yöntem vardır. Bu yöntemlerin dönüş türü boole'dir, gönderim yolunu dönüş değeri üzerinden kontrol edebilirsiniz.

Android'deki olay gönderimi Etkinlik-> Görünüm Grubu-> Görünüm'den başlar.


0
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume =false;
    if(onInterceptTouchEvent(ev){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
}

1
Bir açıklama ekleyebilir misiniz?
Paul Floyd

3
Bu kod snippet'i soruyu çözebilir, ancak bir açıklama da dahil olmak üzere , yayınınızın kalitesini artırmaya yardımcı olur. Gelecekte okuyucular için soruyu cevapladığınızı ve bu kişilerin kod önerinizin nedenlerini bilmeyebileceğini unutmayın.
Rosário Pereira Fernandes

-2

Küçük cevap:

onInterceptTouchEvent, setOnTouchListener öğesinden önce gelir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.