Android: diferença entre onInterceptTouchEvent e dispatchTouchEvent?

249

Qual é a diferença entre onInterceptTouchEvente dispatchTouchEventno Android?

De acordo com o guia do desenvolvedor do Android, os dois métodos podem ser usados ​​para interceptar um evento de toque ( MotionEvent), mas qual é a diferença?

Como onInterceptTouchEvent, dispatchTouchEvente onTouchEventinteragir, dentro de uma hierarquia de Views ( ViewGroup)?

Anne Droid
fonte

Respostas:

272

O melhor lugar para desmistificar isso é o código fonte. Os documentos são lamentavelmente inadequados para explicar isso.

dispatchTouchEvent é realmente definido em Activity, View e ViewGroup. Pense nisso como um controlador que decide como rotear os eventos de toque.

Por exemplo, o caso mais simples é o de View.dispatchTouchEvent, que encaminhará o evento de toque para OnTouchListener.onTouch, se estiver definido, ou para o método de extensão onTouchEvent .

Para ViewGroup.dispatchTouchEvent, as coisas são muito mais complicadas. Ele precisa descobrir qual das visualizações filho deve receber o evento (chamando child.dispatchTouchEvent). Esse é basicamente um algoritmo de teste de acerto, no qual você descobre qual retângulo delimitador da visualização filho contém as coordenadas do ponto de contato.

Mas antes que ele possa despachar o evento para a visão filho apropriada, o pai pode espionar e / ou interceptar o evento todos juntos. É para isso que existe o onInterceptTouchEvent . Portanto, ele chama esse método antes de fazer o teste de ocorrência e, se o evento foi seqüestrado (retornando true de onInterceptTouchEvent), ele envia um ACTION_CANCEL para as visualizações filho, para que possam abandonar o processamento de eventos de toque (de eventos de toque anteriores) e a partir de então todos os eventos de toque no nível pai são despachados para onTouchListener.onTouch (se definido) ou onTouchEvent (). Também nesse caso, onInterceptTouchEvent nunca é chamado novamente.

Você gostaria de substituir [Activity | ViewGroup | View] .dispatchTouchEvent? A menos que você esteja fazendo algum roteamento personalizado, provavelmente não deveria.

Os principais métodos de extensão são ViewGroup.onInterceptTouchEvent se você deseja espionar e / ou interceptar o evento de toque no nível pai e View.onTouchListener / View.onTouchEvent para manipulação de eventos principais.

Em suma, seu design excessivamente complicado, mas as APIs do Android inclinam-se mais para a flexibilidade do que para a simplicidade.

numan salati
fonte
10
Esta é uma resposta ótima e concisa. Para um exemplo mais detalhadas, consulte o treinamento "Gerenciamento de eventos de toque em um ViewGroup"
TalkLittle
1
@numan salati: "a partir de então, todos os eventos de toque no nível pai são despachados para onTouchListener.onTouch" - quero substituir o método de toque de despacho no meu grupo de exibição e fazê-lo despachar os eventos para onTouchListener. Mas não vejo como isso pode ser feito. Não há API para isso, como View.getOnTouchListener (). OnTouch (). Existe um método setOnTouchListener (), mas nenhum método getOnTouchListener (). Como isso pode ser feito então?
Ashwin
@ Ashwin meus pensamentos exatamente, não há setOnInterceptTouchEvent também. Você pode substituir uma exibição de subclasse para uso em um layout / inclusão no código, mas não pode mexer com os rootViews de fragmento / nível de atividade, pois não pode subclassificar essas visualizações sem subclassificar o próprio fragmento / atividade (como na maior compatibilidade Implementações ProgressActivitiy). A API precisa de um setOnInterceptTouchEvent para simplificar. Todo mundo usa interceptTouch em rootViews em algum momento de um semi-complexo App
leRobot
244

Porque este é o primeiro resultado no Google. Quero compartilhar com você uma ótima palestra de Dave Smith no Youtube: Dominar o Android Touch System e os slides estão disponíveis aqui . Isso me deu um bom entendimento profundo sobre o Android Touch System:

Como a Atividade lida com o toque:

  • Activity.dispatchTouchEvent()
    • Sempre o primeiro a ser chamado
    • Envia um evento para a visualização raiz anexada à Janela
    • onTouchEvent()
      • Chamado se nenhuma visualização consumir o evento
      • Sempre o último a ser chamado

