Android: Разница между onInterceptTouchEvent и dispatchTouchEvent?

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

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

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

Ответ 1

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

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 для обработки основных событий.

В целом его чрезмерно сложный дизайн imo, но android apis больше ориентируется на гибкость, чем простота.

Ответ 2

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

Как работает Активность:

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

Как работает Вид:

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

Как работает ViewGroup:

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

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

Ответ: В принципе, dispatchTouchEvent() вызывается на каждом слое View, чтобы определить, заинтересован ли a 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);
}

Ответ 3

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

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

enter image description here

enter image description here

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

Ответ 4

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

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

Порядок обработки такой:

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

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

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

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

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

Пример переопределений в FrameLayout (мой пример в С# как Im-программирование с 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;
}

Ответ 5

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

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

Ответ 6

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

Следующий код в подклассе ViewGroup помешает родительским контейнерам получать события касания:

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

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

Я использовал это с настраиваемым оверлеем, чтобы предотвратить появление фоновых представлений о событиях касания.

Ответ 9

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

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

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

onInterceptTouchEvent() будет вызываться первым, событие ACTION будет вызываться соответственно вниз → двигаться → вверх. Есть 2 случая:

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

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

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

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

Ответ 10

Основное различие:

• Activity.dispatchTouchEvent(MotionEvent) - это позволяет вашу активность перехватывать все события касания до того, как они будут отправлены на окно.
• ViewGroup.onInterceptTouchEvent(MotionEvent) - это позволяет ViewGroup для просмотра событий по мере их отправки в дочерние представления.

Ответ 11

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

Ответ 12

Оба вида Activity и View имеют метод dispatchTouchEvent() и onTouchEvent.The ViewGroup тоже имеет эти методы, но имеет другой метод, называемый onInterceptTouchEvent. Тип возврата этих методов является логическим, вы можете управлять маршрутом отправки через возвращаемое значение.

Отправка события в Android начинается с Activity- > ViewGroup- > View.

Ответ 13

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

Ответ 14

На мой взгляд, dispatchTouchEvent() является очень важной частью при обработке прикосновений к устройству. Activity, ViewGroup, View имеют реализации этого метода. Если мы примем во внимание метод ViewGroup, то увидим, что dispatchTouchEvent() вызывает onInterceptTouchEvent()

enter image description here

Полный SO ответ здесь here

Ответ 15

Малый ответ:

onInterceptTouchEvent появляется перед setOnTouchListener.