В чем разница между onInterceptTouchEvent
и dispatchTouchEvent
в Android?
Согласно руководству разработчика Android, оба метода можно использовать для перехвата сенсорного события ( MotionEvent
), но в чем разница?
Как onInterceptTouchEvent
, dispatchTouchEvent
и onTouchEvent
взаимодействуют друг с другом в иерархии представлений ( ViewGroup
)?
Лучшим местом для демистификации является исходный код. Документы крайне неадекватны в объяснении этого.
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 больше ориентирован на гибкость, чем простота.
Потому что это первый результат в Google. Я хочу поделиться с вами замечательным Разговором Дэйвом Смитом на Youtube: Освоение Android Touch System и слайды доступны здесь . Это дало мне хорошее глубокое понимание системы Android Touch:
Как работает Activity :
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
чтобы определить, заинтересован ли View
в продолжающемся жесте. В ViewGroup
ViewGroup
имеет возможность украсть события 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); }
Существует много путаницы в отношении этих методов, но на самом деле это не так сложно. Большая часть путаницы заключается в том, что:
Порядок обработки такой:
Если вы хотите просмотреть TouchEvents / MotionEvents, не отключая события от своих детей, вы должны сделать две вещи:
Если вы хотите обнаружить какой-то жест как событие салфетки, не отключая другие события для своих детей, пока вы не обнаруживаете жест, вы можете сделать это следующим образом:
Пример переопределений в 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; }
Я натолкнулся на очень интуитивное объяснение на этой веб-странице http://doandroids.com/blogs/tag/codeexample/ . Взято оттуда:
- Boolean onTouchEvent (MotionEvent ev) – вызывается всякий раз, когда обнаружено событие касания с этим представлением в качестве цели
- Boolean onInterceptTouchEvent (MotionEvent ev) – вызывается всякий раз, когда обнаружено событие касания с этой ViewGroup или дочерним элементом в качестве цели. Если эта функция вернёт true, MotionEvent будет перехвачен, то есть он не будет передан ребенку, а скорее будет onTouchEvent этого представления.
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
Вы можете найти ответ в этом видео https://www.youtube.com/watch?v=SYoN-OvdZ3M&list=PLonJJ3BVjZW6CtAMbJz1XD8ELUs1KXaTD&index=19 и следующие 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; }
Я использовал это с пользовательским оверлеем, чтобы предотвратить просмотр фоновых представлений о событиях касания.
Основное различие:
• Activity.dispatchTouchEvent (MotionEvent) – это позволяет вашей активности перехватывать все события касания до того, как они будут отправлены в окно.
• ViewGroup.onInterceptTouchEvent (MotionEvent). Это позволяет ViewGroup просматривать события по мере их отправки в дочерние представления.
Функция onInterceptTouchEvent()
всегда является точкой входа для события ACTION_DOWN
которое является первым событием.
Если вы хотите, чтобы ViewGroup обрабатывал этот жест, верните true из onInterceptTouchEvent()
. При возврате true, onTouchEvent onTouchEvent()
будет onTouchEvent()
все последующие события до следующих ACTION_UP
или ACTION_CANCEL
, и в большинстве случаев события касания между ACTION_DOWN
и ACTION_UP
или ACTION_CANCEL
являются ACTION_MOVE
, которые обычно будут распознаваться как жесты прокрутки / перемещения.
Если вы onInterceptTouchEvent()
false из onInterceptTouchEvent()
, будет вызываться onInterceptTouchEvent()
целевого представления. Он будет повторяться для последующих сообщений, пока вы не onInterceptTouchEvent()
true из onInterceptTouchEvent()
.
В Activity и View есть метод dispatchTouchEvent () и onTouchEvent.The ViewGroup тоже имеет эти методы, но имеет другой метод, называемый onInterceptTouchEvent. Тип возврата этих методов является логическим, вы можете управлять маршрутом отправки через возвращаемое значение.
Отправка события в Android начинается с Activity-> ViewGroup-> View.
Малый ответ:
OnInterceptTouchEvent появляется перед setOnTouchListener.