Android: разница между onInterceptTouchEvent и dispatchTouchEvent? - PullRequest
230 голосов
/ 06 марта 2012

В чем разница между onInterceptTouchEvent и dispatchTouchEvent в Android?

В соответствии с руководством разработчика Android, оба метода могут использоваться для перехвата события касания (MotionEvent), но в чем заключаетсяразница?

Как onInterceptTouchEvent, dispatchTouchEvent и onTouchEvent взаимодействуют вместе в иерархии видов (ViewGroup)?

Ответы [ 14 ]

261 голосов
/ 06 февраля 2013

Лучшее место для демистификации - это исходный код.Документы крайне неадекватны для объяснения этого.

dispatchTouchEvent фактически определено в Activity, View и ViewGroup. Думайте об этом как о контроллере, который решает, как маршрутизировать события касания.

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

Для ViewGroup.dispatchTouchEvent все гораздо сложнее.Ему нужно выяснить, какое из его дочерних представлений должно получить событие (вызывая child.dispatchTouchEvent).Это в основном алгоритм тестирования попаданий, в котором вы выясняете, какой ограничительный прямоугольник дочернего представления содержит координаты точки касания.

Но прежде чем он сможет передать событие соответствующему дочернему представлению, родитель может шпионить и / или перехватитьсобытие все вместе.Для этого и существует onInterceptTouchEvent .Таким образом, он вызывает этот метод в первую очередь перед выполнением тестирования на попадание и, если событие было угнано (возвращая true из onInterceptTouchEvent), он отправляет ACTION_CANCEL дочерним представлениям, чтобы они могли отказаться от обработки события касания (от предыдущего касания).события) и с этого момента все сенсорные события на родительском уровне отправляются в onTouchListener.onTouch (если определено) или onTouchEvent ().Также в этом случае onInterceptTouchEvent больше не вызывается.

Хотели бы вы переопределить [Activity | ViewGroup | View] .dispatchTouchEvent?Если вы не делаете какую-то пользовательскую маршрутизацию, вы, вероятно, не должны.

Основными методами расширения являются ViewGroup.onInterceptTouchEvent, если вы хотите шпионить и / или перехватывать событие касания на родительском уровне, и View.onTouchListener / View.onTouchEvent для обработки основного события.

Все ввсе это слишком сложный дизайн, но Android Apis больше склоняется к гибкости, чем к простоте.

229 голосов
/ 19 марта 2014

Потому что это первый результат в Google.Я хочу поделиться с вами отличным выступлением Дэйва Смита на Youtube: освоение сенсорной системы Android и слайды доступны здесь .Это дало мне хорошее глубокое понимание системы Android Touch:

Как Activity обрабатывает касание:

  • Activity.dispatchTouchEvent()
    • Всегда первым вызываться
    • Отправляет событие в корневое представление, присоединенное к окну
    • onTouchEvent()
      • Вызывается, если ни одно представление не использует событие
      • Всегдапоследний должен называться

Как View обрабатывает прикосновение:

  • View.dispatchTouchEvent()
    • Сначала отправляет событие слушателю, если существует
      • View.OnTouchListener.onTouch()
    • Если не используется, обрабатывает само касание
      • View.onTouchEvent()

Как ViewGroup обрабатывает прикосновение:

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

Он также предоставляет пример кода пользовательского прикосновения на github.com /devunwired / .

Ответ: Обычно dispatchTouchEvent() вызывается на каждом слое View, чтобы определить, заинтересован ли View в продолжающемся жесте.В ViewGroup ViewGroup имеет возможность украсть сенсорные события в своем dispatchTouchEvent() -методе, прежде чем он вызовет dispatchTouchEvent() для детей.ViewGroup остановит диспетчеризацию, только если ViewGroup onInterceptTouchEvent() -метод вернет true.Разница в том, что dispatchTouchEvent() отправляет MotionEvents и onInterceptTouchEvent сообщает, должен ли он перехватывать (не отправляя MotionEvent детям) или (отправляядетям) .

