Android: Perbedaan antara onInterceptTouchEvent dan dispatchTouchEvent?

249

Apa perbedaan antara onInterceptTouchEventdan dispatchTouchEventdi Android?

Menurut panduan pengembang android, kedua metode dapat digunakan untuk mencegat acara sentuh ( MotionEvent), tetapi apa bedanya?

Bagaimana caranya onInterceptTouchEvent, dispatchTouchEventdan onTouchEventberinteraksi bersama dalam hierarki Views ( ViewGroup)?

Anne Droid
sumber

Jawaban:

272

Tempat terbaik untuk menghilangkan mitos ini adalah kode sumber. Dokumen itu sangat tidak memadai untuk menjelaskan hal ini.

dispatchTouchEvent sebenarnya didefinisikan pada Activity, View, dan ViewGroup. Anggap saja sebagai pengontrol yang memutuskan cara mengarahkan acara sentuh.

Misalnya, kasus paling sederhana adalah pada View.dispatchTouchEvent yang akan merutekan acara sentuh ke OnTouchListener.onTouch jika ditentukan atau ke metode ekstensi onTouchEvent .

Untuk ViewGroup.dispatchTouchEvent, berbagai hal jauh lebih rumit. Perlu mengetahui yang mana dari salah satu pandangan anaknya yang harus mendapatkan acara (dengan memanggil child.dispatchTouchEvent). Ini pada dasarnya adalah algoritma pengujian hit di mana Anda mengetahui persegi panjang pembatas tampilan anak mana yang berisi koordinat titik sentuh.

Tetapi sebelum dapat mengirimkan acara ke tampilan anak yang sesuai, orang tua dapat memata-matai dan / atau mencegat acara tersebut bersama-sama. Inilah yang ada di dalamInterceptTouchEvent . Jadi ia memanggil metode ini terlebih dahulu sebelum melakukan pengujian hit dan jika acara tersebut dibajak (dengan mengembalikan true dari onInterceptTouchEvent) mengirimkan ACTION_CANCEL ke tampilan anak sehingga mereka dapat meninggalkan pemrosesan acara sentuh mereka (dari acara sentuh sebelumnya) dan sejak saat itu dan seterusnya semua acara sentuh di tingkat induk dikirim ke onTouchListener.onTouch (jika ditentukan) atau onTouchEvent (). Juga dalam kasus itu, onInterceptTouchEvent tidak pernah dipanggil lagi.

Apakah Anda bahkan ingin menimpa [Activity | ViewGroup | View] .dispatchTouchEvent? Kecuali Anda melakukan perutean kustom, Anda mungkin tidak seharusnya.

Metode ekstensi utama adalah ViewGroup.onInterceptTouchEvent jika Anda ingin memata-matai dan / atau mencegat acara sentuh di tingkat induk dan View.onTouchListener / View.onTouchEvent untuk penanganan acara utama.

Semua dalam semua desain imo yang terlalu rumit tetapi apis android lebih condong ke arah fleksibilitas daripada kesederhanaan.

salati numan
sumber
10
Ini jawaban yang bagus dan singkat. Untuk contoh yang lebih terperinci, lihat pelatihan "Mengelola Acara Sentuhan di dalam ViewGroup"
TalkLittle
1
@numan salati: "sejak saat itu dan seterusnya semua acara sentuh di tingkat induk dikirim ke onTouchListener.onTouch" - Saya ingin mengganti metode sentuh pengiriman dalam grup tampilan saya dan membuatnya mengirimkan acara ke onTouchListener. Tetapi saya tidak melihat bagaimana hal itu dapat dilakukan. Tidak ada api untuk itu seperti View.getOnTouchListener (). OnTouch (). Ada metode setOnTouchListener () tetapi tidak ada metode getOnTouchListener (). Bagaimana itu bisa dilakukan?
Ashwin
@Ashwin pikiranku persis, tidak ada setOnInterceptTouchEvent baik. Anda dapat Mengganti tampilan subkelas untuk digunakan dalam tata letak / menambahkannya dalam kode, tetapi Anda tidak bisa dipusingkan dengan fragmen / root level aktivitasDilihat, karena Anda tidak bisa mensubklasifikasikan tampilan tersebut tanpa mensubklasifikasikan fragmen / aktivitas itu sendiri (seperti pada sebagian besar kompatibilitas Implementasi ProgressActivitiy). API membutuhkan setOnInterceptTouchEvent untuk kesederhanaan. Semua orang menggunakan interceptTouch pada rootViews di beberapa titik di App semi-kompleks
leRobot
244