Como o View lida com o toque:

  • View.dispatchTouchEvent()
    • Envia o evento para o ouvinte primeiro, se existir
      • View.OnTouchListener.onTouch()
    • Se não consumido, processa o próprio toque
      • View.onTouchEvent()

Como um ViewGroup lida com o toque:

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • Verifique se deve substituir as crianças
      • Passa ACTION_CANCEL para filho ativo
      • Se retornar verdadeiro uma vez, ViewGroupconsumirá todos os eventos subsequentes
    • Para cada visão filho (na ordem inversa, eles foram adicionados)
      • Se o toque for relevante (visão interna), child.dispatchTouchEvent()
      • Se não for tratado por um anterior, envie para a próxima visualização
    • Se nenhum filho manipular o evento, o ouvinte terá uma chance
      • OnTouchListener.onTouch()
    • Se não houver ouvinte ou não for tratado
      • onTouchEvent()
  • Eventos interceptados saltam sobre o passo filho

Ele também fornece um exemplo de código de toque personalizado em github.com/devunwired/ .

Resposta: Basicamente, dispatchTouchEvent()é chamado em todas as Viewcamadas para determinar se a Viewestá interessado em um gesto contínuo. Em um ViewGroupo ViewGrouptem a capacidade de roubar os eventos de toque em sua dispatchTouchEvent()-method, antes que ele chamaria dispatchTouchEvent()sobre as crianças. O ViewGroupinterromperia o envio apenas se o ViewGroup onInterceptTouchEvent()método-retornar true. A diferença é que dispatchTouchEvent()está despachando MotionEventse onInterceptTouchEventinforma se deve interceptar (não despachar MotionEventpara crianças) ou não (despachar para crianças) .

Você pode imaginar o código de um ViewGroup fazendo mais ou menos isso (muito simplificado):

public boolean dispatchTouchEvent(MotionEvent ev) {
    if(!onInterceptTouchEvent()){
        for(View child : children){
            if(child.dispatchTouchEvent(ev))
                return true;
        }
    }
    return super.dispatchTouchEvent(ev);
}
seb
fonte
64

Resposta complementar

Aqui estão alguns suplementos visuais para as outras respostas. Minha resposta completa está aqui .

insira a descrição da imagem aqui

insira a descrição da imagem aqui

O dispatchTouchEvent()método de um ViewGroupusa onInterceptTouchEvent()para escolher se ele deve manipular imediatamente o evento de toque (com onTouchEvent()) ou continuar notificando os dispatchTouchEvent()métodos de seus filhos.

Suragch
fonte
O onInterceptTouchEvent também pode ser chamado na atividade? Eu acho que é possível apenas em um ViewGroup ou estou errado? @Suragch
Federico Rizzo
@FedericoRizzo, você está certo! Muito obrigado! Atualizei o diagrama e minha resposta.
Suragch
20

Há muita confusão sobre esses métodos, mas na verdade não é tão complicado. A maior parte da confusão é porque:

  1. Se você View/ViewGroupou algum de seus filhos não retornar verdadeiro onTouchEvent, dispatchTouchEvente onInterceptTouchEventSOMENTE será necessário MotionEvent.ACTION_DOWN. Sem um true de onTouchEvent, a visualização principal assumirá que sua visualização não precisa dos MotionEvents.
  2. Quando nenhum dos filhos de um ViewGroup retornar true em onTouchEvent, onInterceptTouchEvent SOMENTE será chamado MotionEvent.ACTION_DOWN, mesmo que seu ViewGroup retorne true em onTouchEvent.

A ordem de processamento é assim:

  1. dispatchTouchEvent é chamado.
  2. onInterceptTouchEventé chamado MotionEvent.ACTION_DOWNou quando qualquer um dos filhos do ViewGroup retornou true onTouchEvent.
  3. onTouchEventé chamado primeiro pelos filhos do ViewGroup e, quando nenhum dos filhos retorna verdadeiro, é chamado no View/ViewGroup.

Se você deseja visualizar TouchEvents/MotionEventssem desativar os eventos em seus filhos, faça duas coisas:

  1. Substitua dispatchTouchEventpara visualizar o evento e retornar super.dispatchTouchEvent(ev);
  2. Substitua onTouchEvente retorne true, caso contrário você não receberá nada, MotionEvent exceto MotionEvent.ACTION_DOWN.