Вы можете представить себе код группы ViewGroup , выполняющий более или менее это (очень упрощенно):

public boolean dispatchTouchEvent(MotionEvent ev) {
    if(!onInterceptTouchEvent()){
        for(View child : children){
            if(child.dispatchTouchEvent(ev))
                return true;
        }
    }
    return super.dispatchTouchEvent(ev);
}
45 голосов
/ 21 октября 2017

Дополнительный ответ

Вот некоторые визуальные дополнения к другим ответам. Мой полный ответ здесь .

enter image description here

enter image description here

Метод dispatchTouchEvent() для ViewGroup использует onInterceptTouchEvent(), чтобы выбрать, должен ли он немедленно обрабатывать событие касания (с onTouchEvent()) или продолжать уведомлять методы dispatchTouchEvent() о своих дочерних элементах.

17 голосов
/ 07 февраля 2014

В этих методах много путаницы, но на самом деле все не так сложно.Большая часть путаницы заключается в том, что:

  1. Если ваш View/ViewGroup или любой из его потомков не вернут true в onTouchEvent, dispatchTouchEvent и onInterceptTouchEvent будут вызываться ТОЛЬКО для MotionEvent.ACTION_DOWN,Без значения true от onTouchEvent родительское представление будет предполагать, что вашему представлению не нужны MotionEvents.
  2. Когда ни один из дочерних элементов ViewGroup не возвращает true в onTouchEvent, onInterceptTouchEvent будет вызываться ТОЛЬКО для MotionEvent.ACTION_DOWN,даже если ваша ViewGroup возвращает true в onTouchEvent.

Порядок обработки выглядит так:

  1. dispatchTouchEvent вызывается.
  2. onInterceptTouchEventвызывается для MotionEvent.ACTION_DOWN или когда любой из дочерних элементов ViewGroup вернул true в onTouchEvent.
  3. onTouchEvent сначала вызывается для дочерних элементов ViewGroup, и когда ни один из дочерних элементов не возвращает true, этовызывается на View/ViewGroup.

Если вы хотите просмотреть TouchEvents/MotionEvents без отключения событий для ваших детей, вы должны сделать две вещи:

  1. Override dispatchTouchEvent для предварительного просмотра события и возврата super.dispatchTouchEvent(ev);
  2. Переопределить onTouchEvent и вернуть true, иначе вы не получите MotionEvent, кроме MotionEvent.ACTION_DOWN.

ЕслиВы хотите обнаружить какой-то жест, например, событие смахивания, не отключаяЕсли у вас нет событий, если вы не обнаружили жест, вы можете сделать это следующим образом:

  1. Предварительный просмотр событий MotionEvents, как описано выше, и установка флага при обнаружении вашего жеста.
  2. Верните true в onInterceptTouchEvent, когда ваш флаг установлен для отмены обработки MotionEvent вашими детьми.Это также удобное место для сброса вашего флага, потому что onInterceptTouchEvent больше не будет вызываться до следующего MotionEvent.ACTION_DOWN.

Пример переопределений в FrameLayout (мой пример в C # asЯ программирую на 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;
}
8 голосов
/ 28 сентября 2012

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 возвращает 'false', событие не передается дочернему элементу, в этомслучай EditText, тогда как если вы вернете false в onInterceptTouchEvent, событие все равно будет отправлено в EditText

8 голосов
/ 11 июля 2012

Я нашел очень интуитивное объяснение на этой веб-странице http://doandroids.com/blogs/tag/codeexample/. Взято оттуда:

  • boolean onTouchEvent (MotionEvent ev) - вызывается всякий раз, когда происходит событие касания с этим View asобнаружена цель
  • boolean onInterceptTouchEvent (MotionEvent ev) - вызывается всякий раз, когда обнаруживается событие касания с этой ViewGroup или ее дочерним объектом в качестве цели.Если эта функция возвращает true, MotionEvent будет перехвачен, то есть он будет передан не дочернему элементу, а onTouchEvent этого представления.
