Android: різниця між onInterceptTouchEvent і dispatchTouchEvent?


249

Яка різниця між Android onInterceptTouchEventі dispatchTouchEventв ньому?

Згідно з посібником для розробників android, обидва способи можна використовувати для перехоплення сенсорної події ( MotionEvent), але в чому різниця?

Як onInterceptTouchEvent, dispatchTouchEventі onTouchEventвзаємодіють один з одним в ієрархії уявлень ( ViewGroup)?

Відповіді:


272

Найкраще місце для демістифікації цього - вихідний код. Документи дуже важко пояснюють це.

dispatchTouchEvent фактично визначений у розділі "Активність", "Перегляд" та "Перегляд групи". Подумайте про це як про контролер, який вирішує, як спрямовувати події дотику.

Наприклад, найпростіший випадок - це View.dispatchTouchEvent, який буде направляти сенсорну подію або на OnTouchListener.onTouch, якщо він визначений, або на метод розширення onTouchEvent .

Для ViewGroup.dispatchTouchEvent речі набагато складніші. Потрібно розібратися, який з його поглядів на дитину повинен отримати подію (зателефонувавши на child.dispatchTouchEvent). Це в основному алгоритм тестування хітів, де ви з'ясували, який обмежуючий прямокутник подання дитини містить координати точки дотику.

Але перш ніж вона зможе направити подію на відповідний дочірній погляд, батько може шпигувати та / або перехоплювати подію всі разом. Саме для цього існує InInceptceptTouchEvent . Тому він спочатку викликає цей метод перед тим, як здійснити тестування звернень, і якщо подія була захоплена (повернувши правду з onInterceptTouchEvent), вона надсилає ACTION_CANCEL дочірнім представленням, щоб вони могли відмовитися від обробки подій сенсорного зв'язку (від попередніх подій торкання), а потім і далі всі події дотику на батьківському рівні надсилаються на onTouchListener.onTouch (якщо визначено) або onTouchEvent (). Також у цьому випадку onInterceptTouchEvent більше ніколи не викликається.

Ви хочете навіть змінити [Activity | ViewGroup | View] .dispatchTouchEvent? Якщо ви не робите власну маршрутизацію, ви, мабуть, не повинні.

Основними методами розширення є ViewGroup.onInterceptTouchEvent, якщо ви хочете шпигувати та / або перехоплювати сенсорні події на батьківському рівні та View.onTouchListener / View.onTouchEvent для обробки основних подій.

Загалом його надмірно складний дизайн imo but android apis більше схиляється до гнучкості, ніж простоти.


10
Це чудова, лаконічна відповідь. Більш детальний приклад дивіться у тренінгу "Керування подіями на дотик у ViewGroup"
TalkLittle

1
@numan salati: "відтоді всі сенсорні події на батьківському рівні надсилаються на onTouchListener.onTouch" - я хочу замінити метод диспетчерського дотику в моїй групі перегляду і змусити його відправляти події на onTouchListener. Але я не бачу, як це можна зробити. Не існує api для цього, як View.getOnTouchListener (). OnTouch (). Існує метод setOnTouchListener (), але немає методу getOnTouchListener (). Як це можна зробити тоді?
Ешвін

@Ashwin мої думки точно, там також не встановленоOnInterceptTouchEvent. Ви можете замінити подання підкласу для використання в макеті / додавання його в код, але ви не можете заплутатися з фрагментом / рівнем активності rootViews, оскільки ви не можете підкласирувати ці представлення без підкласифікації самого фрагмента / діяльності (як у більшості сумісності Реалізація ProgressActivitiy). Для простоти API потрібен setOnInterceptTouchEvent. Усі користуються interceptTouch у rootViews у якийсь момент у напівскладному додатку
leRobot

244

Тому що це перший результат в Google. Я хочу поділитися з вами чудовою розмовою Дейва Сміта на Youtube: Освоєння системи сенсорної системи Android, і слайди доступні тут . Це дало мені хороше глибоке розуміння щодо сенсорної системи Android:

Як керується активністю :

  • Activity.dispatchTouchEvent()
    • Завжди першими дзвонять
    • Надсилає подію до кореневого перегляду, приєднаного до вікна
    • onTouchEvent()
      • Викликається, якщо події не споживають події
      • Завжди останнім дзвонили