Se você deseja detectar algum gesto como um evento de furto, sem desativar outros eventos em seus filhos, desde que você não tenha detectado o gesto, você pode fazê-lo assim:

  1. Visualize os MotionEvents conforme descrito acima e defina um sinalizador quando detectar seu gesto.
  2. Retorne true onInterceptTouchEventquando seu sinalizador estiver definido para cancelar o processamento do MotionEvent por seus filhos. Este também é um local conveniente para redefinir seu sinalizador, porque onInterceptTouchEvent não será chamado novamente até a próxima MotionEvent.ACTION_DOWN.

Exemplo de substituições em a FrameLayout(meu exemplo em C # é como estou programando com o Xamarin Android, mas a lógica é a mesma em 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;
}
Marcel W
fonte
3
Não sei por que isso não tem mais votos positivos. É uma boa resposta (IMO) e achei muito útil.
Mark Ormesher
8

Eu vim através de uma explicação muito intuitiva nesta página da web http://doandroids.com/blogs/tag/codeexample/ . Retirado de lá:

  • onTouchEvent booleano (MotionEvent ev) - chamado sempre que um evento de toque com essa visualização como destino é detectado
  • boolean onInterceptTouchEvent (MotionEvent ev) - chamado sempre que um evento de toque é detectado com este ViewGroup ou um filho dele como destino. Se essa função retornar verdadeira, o MotionEvent será interceptado, o que significa que não será repassado ao filho, mas ao onTouchEvent desta visualização.
Krzysztow
fonte
2
A pergunta é sobre onInterceptTouchEvent e dispatchTouchEvent. Ambos são chamados antes do onTouchEvent. Mas nesse exemplo, você não pode ver dispatchTouchEvent.
Dayerman
8

os identificadores dispatchTouchEvent antes de onInterceptTouchEvent.

Usando este exemplo simples:

   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);

Você pode ver que o log será como:

I/System.out(25900): Event - dispatchTouchEvent
I/System.out(25900): Event - onInterceptTouchEvent

Portanto, caso você esteja trabalhando com esses 2 manipuladores, use dispatchTouchEvent para manipular, em primeira instância, o evento, que irá para onInterceptTouchEvent.

Outra diferença é que, se dispatchTouchEvent retornar 'false', o evento não será propagado para o filho, neste caso, o EditText, enquanto que se você retornar false em onInterceptTouchEvent, o evento ainda será despachado para o EditText

Dayerman
fonte
5

Resposta curta: dispatchTouchEvent() será chamado em primeiro lugar.

Conselho breve: não deve substituir, dispatchTouchEvent()uma vez que é difícil de controlar; às vezes, pode diminuir o desempenho. IMHO, sugiro substituir onInterceptTouchEvent().


Como a maioria das respostas menciona claramente o evento touch flow na atividade / exibição de grupo / exibição, apenas adiciono mais detalhes sobre o código desses métodos em ViewGroup(ignorando dispatchTouchEvent()):

onInterceptTouchEvent()será chamado primeiro, o evento ACTION será chamado respectivamente para baixo -> mover -> para cima. Existem 2 casos:

  1. Se você retornar falso em três casos (ACTION_DOWN, ACTION_MOVE, ACTION_UP), ele considerará que os pais não precisarão desse evento de toque ; portanto, onTouch()os pais nunca onTouch()ligam , mas os filhos ligam ; no entanto, observe:

    • O onInterceptTouchEvent()evento continua a receber toque, desde que seus filhos não liguem requestDisallowInterceptTouchEvent(true).
    • Se não houver filhos recebendo esse evento (isso pode acontecer em dois casos: não há filhos na posição em que os usuários tocam ou há filhos, mas retorna falso em ACTION_DOWN), os pais enviarão esse evento de volta aos onTouch()pais.
  2. Vice-versa, se você retornar verdadeiro , o pai roubará esse evento de toque imediatamente e onInterceptTouchEvent()parará imediatamente, em vez onTouch()de os pais serem chamados e todos os onTouch()filhos receberão o último evento de ação - ACTION_CANCEL (portanto, significa que os pais roubou um evento de toque e as crianças não podem lidar com isso a partir de então). O fluxo de onInterceptTouchEvent()retorno false é normal, mas há um pouco de confusão com o retorno true case, então listo aqui:

    • Retorno true em ACTION_DOWN, onTouch()os pais receberão ACTION_DOWN novamente e após as ações (ACTION_MOVE, ACTION_UP).
    • Retorne verdadeiro em ACTION_MOVE, onTouch()os pais receberão as próximas ACTION_MOVE (não são as mesmas ACTION_MOVE onInterceptTouchEvent()) e as seguintes ações (ACTION_MOVE, ACTION_UP).
    • Retorne verdadeiro em ACTION_UP, pois onTouch()os pais NÃO serão chamados, pois é muito tarde para os pais roubarem o evento de toque.