4 голосов
/ 08 января 2015

Вы можете найти ответ в этом видео https://www.youtube.com/watch?v=SYoN-OvdZ3M&list=PLonJJ3BVjZW6CtAMbJz1XD8ELUs1KXaTD&index=19 и следующих 3 видео.Все сенсорные события объяснены очень хорошо, они очень понятны и полны примеров.

3 голосов
/ 08 мая 2018

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

Краткий совет: не должен отменять dispatchTouchEvent(), посколькуэто трудно контролировать, иногда это может замедлить вашу производительность.ИМХО, я предлагаю переопределить onInterceptTouchEvent().

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

onInterceptTouchEvent() будет вызван первым, событие ACTION будет вызвано соответственно down -> move -> up.Существует 2 случая:

  1. Если вы вернете false в 3 случаях (ACTION_DOWN, ACTION_MOVE, ACTION_UP), он будет считаться родительским ненужно это событие касания , поэтому onTouch() родителей никогда не звонит , но onTouch() детей будут звонить вместо ;однако, пожалуйста, обратите внимание:

    • onInterceptTouchEvent() все еще продолжает получать сенсорное событие, пока его дети не вызывают requestDisallowInterceptTouchEvent(true).
    • Если нет детей, получающих это событие (это может произойти в 2 случаях: нет детей в позиции, к которой обращаются пользователи, или есть дети, но при ACTION_DOWN он возвращает false), родители отправят это событие обратноonTouch() родителей.
  2. И наоборот, если вы вернете true , родитель украдет это событие касания немедленно, и onInterceptTouchEvent() немедленно прекратится, вместо этого onTouch() родителей будут называть , а все onTouch() детей получат последнее событие действия - ACTION_CANCEL (таким образом, это означает, что родители украли событие касания, и дети не могут обрабатывать его с тех пор).Поток onInterceptTouchEvent() return false нормальный, но есть небольшая путаница с return true case, поэтому я перечислю это здесь:

    • Return true в ACTION_DOWN, onTouch() родителей получитACTION_DOWN снова и последующие действия (ACTION_MOVE, ACTION_UP).
    • Верните true в ACTION_MOVE, onTouch() родителей получит next ACTION_MOVE (не то же самое в ACTION_MOVE вonInterceptTouchEvent()) и последующие действия (ACTION_MOVE, ACTION_UP).
    • Возвратите истину при ACTION_UP, onTouch() родителей будут НЕ вызваны вообще, так как родители слишком поздно будут вороватьсобытие касания.

Еще одна вещь важный - это ACTION_DOWN события в onTouch(), который определит, хочет ли представление получить больше действий от этогособытие или нет.Если представление возвращает значение true при значении ACTION_DOWN в onTouch(), это означает, что представление готово получить больше действий от этого события.В противном случае возврат false в ACTION_DOWN в onTouch() будет означать, что представление не получит больше действий от этого события.

3 голосов
/ 22 августа 2012

Следующий код в подклассе 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 голос
/ 18 февраля 2014

ViewGroup onInterceptTouchEvent() всегда является точкой входа для события ACTION_DOWN, которое является первым событием, которое должно произойти.

Если вы хотите, чтобы ViewGroup обработал этот жест, верните true из onInterceptTouchEvent().При возврате true, ViewGroup onTouchEvent() будет получать все последующие события до следующих ACTION_UP или ACTION_CANCEL, и в большинстве случаев события касания между ACTION_DOWN и ACTION_UP или ACTION_CANCEL равны ACTION_MOVE, чтообычно распознаются как жесты прокрутки / перемещения.

Если вы вернете false из onInterceptTouchEvent(), будет вызвано целевое представление onTouchEvent().Это будет повторяться для последующих сообщений, пока вы не вернете true из onInterceptTouchEvent().

Источник: http://neevek.net/posts/2013/10/13/implementing-onInterceptTouchEvent-and-onTouchEvent-for-ViewGroup.html

...