Karena ini adalah hasil pertama di Google. Saya ingin berbagi dengan Anda Talk yang hebat oleh Dave Smith di Youtube: Menguasai Sistem Android Touch dan slide tersedia di sini . Ini memberi saya pemahaman mendalam tentang Sistem Sentuh Android:

Cara Aktivitas menangani sentuhan:

  • Activity.dispatchTouchEvent()
    • Selalu yang pertama dipanggil
    • Mengirim acara untuk me-root tampilan yang dilampirkan ke Jendela
    • onTouchEvent()
      • Disebut jika tidak ada tampilan yang mengkonsumsi acara tersebut
      • Selalu disebut terakhir

Bagaimana View menangani sentuhan:

  • View.dispatchTouchEvent()
    • Kirim acara ke pendengar terlebih dahulu, jika ada
      • View.OnTouchListener.onTouch()
    • Jika tidak dikonsumsi, proses sentuhan itu sendiri
      • View.onTouchEvent()

Cara ViewGroup menangani sentuhan:

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • Periksa apakah harus menggantikan anak-anak
      • Lulus ACTION_CANCEL ke anak aktif
      • Jika kembali benar sekali, ViewGroupmenghabiskan semua peristiwa berikutnya
    • Untuk setiap tampilan anak (dalam urutan terbalik, mereka ditambahkan)
      • Jika sentuhan relevan (tampilan dalam), child.dispatchTouchEvent()
      • Jika tidak ditangani oleh sebelumnya, kirim ke tampilan berikutnya
    • Jika tidak ada anak yang menangani acara tersebut, pendengar mendapat kesempatan
      • OnTouchListener.onTouch()
    • Jika tidak ada pendengar, atau tidak ditangani
      • onTouchEvent()
  • Peristiwa dicegat melompati langkah anak

Dia juga memberikan contoh kode sentuhan khusus pada github.com/devunwired/ .

Jawaban: Pada dasarnya dispatchTouchEvent()ini dipanggil pada setiap Viewlayer untuk menentukan apakah a Viewtertarik pada gerakan yang sedang berlangsung. Dalam ViewGroupsatu ViewGroupmemiliki kemampuan untuk mencuri peristiwa sentuhan dalam bukunya dispatchTouchEvent()-metode, sebelum itu akan memanggil dispatchTouchEvent()pada anak-anak. Hanya ViewGroupakan menghentikan pengiriman jika ViewGroup onInterceptTouchEvent()-metode mengembalikan true. The perbedaan adalah bahwa dispatchTouchEvent()akan mengirim MotionEventsdan onInterceptTouchEventmengatakan jika itu harus mencegat (tidak pengiriman yang MotionEventuntuk anak-anak) atau tidak (pengiriman kepada anak-anak) .

Anda dapat membayangkan kode dari suatu ViewGroup melakukan lebih atau kurang ini (sangat disederhanakan):

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

Jawaban Tambahan

Berikut adalah beberapa suplemen visual untuk jawaban lainnya. Jawaban lengkap saya ada di sini .

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

The dispatchTouchEvent()Metode dari ViewGrouppenggunaan onInterceptTouchEvent()untuk memilih apakah harus segera menangani event sentuhan (dengan onTouchEvent()) atau terus memberitahu dispatchTouchEvent()metode anak-anaknya.

