Como exatamente o atributo XML android: onClick difere de setOnClickListener?

416

Pelo que li, você pode atribuir um onClickmanipulador a um botão de duas maneiras.

Usando o android:onClickatributo XML no qual você acabou de usar o nome de um método público com a assinatura void name(View v)ou usando o setOnClickListenermétodo no qual passa um objeto que implementa a OnClickListenerinterface. O último geralmente requer uma classe anônima da qual pessoalmente não gosto (gosto pessoal) ou definição de uma classe interna que implemente o OnClickListener.

Usando o atributo XML, você só precisa definir um método em vez de uma classe, então eu queria saber se o mesmo pode ser feito via código e não no layout XML.

emitrax
fonte
4
Eu li o seu problema e acho que você está preso no mesmo lugar, assim como eu. Me deparei com um vídeo muito bom que me ajudou muito a resolver meu problema. Encontre o vídeo no link a seguir: youtube.com/watch?v=MtmHURWKCmg&feature=youtu.be Espero que isso ajude você também :) #
user2166292 /
9
Para quem quer economizar tempo assistindo ao vídeo postado no comentário acima, ele simplesmente demonstra como dois botões podem ter o mesmo método para seu onClickatributo no arquivo de layout. Isso é feito graças ao parâmetro View v. Você simplesmente confere if (v == findViewById(R.id.button1)) etc.
CodyBugstein
13
@Irmay, eu acho que é melhor usar v.getId() == R.id.button1, já que você não precisa encontrar o controle real e fazer uma comparação. E você pode usar um em switchvez de muitos ifs.
Sami Kuhmonen
3
Este tutorial irá ajudá-lo muito. clique aqui
c49
Usar o xml android: onClick causa uma falha.

Respostas:

604

Não, isso não é possível via código. O Android implementa apenas OnClickListenerpara você quando você define o android:onClick="someMethod"atributo.

Esses dois trechos de código são iguais, apenas implementados de duas maneiras diferentes.

Implementação de código

Button btn = (Button) findViewById(R.id.mybutton);

btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        myFancyMethod(v);
    }
});

// some more code

public void myFancyMethod(View v) {
    // does something very interesting
}

Acima está uma implementação de código de um OnClickListener. E esta é a implementação XML.

Implementação XML

<?xml version="1.0" encoding="utf-8"?>
<!-- layout elements -->
<Button android:id="@+id/mybutton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Click me!"
    android:onClick="myFancyMethod" />
<!-- even more layout elements -->

Em segundo plano, o Android não faz nada além do código Java, chamando seu método em um evento de clique.

Observe que, com o XML acima, o Android procurará o onClickmétodo myFancyMethod()apenas na atividade atual. É importante lembrar se você estiver usando fragmentos, pois mesmo se você adicionar o XML acima usando um fragmento, o Android não procurará o onClickmétodo no .javaarquivo do fragmento usado para adicionar o XML.

Outra coisa importante que notei. Você mencionou que não prefere métodos anônimos . Você queria dizer que não gosta de aulas anônimas .

Otaviano A. Damiean
fonte
4
Eu não sou um guru de Java, mas sim, eu quis dizer classe anônima. Obrigado pela sua resposta. Muito claro.
emitrax
118
Observe que, se você usar o XML onclick, precisará colocar o método onclick ( myFancyMethod()) na atividade atual. Isto é importante se você estiver usando fragmentos, uma vez que a forma de programação de configuração onclick ouvintes provavelmente terá o método de manipulação de cliques em onCreateView de um fragmento () ... onde seria não ser encontrada se referido a partir de XML.
Pedro #
12
Sim, o método deve ser público.
Otaviano A. Damiean
12
O interessante é que fazê-lo em código permite proteger o acesso ao método, tornando-o privado, enquanto o método xml leva à exposição do método.
bgse
5
O fato de que a função (na abordagem XML) deve estar em Activity é importante não apenas quando você considera fragmentos, mas também visualizações customizadas (contendo o botão). Quando você tem uma exibição personalizada que reutiliza em várias atividades, mas deseja usar o mesmo método onClick para todos os casos, o método XML não é o mais conveniente. Você precisaria colocar isso noClickMethod (com o mesmo corpo) em todas as atividades que usam sua exibição personalizada.
Bartek Lipinski
87

Quando vi a resposta principal, percebi que meu problema não era colocar o parâmetro (View v) no método sofisticado:

public void myFancyMethod(View v) {}

Ao tentar acessá-lo a partir do xml, deve-se usar

android:onClick="myFancyMethod"/>

