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()
- Если не потребляется, обрабатывает сам сенсор
Как работает ViewGroup:
-
ViewGroup.dispatchTouchEvent()
-
onInterceptTouchEvent()
- Проверьте, не следует ли заменять детей
- Пропускает
ACTION_CANCEL
к активному дочернему элементу - Возвращает true, уничтожает все последующие события
- Для каждого дочернего представления в обратном порядке они были добавлены
- Если касание имеет значение (внутренний вид),
child.dispatchTouchEvent()
- Если не обрабатывается предыдущим, отправка в следующий вид
- Если дети не справляются со случаем, слушатель получает шанс
-
OnTouchListener.onTouch()
- Если нет слушателя или не обрабатывается
- Перехваченные события переходят через дочерний шаг
Он также предоставляет пример кода пользовательского касания 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
Ответ 7
Вы можете найти ответ в этом видео https://www.youtube.com/watch?v=SYoN-OvdZ3M&list=PLonJJ3BVjZW6CtAMbJz1XD8ELUs1KXaTD&index=19 и следующие 3 видео. Все события касания объясняются очень хорошо, это очень ясно и полно примеров.
Ответ 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 случая:
Если вы вернете false в 3 случаях (ACTION_DOWN, ACTION_MOVE, ACTION_UP), он будет считать, что родителю не понадобится это сенсорное событие, поэтому onTouch()
из родители никогда не звонят, но onTouch()
из детей звонит вместо этого; однако, пожалуйста, обратите внимание:
onInterceptTouchEvent()
все еще продолжает получать сенсорное событие, пока его дети не вызывают requestDisallowInterceptTouchEvent(true)
.
- Если нет детей, получающих это событие (это может произойти в 2 случаях: нет детей в позиции, к которой обращаются пользователи, или есть дети, но при ACTION_DOWN возвращается ложь), родители отправят это событие обратно в
onTouch()
родители.
И наоборот, если вы вернете 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.