Mais uma coisa importante é ACTION_DOWN do evento onTouch(), determinando se a visualização gostaria de receber mais ação desse evento ou não. Se a visualização retornar verdadeira em ACTION_DOWN onTouch(), significa que a visualização está disposta a receber mais ações desse evento. Caso contrário, retornar false em ACTION_DOWN in onTouch()implicará que a exibição não receberá mais nenhuma ação desse evento.

Nguyen Tan Dat
fonte
3

O código a seguir em uma subclasse ViewGroup impediria que seus contêineres pai recebessem eventos de toque:

  @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;
  }

Usei isso com uma sobreposição personalizada para impedir que as visualizações em segundo plano respondam aos eventos de toque.

James Wald
fonte
1

A principal diferença :

• Activity.dispatchTouchEvent (MotionEvent) - Isso permite que sua atividade intercepte todos os eventos de toque antes de serem enviados para a janela.
• ViewGroup.onInterceptTouchEvent (MotionEvent) - Isso permite que um ViewGroup assista eventos à medida que são despachados para as Visualizações filho.

ChapMic
fonte
1
Sim, eu sei que essas respostas do guia do desenvolvedor do Android - ainda não estão claras. O método dispatchTouchEvent existe também para um ViewGroup, não apenas para uma Activity. Minha pergunta era: como os três métodos dispatchTouchEvent, onInterceptTouchEvent e onTouchEvent interagem juntos dentro de uma hierarquia de ViewGroups, por exemplo, RelativeLayouts.
Anne Droid
1

O ViewGroup's onInterceptTouchEvent()é sempre o ponto de entrada para o ACTION_DOWNevento que é o primeiro evento a ocorrer.

Se você deseja que o ViewGroup processe esse gesto, retorne true de onInterceptTouchEvent(). Ao retornar true, o ViewGroup onTouchEvent()receberá todos os eventos subsequentes até o próximo ACTION_UPou ACTION_CANCEL, e na maioria dos casos, os eventos de toque entre ACTION_DOWNe ACTION_UPou ACTION_CANCELsão ACTION_MOVE, que normalmente serão reconhecidos como gestos de rolagem / arremesso.

Se você retornar false onInterceptTouchEvent(), as visões de destino onTouchEvent()serão chamadas. Ele será repetido para mensagens subseqüentes até que você retorne verdadeiro onInterceptTouchEvent().

Fonte: http://neevek.net/posts/2013/10/13/implementing-onInterceptTouchEvent-and-onTouchEvent-for-ViewGroup.html

vipsy
fonte
0

Activity e View têm o método dispatchTouchEvent () e onTouchEvent. O ViewGroup também possui esses métodos, mas possui outro método chamado onInterceptTouchEvent. O tipo de retorno desses métodos é booleano; você pode controlar a rota de despacho através do valor de retorno.

O envio do evento no Android inicia em Atividade-> Grupo de Visão-> Visão.

Ryan
fonte
0
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume =false;
    if(onInterceptTouchEvent(ev){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
}
Mc_fool_himself
fonte
1
Você poderia adicionar alguma explicação?
Paul Floyd
3
Embora esse trecho de código possa resolver a questão, incluir uma explicação realmente ajuda a melhorar a qualidade da sua postagem. Lembre-se de que você está respondendo à pergunta dos leitores no futuro e essas pessoas podem não saber os motivos da sua sugestão de código.
Rosário Pereira Fernandes
-2

Pequena resposta:

onInterceptTouchEvent vem antes de setOnTouchListener.

sagits
fonte