Espero que ajude alguém.

jp093121
fonte
74

android:onClick é para o nível 4 da API em diante; portanto, se você estiver segmentando <1.6, não poderá usá-lo.

James
fonte
33

Verifique se você esqueceu de colocar o método público!

Ruivo
fonte
1
por que é obrigatório torná-lo público?
eRaisedToX
3
@eRaisedToX Eu acho que é bem claro: se não é público, não pode ser chamado a partir do framework Android.
M0skit0 18/05
28

A especificação de android:onClickresultados de atributos na Buttoninstância que chama setOnClickListenerinternamente. Portanto, não há absolutamente nenhuma diferença.

Para ter um entendimento claro, vamos ver como o onClickatributo XML é tratado pela estrutura.

Quando um arquivo de layout é inflado, todas as Visualizações especificadas nele são instanciadas. Nesse caso específico, a Buttoninstância é criada usando o public Button (Context context, AttributeSet attrs, int defStyle)construtor. Todos os atributos na tag XML são lidos no pacote de recursos e transmitidos AttributeSetpara o construtor.

ButtonA classe é herdada da Viewclasse que resulta na Viewchamada do construtor, que cuida da configuração do manipulador de retorno de chamada por clique setOnClickListener.

O atributo onClick definido em attrs.xml , é referido em View.java como R.styleable.View_onClick.

Aqui está o código View.javaque faz a maior parte do trabalho para você chamando setOnClickListenerpor si só.

 case R.styleable.View_onClick:
            if (context.isRestricted()) {
                throw new IllegalStateException("The android:onClick attribute cannot "
                        + "be used within a restricted context");
            }

            final String handlerName = a.getString(attr);
            if (handlerName != null) {
                setOnClickListener(new OnClickListener() {
                    private Method mHandler;

                    public void onClick(View v) {
                        if (mHandler == null) {
                            try {
                                mHandler = getContext().getClass().getMethod(handlerName,
                                        View.class);
                            } catch (NoSuchMethodException e) {
                                int id = getId();
                                String idText = id == NO_ID ? "" : " with id '"
                                        + getContext().getResources().getResourceEntryName(
                                            id) + "'";
                                throw new IllegalStateException("Could not find a method " +
                                        handlerName + "(View) in the activity "
                                        + getContext().getClass() + " for onClick handler"
                                        + " on view " + View.this.getClass() + idText, e);
                            }
                        }

                        try {
                            mHandler.invoke(getContext(), View.this);
                        } catch (IllegalAccessException e) {
                            throw new IllegalStateException("Could not execute non "
                                    + "public method of the activity", e);
                        } catch (InvocationTargetException e) {
                            throw new IllegalStateException("Could not execute "
                                    + "method of the activity", e);
                        }
                    }
                });
            }
            break;

Como você pode ver, setOnClickListeneré chamado para registrar o retorno de chamada, como fazemos em nosso código. A única diferença que ele usa Java Reflectionpara chamar o método de retorno de chamada definido em nossa Atividade.

Aqui está o motivo dos problemas mencionados em outras respostas:

  • O método de retorno de chamada deve ser público : como Java Class getMethodé usado, apenas as funções com especificador de acesso público são pesquisadas. Caso contrário, esteja pronto para lidar com a IllegalAccessExceptionexceção.
  • Ao usar o botão com onClick no fragmento, o retorno de chamada deve ser definido em Activity : getContext().getClass().getMethod()call restringe a pesquisa do método ao contexto atual, que é Activity no caso de Fragment. Portanto, o método é pesquisado na classe Activity e não na classe Fragment.
  • O método de retorno de chamada deve aceitar o parâmetro View : uma vez que Java Class getMethodprocura o método que aceita View.classcomo parâmetro.
Manish Mulimani
fonte
1
Essa foi a peça que faltava para mim - Java usa o Reflection para encontrar o manipulador de cliques começando com getContext (). Foi um pouco misterioso para mim como o clique se propaga de um fragmento para uma Atividade.
Andrew Queisser
15

Há respostas muito boas aqui, mas quero adicionar uma linha:

No android:onclickXML, o Android usa a reflexão java nos bastidores para lidar com isso.

E como explicado aqui, a reflexão sempre diminui o desempenho. (especialmente na Dalvik VM). O registro onClickListeneré uma maneira melhor.

Krupal Shah
fonte
5
Quanto pode retardar o aplicativo? :) Meio milissegundo; nem mesmo? Em comparação com realmente inflar o layout é como uma pena e uma baleia
Konrad Morawski
14