Suragch
sumber
Bisakah onInterceptTouchEvent dipanggil juga dalam aktivitas ?? Saya pikir itu hanya mungkin di ViewGroup atau saya salah? @Suragch
Federico Rizzo
@FedericoRizzo, Anda benar! Terima kasih banyak! Saya memperbarui diagram dan jawaban saya.
Suragch
20

Ada banyak kebingungan tentang metode ini, tetapi sebenarnya tidak terlalu rumit. Sebagian besar kebingungan adalah karena:

  1. Jika Anda View/ViewGroupatau salah satu anaknya tidak kembali benar onTouchEvent, dispatchTouchEventdan onInterceptTouchEventHANYA akan dipanggil untuk MotionEvent.ACTION_DOWN. Tanpa true from onTouchEvent, tampilan induk akan menganggap tampilan Anda tidak memerlukan MotionEvents.
  2. Ketika tidak ada anak-anak dari ViewGroup yang mengembalikan true di onTouchEvent, onInterceptTouchEvent HANYA akan dipanggil MotionEvent.ACTION_DOWN, bahkan jika ViewGroup Anda mengembalikan true dalam onTouchEvent.

Pesanan pemrosesan seperti ini:

  1. dispatchTouchEvent disebut.
  2. onInterceptTouchEventdipanggil untuk MotionEvent.ACTION_DOWNatau ketika salah satu anak-anak di ViewGroup mengembalikan true in onTouchEvent.
  3. onTouchEventpertama kali dipanggil pada anak-anak di ViewGroup dan ketika tidak ada anak yang mengembalikan true itu dipanggil pada View/ViewGroup.

Jika Anda ingin melihat pratinjau TouchEvents/MotionEventstanpa menonaktifkan acara pada anak-anak Anda, Anda harus melakukan dua hal:

  1. Override dispatchTouchEventuntuk melihat acara dan kembali super.dispatchTouchEvent(ev);
  2. Timpa onTouchEventdan kembalikan benar, jika tidak, Anda tidak akan mendapatkannya MotionEvent kecuali MotionEvent.ACTION_DOWN.

Jika Anda ingin mendeteksi beberapa gerakan seperti acara gesek, tanpa menonaktifkan acara lain pada anak-anak Anda selama Anda tidak mendeteksi gerakan tersebut, Anda dapat melakukannya seperti ini:

  1. Pratinjau MotionEvents seperti dijelaskan di atas dan atur tanda ketika Anda mendeteksi gerakan Anda.
  2. Kembalikan benar onInterceptTouchEventketika bendera Anda diatur untuk membatalkan pemrosesan MotionEvent oleh anak-anak Anda. Ini juga tempat yang nyaman untuk mengatur ulang flag Anda, karena onInterceptTouchEvent tidak akan dipanggil lagi sampai yang berikutnya MotionEvent.ACTION_DOWN.

