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

249

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

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

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

Энн Дроид
источник

Ответы:

272

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

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 больше склоняется к гибкости, чем к простоте.

Numan Salati
источник
10
Это отличный, краткий ответ. Более подробный пример см. В тренинге «Управление сенсорными событиями в ViewGroup»
TalkLittle,
1
@numan salati: «с этого момента все события касания на родительском уровне отправляются в onTouchListener.onTouch» - я хочу переопределить метод отправки касания в моей группе представлений и заставить его отправлять события в onTouchListener. Но я не понимаю, как это можно сделать. Для этого нет API, как View.getOnTouchListener (). OnTouch (). Существует метод setOnTouchListener (), но нет метода getOnTouchListener (). Как это можно сделать тогда?
Эшвин
@ Отбрось мои мысли точно, там тоже нет setOnInterceptTouchEvent. Вы можете переопределить представление подкласса для использования в макете / добавить его в код, но вы не можете возиться с корневыми представлениями уровня фрагмента / действия, так как вы не можете создать подклассы этих представлений без создания подкласса самого фрагмента / действия (как в большинстве случаев совместимости). Внедрение ProgressActivitiy). Для простоты API необходим setOnInterceptTouchEvent. Все используют interceptTouch в rootViews в какой-то момент в
полусложном
244

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

Как Активность ручки потрогать:

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

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

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

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

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

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

Ответ: в основном dispatchTouchEvent()вызывается на каждом Viewслое, чтобы определить View, заинтересован ли а в продолжающемся жесте. В имеет способность красть сенсорные события в его -методе, прежде чем он будет звонить на детях. Команда только остановит диспетчеризацию, если -method вернет true. Разница в том , что в диспетчерских и говорит , если он должен перехватить (не диспетчеризация детей) или нет (диспетчерские ребенок) .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);
}
СЕБ
источник
65

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

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

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

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

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

Suragch
источник
Может ли onInterceptTouchEvent вызываться также в действии ?? Я думаю, что это возможно только в ViewGroup или я не прав? @Suragch
Федерико Риццо
@FedericoRizzo, ты прав! Большое спасибо! Я обновил схему и мой ответ.
Сурагч
20

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

  1. Если ваш View/ViewGroupили любой из его детей не вернет true onTouchEvent, dispatchTouchEventи onInterceptTouchEventбудет вызвано ТОЛЬКО MotionEvent.ACTION_DOWN. Без истинного значения 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. Переопределить, dispatchTouchEventчтобы просмотреть событие и вернуться super.dispatchTouchEvent(ev);
  2. Переопределите onTouchEventи верните true, иначе вы не получите ничего, MotionEvent кроме MotionEvent.ACTION_DOWN.

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

  1. Просмотрите MotionEvents, как описано выше, и установите флаг, когда вы обнаружили свой жест.
  2. Верните true, 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 или ее дочерним объектом в качестве цели. Если эта функция возвращает true, MotionEvent будет перехвачен, то есть он будет передан не дочернему элементу, а onTouchEvent этого представления.
Krzysztow
источник
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 возвращает «false», событие не распространяется на дочерний элемент, в данном случае это EditText, тогда как если вы возвращаете false в onInterceptTouchEvent, событие все равно получает диспетчеризацию для EditText.

Dayerman
источник
5

Краткий ответ: 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 возвращается ложь), родители отправят это событие onTouch()родителям.
  2. И наоборот, если вы вернете true , родитель немедленно украдет это событие касания и onInterceptTouchEvent()немедленно остановится, вместо onTouch()того, чтобы будут вызваны родители, а также все onTouch()дети получат последнее событие действия - ACTION_CANCEL (таким образом, это означает, что родители украл сенсорное событие, и дети не смогут с этим справиться). Поток onInterceptTouchEvent()return false является нормальным, но есть небольшая путаница с возвратом true case, поэтому я перечислю это здесь:

    • Возвращает истину в ACTION_DOWN, onTouch()из родителей получит ACTION_DOWN снова и последующие действия (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 in onTouch()будет означать, что представление не получит больше действий от этого события.

Нгуен Тан Дат
источник
4

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

BlueMango
источник
Это действительно хорошие видео, я посмотрел несколько, и это решило мою проблему.
Саймон
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 наблюдать за событиями, когда они отправляются дочерним представлениям.

ChapMic
источник
1
Да, я знаю этот ответ из руководства разработчика Android - тем не менее, неясно. Метод dispatchTouchEvent существует также для ViewGroup не только для Activity. Мой вопрос состоял в том, как три метода dispatchTouchEvent, onInterceptTouchEvent и onTouchEvent взаимодействуют вместе в иерархии ViewGroups, например, RelativeLayouts.
Энн Дройд
1

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

Если вы хотите, чтобы ViewGroup обработал этот жест, верните true из onInterceptTouchEvent(). По возвращению правда, 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

vipsy
источник
0

И Activity, и View имеют методы dispatchTouchEvent () и onTouchEvent. В 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);
    }
}
Mc_fool_himself
источник
1
Не могли бы вы добавить объяснение?
Пол Флойд
3
Хотя этот фрагмент кода может решить вопрос, в том числе объяснение действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причин, по которым вы предлагаете код.
Росарио Перейра Фернандес
-2

Маленький ответ:

onInterceptTouchEvent предшествует setOnTouchListener.

sagits
источник