Observe que, se você deseja usar o recurso onClick XML, o método correspondente deve ter um parâmetro, cujo tipo deve corresponder ao objeto XML.

Por exemplo, um botão será vinculado ao seu método por meio da sequência de nomes:android:onClick="MyFancyMethod" mas a declaração do método deve mostrar: ...MyFancyMethod(View v) {...

Se você estiver tentando adicionar esse recurso a um item de menu , ele terá exatamente a mesma sintaxe no arquivo XML, mas seu método será declarado como:...MyFancyMethod(MenuItem mi) {...

Antoine Lizée
fonte
6

Outra maneira de definir seus ouvintes de clique seria usar XML. Basta adicionar o atributo android: onClick à sua tag.

É uma boa prática usar o atributo xml “onClick” em uma classe Java anônima sempre que possível.

Primeiro de tudo, vamos dar uma olhada na diferença de código:

Atributo XML / atributo onClick

Parte XML

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/button1" 
    android:onClick="showToast"/>

Parte Java

public void showToast(View v) {
    //Add some logic
}

Classe Java anônima / setOnClickListener

Parte XML

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

Parte Java

findViewById(R.id.button1).setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //Add some logic
        }
});

Aqui estão os benefícios de usar o atributo XML sobre uma classe Java anônima:

  • Com a classe Java anônima, sempre precisamos especificar um ID para nossos elementos, mas o atributo XML pode ser omitido.
  • Com a classe Java anônima, precisamos procurar ativamente o elemento dentro da visualização (parte findViewById), mas com o atributo XML Android o faz por nós.
  • A classe Java anônima requer pelo menos 5 linhas de código, como podemos ver, mas com o atributo XML, 3 linhas de código são suficientes.
  • Com a classe Java anônima, precisamos nomear nosso método "onClick", mas com o atributo XML, podemos adicionar qualquer nome que desejar, o que ajudará dramaticamente a legibilidade do nosso código.
  • O atributo xml "onClick" foi adicionado pelo Google durante o lançamento do nível 4 da API, o que significa que é uma sintaxe um pouco mais moderna e a sintaxe moderna é quase sempre melhor.

Obviamente, nem sempre é possível usar o atributo Xml, eis as razões pelas quais não o escolhemos:

  • Se estamos trabalhando com fragmentos. O atributo onClick pode ser adicionado apenas a uma atividade; portanto, se tivermos um fragmento, precisaremos usar uma classe anônima.
  • Se desejarmos mover o ouvinte onClick para uma classe separada (talvez seja muito complicado e / ou gostaríamos de reutilizá-lo em diferentes partes de nosso aplicativo), não desejaríamos usar o atributo xml ou.
Bestin John
fonte
E observe que a função chamada usando atributos XML sempre deve ser pública e, se for declarada como galinha particular, resultará em exceção.
Bestin John
5

Com o Java 8, você provavelmente poderia usar a Referência de método para conseguir o que deseja.

Suponha que este seja seu onClickmanipulador de eventos para um botão.

private void onMyButtonClicked(View v) {
    if (v.getId() == R.id.myButton) {
        // Do something when myButton was clicked
    }
}

Em seguida, você passa a onMyButtonClickedreferência do método da instância em uma setOnClickListener()chamada como esta.

Button myButton = (Button) findViewById(R.id.myButton);
myButton.setOnClickListener(this::onMyButtonClicked);

Isso permitirá que você evite definir explicitamente uma classe anônima. No entanto, devo enfatizar que a Referência de método do Java 8 é na verdade apenas um açúcar sintático. Na verdade, ele cria uma instância da classe anônima para você (assim como a expressão lambda), portanto, cuidado semelhante ao do manipulador de eventos no estilo da expressão lambda foi aplicado quando você cancela o registro do manipulador de eventos. Este artigo explica muito bem.

PS. Para quem está curioso sobre como realmente usar o recurso da linguagem Java 8 no Android, é uma cortesia da retrolambda biblioteca .

onelaview
fonte
5

Usando o atributo XML, você só precisa definir um método em vez de uma classe, então eu queria saber se o mesmo pode ser feito via código e não no layout XML.

Sim, você pode criar fragmentou activityimplementarView.OnClickListener

e quando você inicializa seus novos objetos de exibição no código, você pode simplesmente fazer mView.setOnClickListener(this);

e isso define automaticamente todos os objetos de exibição no código para usar o onClick(View v)método que seu fragmentou activityetc possui.

para distinguir qual visualização chamou o onClickmétodo, você pode usar uma instrução switch no v.getId()método