Як торкаються ручки View :

  • View.dispatchTouchEvent()
    • Спочатку надсилає подію слухачеві, якщо така є
      • View.OnTouchListener.onTouch()
    • Якщо не споживається, обробляє сам дотик
      • View.onTouchEvent()

Як торкається ViewGroup :

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • Перевірте, чи має це витісняти дітей
      • Переходить ACTION_CANCEL до активної дитини
      • Якщо вона повертається істиною один раз, вона ViewGroupспоживає всі наступні події
    • Для кожного дитячого перегляду (у зворотному порядку вони були додані)
      • Якщо дотик доречний (внутрішній вигляд), child.dispatchTouchEvent()
      • Якщо це не обробляється попереднім, перейдіть до наступного подання
    • Якщо жодна дитина не проводить події, слухач отримує шанс
      • OnTouchListener.onTouch()
    • Якщо слухача немає або його не обробляють
      • onTouchEvent()
  • Перехоплені події перестрибують через крок дитини

Він також надає приклад коду користувацького дотику на github.com/devunwired/ .

Відповідь: В основному dispatchTouchEvent()виклик викликається на кожному Viewшарі, щоб визначити, чи Viewзацікавлений в поточному жесті. В має здатність красти сенсорні події в його -Метод, перш ніж він буде дзвонити на дітях. Б зупинити тільки диспетчеризацію , якщо -метод повертає істину. Різниця в тому , що в диспетчерських і каже , якщо він повинен перехопити (НЕ диспетчеризація дітей) чи ні (диспетчерські дитина) .ViewGroupViewGroupdispatchTouchEvent()dispatchTouchEvent()ViewGroupViewGroup onInterceptTouchEvent()dispatchTouchEvent()MotionEventsonInterceptTouchEventMotionEvent

Ви можете уявити собі код ViewGroup, який робить це більш-менш (дуже спрощено):

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

64

Додатковий відповідь

Ось кілька візуальних доповнень до інших відповідей. Моя повна відповідь тут .

введіть тут опис зображення

введіть тут опис зображення

dispatchTouchEvent()Метод ViewGroupвикористання onInterceptTouchEvent()вибрати , чи слід негайно обробити подія дотику (з onTouchEvent()) або продовжувати уведомляющие про dispatchTouchEvent()методах своїх дітей.


Чи можна викликати onInterceptTouchEvent у своїй діяльності ?? Я думаю, що це можливо лише в ViewGroup або я помиляюся? @Suragch
Федеріко Ріццо

@FedericoRizzo, ти маєш рацію! Велике спасибі! Я оновив схему і свою відповідь.
Сурагч

20

Існує багато плутанини щодо цих методів, але насправді це не так вже й складно. Більшість плутанини пов’язана з тим, що:

  1. Якщо ваш View/ViewGroupабо хтось із його дітей не повернеться правдою onTouchEvent, dispatchTouchEventі onInterceptTouchEventТОЛЬКО буде закликаний MotionEvent.ACTION_DOWN. Без істинного з onTouchEvent, батьківський погляд припускає, що ваш погляд не потребує MotionEvents.
  2. Коли жоден із дітей ViewGroup не поверне справжнє в onTouchEvent, onInterceptTouchEvent буде ВИНАГО закликаний MotionEvent.ACTION_DOWN, навіть якщо ваша ViewGroup поверне справжню версію в onTouchEvent.

Замовлення на обробку виглядає так:

  1. dispatchTouchEvent це називається.
  2. onInterceptTouchEventвикликається MotionEvent.ACTION_DOWNабо коли хтось із дітей ViewGroup повернувся в "true" onTouchEvent.
  3. onTouchEventспочатку викликається дітьми ViewGroup, і коли жоден із дітей не повертає справжнє, його викликають на View/ViewGroup.

Якщо ви хочете попередній перегляд, TouchEvents/MotionEventsне вимикаючи події на своїх дітей, потрібно зробити дві дії:

  1. Перемістити dispatchTouchEventдля попереднього перегляду події та повернення super.dispatchTouchEvent(ev);
  2. Override onTouchEventі повертає істину, в іншому випадку ви не отримаєте який - або MotionEvent крім MotionEvent.ACTION_DOWN.