Contoh override dalam FrameLayout(contoh saya adalah C # saat saya pemrograman dengan Xamarin Android, tetapi logikanya sama di Jawa):

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
sumber
3
Saya tidak tahu mengapa ini tidak memiliki lebih banyak upvotes. Ini jawaban yang bagus (IMO) dan saya merasa sangat membantu.
Mark Ormesher
8

Saya mendapat penjelasan yang sangat intuitif di laman web ini http://doandroids.com/blogs/tag/codeexample/ . Diambil dari sana:

  • boolean onTouchEvent (MotionEvent ev) - dipanggil setiap kali ada acara sentuh dengan View ini sebagai target terdeteksi
  • boolean onInterceptTouchEvent (MotionEvent ev) - dipanggil setiap kali acara sentuh terdeteksi dengan ViewGroup ini atau anak-anak sebagai target. Jika fungsi ini mengembalikan true, MotionEvent akan dicegat, artinya itu tidak akan diteruskan ke anak, melainkan ke onTouchEvent dari Tampilan ini.
Krzysztow
sumber
2
pertanyaannya adalah tentang onInterceptTouchEvent dan dispatchTouchEvent. Keduanya adalah panggilan sebelum onTouchEvent. Tetapi dalam contoh itu Anda tidak dapat melihat dispatchTouchEvent.
Dayerman
8

menangani dispatchTouchEvent sebelum diInterceptTouchEvent.

Menggunakan contoh sederhana ini:

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

Anda dapat melihat bahwa log akan seperti:

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

Jadi jika Anda bekerja dengan 2 penangan ini gunakan dispatchTouchEvent untuk menangani acara instance pertama, yang akan pergi ke onInterceptTouchEvent.

Perbedaan lainnya adalah bahwa jika dispatchTouchEvent mengembalikan 'false', acara tersebut tidak dapat disebarkan ke anak, dalam hal ini EditText, sedangkan jika Anda mengembalikan false ke dalamInterceptTouchEvent acara masih dapat dikirim ke EditText

Dayerman
sumber
5

Jawaban singkat: dispatchTouchEvent() akan dipanggil pertama -tama.

Saran singkat: jangan ditimpa dispatchTouchEvent()karena sulit dikendalikan, kadang-kadang dapat memperlambat kinerja Anda. IMHO, saya sarankan mengganti onInterceptTouchEvent().


Karena sebagian besar jawaban menyebutkan dengan cukup jelas tentang peristiwa aliran sentuh pada aktivitas / grup tampilan / tampilan, saya hanya menambahkan lebih detail tentang kode pada metode ini di ViewGroup(mengabaikan dispatchTouchEvent()):

onInterceptTouchEvent()akan dipanggil pertama, AKSI akan dipanggil masing-masing turun -> pindah -> naik. Ada 2 kasus:

  1. Jika Anda mengembalikan false dalam 3 kasus (ACTION_DOWN, ACTION_MOVE, ACTION_UP), itu akan dipertimbangkan karena orang tua tidak akan memerlukan acara sentuh ini , jadi onTouch()orang tua tidak pernah menelepon , tetapi onTouch()anak-anak akan memanggil sebaliknya ; namun harap perhatikan:

    • Yang onInterceptTouchEvent()masih terus menerima acara sentuhan, selama anak-anaknya tidak menelepon requestDisallowInterceptTouchEvent(true).
    • Jika tidak ada anak yang menerima acara itu (itu bisa terjadi dalam 2 kasus: tidak ada anak di posisi yang disentuh pengguna, atau ada anak yang mengembalikan false di ACTION_DOWN), orang tua akan mengirimkan acara itu kembali ke onTouch()orang tua.
  2. Begitu juga sebaliknya, jika Anda mengembalikan true , orang tua akan segera mencuri acara sentuhan ini , dan onInterceptTouchEvent()akan segera berhenti, alih onTouch()- alih orang tua akan dipanggil dan semua onTouch()anak akan menerima acara tindakan terakhir - ACTION_CANCEL (dengan demikian, itu berarti orang tua mencuri acara sentuhan, dan anak-anak tidak bisa mengatasinya sejak saat itu). Aliran onInterceptTouchEvent()return false adalah normal, tetapi ada sedikit kebingungan dengan return true case, jadi saya daftar di sini:

    • Kembalikan benar pada ACTION_DOWN, onTouch()dari orang tua akan menerima ACTION_DOWN lagi dan mengikuti tindakan (ACTION_MOVE, ACTION_UP).
    • Kembalikan true di ACTION_MOVE, onTouch()dari orang tua akan menerima ACTION_MOVE berikutnya (bukan ACTION_MOVE yang sama onInterceptTouchEvent()) dan tindakan berikut (ACTION_MOVE, ACTION_UP).
    • Kembalikan benar pada ACTION_UP, onTouch()orang tua TIDAK akan menelepon sama sekali karena sudah terlambat bagi orang tua untuk mencuri acara sentuhan.

Satu hal lagi yang penting adalah ACTION_DOWN dari acara di onTouch()akan menentukan apakah tampilan ingin menerima lebih banyak tindakan dari acara itu atau tidak. Jika tampilan mengembalikan true di ACTION_DOWN dalam onTouch(), itu berarti tampilan bersedia menerima lebih banyak tindakan dari peristiwa itu. Jika tidak, kembalikan salah pada ACTION_DOWN di onTouch()akan menyiratkan bahwa tampilan tidak akan menerima tindakan lagi dari peristiwa itu.

Nguyen Tan Dat
sumber
3

Kode berikut dalam subkelas ViewGroup akan mencegah wadah induknya menerima acara sentuh:

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

Saya menggunakan ini dengan overlay khusus untuk mencegah tampilan latar belakang merespons acara sentuh.

James Wald
sumber
1

Perbedaan utama :

• Activity.dispatchTouchEvent (MotionEvent) - Ini memungkinkan Aktivitas Anda untuk mencegat semua peristiwa sentuh sebelum dikirim ke jendela.
• ViewGroup.onInterceptTouchEvent (MotionEvent) - Ini memungkinkan ViewGroup untuk menonton acara saat mereka dikirim ke Tampilan anak.

ChapMic
sumber
1
Ya, saya tahu jawaban ini dari panduan pengembang Android - namun tidak jelas. Metode dispatchTouchEvent juga ada untuk ViewGroup tidak hanya untuk suatu Aktivitas. Pertanyaan saya adalah, bagaimana ketiga metode dispatchTouchEvent, onInterceptTouchEvent dan onTouchEvent berinteraksi bersama dalam hierarki ViewGroups misalnya RelativeLayouts.
Anne Droid
1

ViewGroup onInterceptTouchEvent()selalu menjadi titik masuk untuk ACTION_DOWNacara yang merupakan acara pertama yang terjadi.

Jika Anda ingin ViewGroup memproses gerakan ini, kembalikan benar dari onInterceptTouchEvent(). Pada kembali benar, ViewGroup ini onTouchEvent()akan menerima semua kejadian setelah sampai berikutnya ACTION_UPatau ACTION_CANCEL, dan dalam kebanyakan kasus, peristiwa sentuhan antara ACTION_DOWNdan ACTION_UPatau ACTION_CANCELyang ACTION_MOVE, yang biasanya akan diakui sebagai bergulir / melemparkan isyarat.

Jika Anda mengembalikan false dari onInterceptTouchEvent(), tampilan target onTouchEvent()akan dipanggil. Ini akan diulangi untuk pesan selanjutnya sampai Anda mengembalikan true dari onInterceptTouchEvent().

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

vipsy
sumber
0

Baik Activity dan View memiliki metode dispatchTouchEvent () dan onTouchEvent. ViewGroup juga memiliki metode ini, tetapi memiliki metode lain yang disebut onInceptceptTouchEvent. Jenis pengembalian dari metode tersebut adalah boolean, Anda dapat mengontrol rute pengiriman melalui nilai kembali.

Pengiriman acara di Android dimulai dari Activity-> ViewGroup-> View.

Ryan
sumber
0
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume =false;
    if(onInterceptTouchEvent(ev){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
}
Mc_fool_himself
sumber
1
Bisakah Anda menambahkan beberapa penjelasan?
Paul Floyd
3
Sementara potongan kode ini dapat menyelesaikan pertanyaan, termasuk penjelasan sangat membantu untuk meningkatkan kualitas posting Anda. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, dan orang-orang itu mungkin tidak tahu alasan untuk saran kode Anda.
Rosário Pereira Fernandes
-2

Jawaban kecil:

onInterceptTouchEvent datang sebelum setOnTouchListener.

sagit
sumber