Esta resposta é diferente da que diz "Não, isso não é possível via código"

CQM
fonte
4
   Add Button in xml and give onclick attribute name that is the name of Method.
   <!--xml --!>
   <Button
  android:id="@+id/btn_register"
  android:layout_margin="1dp"
  android:onClick="addNumber"
  android:text="Add"
  />


    Button btnAdd = (Button) findViewById(R.id.mybutton); btnAdd.setOnClickListener(new View.OnClickListener() {
   @Override
    public void onClick(View v) {
      addNumber(v);
    }
    });

  Private void addNumber(View v){
  //Logic implement 
    switch (v.getId()) {
    case R.id.btnAdd :
        break;
     default:
        break;
    }}
jeet parmar
fonte
3

Apoiando a resposta do Ruivo, sim, você deve declarar o método como "público" para poder usar o onclick XML do Android - estou desenvolvendo um aplicativo direcionado do nível 8 da API (minSdk ...) a 16 (targetSdk ...).

Eu estava declarando meu método como privado e causou erro, apenas declarando-o como público funciona muito bem.

Waqas Hasan
fonte
parece que as variáveis ​​declaradas na classe da atividade de hospedagem não podem ser usadas no escopo do retorno de chamada declarado; o Bundle Activity.mBundle lançará um IllegalStateException / NullPointerException se usado em myFancyMethod ().
Quasaur 27/02
2

Tenha cuidado, embora o android:onClickXML pareça ser uma maneira conveniente de lidar com o clique, a setOnClickListenerimplementação faz algo adicional do que adicionar o onClickListener. De fato, colocou a propriedade view clickableem true.

Embora possa não ser um problema na maioria das implementações do Android, de acordo com o construtor de telefone, o botão é sempre padrão como clickable = true, mas outros construtores em alguns modelos de telefone podem ter um clickable = false padrão nas visualizações que não são do botão.

Portanto, definir o XML não é suficiente, você deve pensar o tempo todo para adicionar android:clickable="true" um botão não, e se você tiver um dispositivo em que o padrão seja clicável = true e você esquecer uma única vez para colocar esse atributo XML, não perceberá o problema em tempo de execução, mas receberá o feedback do mercado quando ele estiver nas mãos de seus clientes!

Além disso, nunca podemos ter certeza de como o programa irá ofuscar e renomear atributos XML e método de classe, portanto, não é 100% seguro que eles nunca terão um bug um dia.

Portanto, se você nunca quiser ter problemas e nunca pensar nisso, é melhor usar setOnClickListenerou bibliotecas como o ButterKnife com anotações@OnClick(R.id.button)

Livio
fonte
O onClickatributo XML também define clickable = trueporque chama setOnClickListenerna Viewinternamente
Florian Walther
1

Suponha, você deseja adicionar um evento de clique como este main.xml

<Button
    android:id="@+id/btn_register"
    android:layout_margin="1dp"
    android:layout_marginLeft="3dp"
    android:layout_marginTop="10dp"
    android:layout_weight="2"
    android:onClick="register"
    android:text="Register"
    android:textColor="#000000"/>

No arquivo java, você deve escrever um método como este.

public void register(View view) {
}
nazrul islam
fonte
0

Estou Escreva este código no arquivo xml ...

<Button
    android:id="@+id/btn_register"
    android:layout_margin="1dp"
    android:layout_marginLeft="3dp"
    android:layout_marginTop="10dp"
    android:layout_weight="2"
    android:onClick="register"
    android:text="Register"
    android:textColor="#000000"/>

E escreva esse código no fragmento ...

public void register(View view) {
}
user2786249
fonte
Isso é possível em fragmento.
user2786249
0

A melhor maneira de fazer isso é com o seguinte código:

 Button button = (Button)findViewById(R.id.btn_register);
 button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //do your fancy method
            }
        });
Katarina Dabo
fonte
Não vejo como isso responde à pergunta - o solicitante deseja evitar a criação de uma classe anônima e, em vez disso, usa um método de classe.
ajshort
0

Para facilitar sua vida e evitar a Classe Anônima em setOnClicklistener (), implemente uma Interface View.OnClicklistener como abaixo:

classe pública YourClass estende CommonActivity implementa View.OnClickListener, ...

isso evita:

btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        yourMethod(v);
    }
});

e vai diretamente para:

@Override
public void onClick(View v) {
  switch (v.getId()) {
    case R.id.your_view:
      yourMethod();
      break;
  }
}
Vicente Domingos
fonte