Якщо ви хочете виявити такий жест, як подія пальцем, не відключаючи інші події у ваших дітей до тих пір, поки ви не виявили жест, можете зробити це так:

  1. Перегляньте MotionEvents, як описано вище, і встановіть прапор, коли ви виявили свій жест.
  2. Поверніть істинно, onInterceptTouchEventколи ваш прапор встановлений для скасування обробки MotionEvent вашими дітьми. Це також зручне місце для скидання вашого прапора, оскільки onInterceptTouchEvent не буде викликаний знову до наступного MotionEvent.ACTION_DOWN.

Приклад переопределення в FrameLayout(мій приклад - це C #, коли я програмую з Xamarin Android, але логіка однакова в Java):

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
Я не знаю, чому це не має більше коштів. Це гарна відповідь (ІМО), і я вважаю це дуже корисним.
Марк Ормешер

8

Я наткнувся на дуже інтуїтивне пояснення на цій веб-сторінці http://doandroids.com/blogs/tag/codeexample/ . Взято звідти:

  • boolean onTouchEvent (MotionEvent ev) - викликається щоразу, коли буде виявлено сенсорну подію з цим Поглядом як ціль
  • boolean onInterceptTouchEvent (MotionEvent ev) - викликається всякий раз, коли виявлена ​​подія дотику за допомогою цієї ViewGroup або дочірньої групи як цілі. Якщо ця функція повернеться істинною, MotionEvent буде перехоплений, тобто не буде передано дитині, а скоріше на onTouchEvent цього Погляду.

2
Питання про OnInterceptTouchEvent і dispatchTouchEvent. Обидва дзвонять перед onTouchEvent. Але в цьому прикладі ви не бачите dispatchTouchEvent.
Дейерман

8

dispatchTouchEvent ручками перед onInterceptTouchEvent.

Використовуючи цей простий приклад:

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

Ви можете бачити, що журнал буде таким:

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

Тож у випадку, якщо ви працюєте з цими двома обробниками, використовуйте dispatchTouchEvent для обробки події в першій інстанції, яка перейде до OnInterceptTouchEvent.

Інша відмінність полягає в тому, що якщо dispatchTouchEvent поверне "помилково", подія не поширюється на дитину, в цьому випадку EditText, тоді як якщо ви повернете помилкове в onInterceptTouchEvent, подія все одно отримає відправлення на EditText


5

Коротка відповідь: перш за все dispatchTouchEvent()буде називатися .

Коротка порада: не слід перекривати, dispatchTouchEvent()оскільки важко контролювати, іноді це може уповільнити вашу роботу. ІМХО, я пропоную переосмислити onInterceptTouchEvent().


Оскільки більшість відповідей досить чітко згадують про подію "touch touch" про активність / групу перегляду / перегляд, я лише додаю більш детальну інформацію про код цих методів у ViewGroup(ігноруванні dispatchTouchEvent()):

onInterceptTouchEvent()буде викликано першим, дія ДІЇ буде викликана відповідно вниз -> переміщення -> вгору. Є 2 випадки:

  1. Якщо ви повернете помилкове у 3 випадках (ACTION_DOWN, ACTION_MOVE, ACTION_UP), він вважатиме, що батькові не потрібно цього сенсорного події , тому onTouch()батьки ніколи не дзвонять , а onTouch()діти замість цього зателефонують ; однак зауважте:

    • Все onInterceptTouchEvent()ще продовжують отримувати сенсорний захід, доки його діти не дзвонять requestDisallowInterceptTouchEvent(true).
    • Якщо немає дітей, які отримують цю подію (це може статися у двох випадках: немає дітей у позиції, до якої користувачі торкаються, або є діти, але вона повертається помилково в ACTION_DOWN), батьки відправлять цю подію назад до onTouch()батьків.
  2. І навпаки, якщо ви повернете вірно , батько негайно вкраде цю подій , і onInterceptTouchEvent()негайно зупиниться, замість onTouch()того, що батьки будуть викликані , а також усі onTouch()діти отримають останню подію дії - ACTION_CANCEL (таким чином, це означає, що батьки вкрав подію дотику, і діти з цього часу не можуть впоратися з цим). Потік onInterceptTouchEvent()повернення false є нормальним, але невелика плутанина з істинним випадком return, тому я перераховую його тут:

    • Повертає істину в ACTION_DOWN, onTouch()з батьки отримуватимуть ACTION_DOWN знову і наступні дії (ACTION_MOVE, ACTION_UP).
    • Повернення справжнього в ACTION_MOVE, onTouch()батьки отримуватимуть наступну ACTION_MOVE (не та ж ACTION_MOVE в onInterceptTouchEvent()) та наступні дії (ACTION_MOVE, ACTION_UP).
    • Повернення справжнього в ACTION_UP, onTouch()батьки взагалі НЕ зателефонують, оскільки батькам уже пізно вкрасти подію на дотик.

Ще одна важлива річ : ACTION_DOWN події в onTouch()визначатиме, чи хотів би перегляд отримати більше дії від цієї події чи ні. Якщо вигляд повернеться істинним в ACTION_DOWN у onTouch(), це означає, що перегляд готовий отримати більше дії від цієї події. В іншому випадку повернення помилки в ACTION_DOWN в onTouch()буде означати, що представлення не отримає більше жодної дії від цієї події.


4

Відповідь ви можете знайти у цьому відео https://www.youtube.com/watch?v=SYoN-OvdZ3M&list=PLonJJ3BVjZW6CtAMbJz1XD8ELUs1KXaTD&index=19 та наступні 3 відео. Усі події на дотик пояснюються дуже добре, це дуже зрозуміло і насичено прикладами.


Це дійсно хороші відео, я переглянув декілька, і це вирішило мою проблему.
Саймон

3

Наведений нижче код підкласу ViewGroup не дозволить батькам-контейнерам отримувати сенсорні події:

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

Я використовував це за допомогою спеціального накладання, щоб запобігти реакції фонових зображень на події, що торкаються.


1

Основна відмінність:

• Activity.dispatchTouchEvent (MotionEvent) - це дозволяє вашій діяльності перехоплювати всі події на дотик, перш ніж вони будуть відправлені у вікно.
• ViewGroup.onInterceptTouchEvent (MotionEvent) - це дозволяє ViewGroup переглядати події під час їх відправлення до дочірніх переглядів.


1
Так, я знаю, що ці відповіді знаходяться в посібнику з Android для розробників - все ж незрозуміло. Метод dispatchTouchEvent існує також для ViewGroup не лише для діяльності. Моє запитання полягало в тому, як три методи взаємодії dispatchTouchEvent, onInterceptTouchEvent та onTouchEvent взаємодіють разом в межах ієрархії ViewGroups, наприклад, RelativeLayouts.
Енн Дроїд

1

ViewGroup's onInterceptTouchEvent()завжди є точкою входу для ACTION_DOWNподії, яка вперше відбулася.

Якщо ви хочете, щоб ViewGroup обробив цей жест, поверніться з true onInterceptTouchEvent(). По поверненню правда, ViewGroup - х onTouchEvent()буде отримувати всі наступні події до наступного ACTION_UPабо ACTION_CANCEL, в більшості випадків, сенсорні події між ACTION_DOWNі ACTION_UPчи ACTION_CANCELє ACTION_MOVE, які, як правило , визнаються прокрутки / жбурнути жести.

Якщо ви повернете помилковий з onInterceptTouchEvent(), onTouchEvent()буде викликано цільовий вигляд . Він буде повторюватися для подальших повідомлень, поки ви не повернете правду з onInterceptTouchEvent().

Джерело: http://neevek.net/posts/2013/10/13/implementing-onInterceptTouchEvent-and-onTouchEvent-for-ViewGroup.html


0

І Activity, і View мають методи dispatchTouchEvent () та onTouchEvent.The ViewGroup також має ці методи, але має інший метод, званий onInterceptTouchEvent. Тип повернення цих методів булів, ви можете керувати маршрутом відправки через значення повернення.

Відправлення події в Android починається з Activity-> ViewGroup-> View.


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

1
Чи можете ви додати трохи пояснень?
Пол Флойд

3
Хоча цей фрагмент коду може вирішити питання, у тому числі пояснення дійсно допомагає покращити якість вашої публікації. Пам'ятайте, що ви відповідаєте на запитання читачів у майбутньому, і ці люди можуть не знати причини вашої пропозиції щодо коду.
Розаріо Перейра Фернандес

-2

Невелика відповідь:

onInterceptTouchEvent виходить перед setOnTouchListener.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.