quinta-feira, outubro 04, 2012

Desenvolvendo para Android: Event Listeners

Todo framework de tratamento de interface gráfica precisa tratar a questão do assincronismo da interação: uma interface gráfica apresenta ao operador um conjunto de elementos com os quais ele pode interagir em qualquer ordem e a qualquer momento.

No Android esta interação é tratada pelos Event Listeners. Embora seja fácil usar isto mecanicamente, os conceitos envolvidos não são totalmente triviais.


O assincronismo da interação das interfaces gráficas é bem diferente da interação com programas de console, como os exemplos típicos dos livros de C. Nos programas de console o programa pára explicitamente aguardando uma interação (tipicamente uma digitação) e prossegue quando ela é completada:
int a,b;

// Aguarda ler dois números
scanf ("%d %d", &a, &b);

// Números foram lidos, mostra a soma
printf ("%d + %d = %d\n", a, b, a+b);
Em uma interface gráfica teríamos, por exemplo, três elementos na tela: os campos para entrada dos números e o botão de soma. O usuário pode interagir livremente com estes elementos e, potencialmente, o programa pode querer saber de cada interação (embora neste exemplo baste tratar o botão).

Se você já conhece alguma coisa de programação Android, então já viu um exemplo como este (copiado diretamente da documentação do Button):
public class MyActivity extends Activity {
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);

         setContentView(R.layout.content_layout_id);

         final Button button = (Button) findViewById(R.id.button_id);
         button.setOnClickListener(new View.OnClickListener() {
             public void onClick(View v) {
                 // Perform action on click
             }
         });
     }
 } 
Mas o que está acontecendo aí?

No Android deve ser definido um listener para as interações (eventos) que desejamos tratar. Os event listeneres são interfaces definidas na classe View. Todas estas interfaces possuem um único método, que será chamado quando o evento ocorrer.

Para tratar um evento Xis de um objeto descendente de View (no exemplo acima, o evento Click do Button), chamamos o método setOnXisListener do objeto, passando a referência de um objeto que implementa a interface View.OnXisListener. Na implementação de View.OnXisListener é definido o método OnXis, que será chamdado quando o evento Xis ocorrer. (É bom você ler isto uma segunda vez).

Como criamos este objeto que implementa a interface desejada? Tenho observado algumas formas, veja qual você acha mais clara ou prática.

A primeira é a usada acima: criar um objeto diretamente a partir da interface (que não deixa de ser uma espécie de classe), colocando o new direto como parâmetro do setOnXisListener. Esta forma é bastante compacta, mas eu acho confusa, principalmente se o método onXis for longo. O objeto listener criado será associado a um único elemento da tela; se preferir você pode fazer o new separado da chamada a setOnXisListener, guardando a referência ao objeto em uma variável.

Existe um outro detalhe a considerar: quando a rotina onXis for chamada, estamos dentro de um objeto da classe OnXisListener, que foi definido dentro do nosso objeto descente de Activity. Se você usar this, estará se referindo ao objeto OnXisListener, para o objecto Activity você precisará escrever MyActivity.this.

Uma segunda forma é você definir uma classe sua que implemente o OnXisListener. Reescrevendo o exemplo acima:
public class MyActivity extends Activity {
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);

         setContentView(R.layout.content_layout_id);

         final Button button = (Button) findViewById(R.id.button_id);
         button.setOnClickListener(new MyButtonClick ());
     }

    private class MyButtonClick implements View.OnClickListener() {
             public void onClick(View v) {
                 // Perform action on click
             }
         }
 }
O código fica um pouco mais longo, mas eu acho que as coisas ficam mais claras.

Por último, você pode implementar o OnXisListener diretamente na classe descendente de Activity:
public class MyActivity extends Activity implements View.OnClickListener {
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);

         setContentView(R.layout.content_layout_id);

         final Button button = (Button) findViewById(R.id.button_id);
         button.setOnClickListener(this);
     }

     public void onClick(View v) {
         // Perform action on click
     }
 }
Note que desta forma podemos passar diretamente this para setOnXisListener. Em onXis, this é o nosso objeto activity. A desvantagem desta forma é que fica mais complicado se você tiver mais de um elemento do mesmo tipo para o qual você quer tratar de forma diferente o mesmo evento. Por exemplo, se você tiver dois botões e usar o código acima com ambos, a rotina onClick será chamada quando qualquer um dos dois for apertado (você pode distinguir o botão pelo objeto v que é passado, v.getId() fornece o id do elemento).

Nenhum comentário: