terça-feira, 20 de agosto de 2013

Android – Trabalhando com o TouchScreen


Um dos recursos mais poderosos e fascinantes no desenvolvimento utilizando o sistema android é a possibilidade de poder interceptar eventos na tela com o toque do dedo, o “TouchScreen”. Temos classes específicas para este trabalho, a minha idéia neste mês é descrever as principais funcionalidades destas classes junto com seus métodos criando exemplos didáticos e de grande utilidade no dia-a-dia.

Eventos de Entrada (“Input Events”)

Existem várias maneiras de interceptar os eventos de interação do usuário com o aplicativo. Ao considerar eventos dentro de sua interface de usuário, a abordagem é capturar os eventos dentro da região denominada “View”, fornecendo os meios para fazê-lo. Quando compomos um layout, fazemos o uso de quantas “Views” forem necessárias, neste caso, podemos observar vários métodos de retorno de chamada pública que parecem úteis para os denominados eventos de InPut(entrada) e OutPut(saída). Esses métodos são chamados pelo Android quando a respectiva ação ocorre no objeto em questão. Por exemplo, quando um componente (um botão) é tocado, o método “onTouchEvent()” é acionado no objeto. No entanto, a fim de interceptar isso, você deve estender a classe e substituir o método. No entanto, ampliando cada objeto “View”, a fim de lidar com um evento como esse não seria prático. É por isso que a classe “View” também contém uma coleção de interfaces aninhadas com retornos de chamada que poderemos utilizar com uma maior facilidade. Estas interfaces, chamadas de “Listeners” (“ouvintes”), são o seu “bilhete” para capturar a interação do usuário com a interface do aplicativo. Temos a possibilidade de estender uma classe “View”, a fim de construir por exemplo um componente personalizado. Outra situação é estender a classe “Button” para fazer algo mais sofisticado. Neste caso, poderemos definir os comportamentos de eventos padrão para sua classe usando os manipuladores de eventos, os (“Event Handlers”)

Manipuladores de Eventos (“Event Handlers”)

De acordo com a própria documentação do Android, quando estamos construindo um componente a partir da classe “View” teremos vários métodos para serem implementados, sendo:
onKeyDown (int, KeyEvent) - Chamado quando apertamos alguma tecla.
onKeyUp (int, KeyEvent) - Chamado quando soltamos alguma tecla.
onTrackballEvent (MotionEvent) - Chamado quando ocorre um evento de movimento.
onTouchEvent (MotionEvent) - Chamado quando ocorre um evento de movimento da tela com o toque do dedo.
onFocusChanged (boolean, int, Rect) - Chamado quando recebemos ou perdemos o foco.
Demonstrei os métodos acima a fim de apresentar os manipuladores existentes. Daremos importância ao evento “OnTouchEvent”.

Entendendo a classe “MotionEvent”

A classe base para suporte TouchScreen é a classe “MotionEvent” onde é passado para as “Views” através do método onTouchEvent () (abordado anteriormente) podendo também ser sobrecarregado . Vejamos na Imagem 01 a hierarquia desta classe para maiores informações.

Figura 01: Estrutura da classe “MotionEvent”.

A classe “MotionEvent” contém inúmeros métodos e constantes como por exemplo: informações das coordenadas X, Y e capturas de movimentos.

Principais métodos e constantes

- GetAction(): Este método recebe a ação que foi executada, fornecendo as seguintes constantes para determinar a ação.

ACTION_DOWN: Ação que ocorre quando um toque foi iniciado contendo o local de partida inicial do movimento.
ACTION_UP: Esta constante é acionada quando um gesto pressionado tenha terminado, contendo a localização final, bem como todos os pontos intermediários desde o último movimento.
ACTION_MOVE: Ação que ocorre entre as constantes ACTION_DOWN e ACTION_UP. (A movimentação em si)

Todas as constantes abordadas acima retornam um valor inteiro.

- GetY(): Este método retorna um valor do tipo “Float” das coordenadas “Y” (linha vertical). Este valor pode ser transformado para inteiro.
- GetX():Este método retorna um valor do tipo “Float” das coordenadas “X” (Linha horizontal). Este valor pode ser transformado para inteiro.

Exemplo 1 - Movimentando uma Imagem com o toque do dedo

Nos exemplos deste artigo iremos envolver todos os conceitos aprendidos anteriormente sendo que neste primeiro criaremos uma imagem dinamicamente. Esta imagem será movimentada com o dedo e usando o “TouchScreen” na tela do dispositivo. Para isto descreverei todos os passos daqui pra frente.
Inicie uma aplicação do Zero clicando em “File/New/Android Project...” criando uma classe para estender (extends) para a interface “View”. Clique sobre o pacote e com o botão direito escolha “New/Class”. É nesta classe onde criaremos todos os métodos responsáveis pela movimentação da Imagem em questão. Ver Figura 02.


Figura 02: Criando a classe “CriarImagem.java”.

Como foi discutido anteriormente, devemos utilizar a classe “View” para implementarmos o método OnTouchEvent().
Classe “CriarImagem.java”
Antes de iniciarmos a codificação desta classe será necessário adicionar uma imagem qualquer em nosso aplicativo. Basta copiar e colar no diretório “res/drawable-hdpi”. Ver Imagem 03.

Figura 03: Imagem adicionada no projeto.

Observação Importante: 

Esta imagem deverá ser do tipo “.png” ou “jpg” possuindo o nome escrito especificamente em caixa baixa para assim evitar problemas de compilação.

Logo em seguida importaremos as bibliotecas que serão utilizadas ao decorrer do desenvolvimento.

package pct.Android_OnTouchEvent;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class CriarImagem extends View
{
     private Drawable imagem;
     private int x, y, largura, altura;
     private boolean Clicou;
     public int movimento = 12;

A classe principal “CriarImagem” herdará da classe “View”. Utilizaremos algumas variáveis privadas, sendo uma do tipo “Drawable” responsável por trabalhar com a imagem, outras do tipo “int” onde definimos a largura, altura, as coordenadas X, Y e uma booleana indicando se clicamos ou não na imagem.

public CriarImagem(Context context, AttributeSet attrs)
{
     super(context, attrs);
imagem =    context.getResources().getDrawable(R.drawable.theclub);
     largura = imagem.getIntrinsicWidth();
     altura = imagem.getIntrinsicHeight();
     x = 150;
     y = 300;
     setFocusable(true);
}

No método Construtor passamos dois parâmetros: o “Context” o Contexto da aplicação e o “AttributeSet” que significa uma coleção de atributos que podem ser utilizados dentro deste método.  O método “context.getResources().getDrawable()” irá recuperar a imagem, os métodos “getIntrinsicWidth()” e “getIntrinsicHeight()” irão retornar a largura e altura respectivamente, x e y serão as coordenadas para posicionamento da imagem em relação a tela do dispositivo. Já o método “setFocusable()” definirá se a “view” irá ou não receber o foco enquanto estiver em modo “TouchScreen”. Deixaremos como “true”

@Override
protected void onDraw(Canvas canvas)
{
     super.onDraw(canvas);
     imagem.setBounds(x,y,x+largura,y+altura);
     imagem.draw(canvas);
}


Para desenhar a imagem foi necessário reescrever o método “OnDraw()” tendo como parâmetro a classe “Canvas”. Passamos as coordenadas X e Y e a largura e altura utilizando o método “setBounds()” e redesenhamos com o “draw()” usando a classe “Canvas”. Este método é disparado sempre quando necessitamos redesenhar a imagem.


public boolean onTouchEvent(MotionEvent motionEvent)
{
     this.x = (int)motionEvent.getX();
     this.y = (int) motionEvent.getY();
        
     switch(motionEvent.getAction())
     {
         case MotionEvent.ACTION_DOWN:
         {
              Clicou = imagem.copyBounds().contains(x,y);
              break;
         }
                    
         case MotionEvent.ACTION_MOVE:
          if (Clicou)
          {
              this.x = x - (largura/2);
              this.y = y - (altura/2);
              break;
          }
                                          
         case MotionEvent.ACTION_UP :
         {
              Clicou = false;
              break;
         }
      }
        
     invalidate();
     return true;
}

Este é o principal evento que iremos trabalhar. Ele tem como parâmetro de entrada a classe “MotionEvent” e retorna um valor booleano. No primeiro momento iremos atribuir às variáveis globais X e Y as respectivas coordenadas com os métodos “getX()” e “getY()”. Faremos um “Case” para comparar as Constantes do método “GetAction()”, o mesmo que recebe a ação que foi executada, fornecendo constantes para determinar a ação. Na constante “ACTION_DOWN” com o método “copyBounds()” indicamos que clicamos na imagem informando suas coordenadas. A constante “ACTION_MOVE”, faz o ato de mover em si, movimentamos a figura fazendo o cálculo das coordenadas menos o tamanho dividido por dois, dando uma impressão de movimento. Por final retiramos o foco com a constante “ACTION_UP”. Resumindo, este método irá movimentar a Imagem conforme o toque do dedo.

Configurações no “AndroidManifest.xml”

Nas configurações do arquivo “AndroidManifest.xml” não faremos praticamente nada de mirabolante, apenas setamos o uso da “Activity” “Android_OnTouchEventActivity” que será comentado logo a seguir. Ver em seguida a listagem completa.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="pct.Android_OnTouchEvent"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="8" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".Android_OnTouchEventActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>

Configurações no “Strings.xml”

Este arquivo irá definir algumas constantes que serão abordadas ao decorrer do projeto, como por exemplo: a cor cinza que preencherá o fundo onde movimentaremos a imagem. Ver código abaixo:.

<?
xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Trabalhando com o evento OnTouchEvent!</string>
    <string name="app_name">The Club - Android</string>
    <color name="cinza">#D0D0D0</color>
</resources>

Criando o “Lay-Out”

O aplicativo possuirá um “LinearLayout” onde conterá um “TextView” e um componente chamado desenho que faz referência à classe “CriarImagem”. Ver Imagem 04.

Figura 04: Componentes utilizados.

A Imagem 05 nos dá uma noção do Lay-out.

Figura 05: Lay-Out do exemplo 1.

O XML correspondente:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView 
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
<pct.Android_OnTouchEvent.CriarImagem
     android:id="@+id/desenho"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:background="@color/cinza"   
     />   
</LinearLayout>

Devemos dar uma atenção especial na linha de código abaixo:

<
pct.Android_OnTouchEvent.CriarImagem />


Percebam que interessante, conseguimos chamar diretamente uma classe (no caso uma “View”) de dentro do XML, é fantástico!

Codificando o exemplo 1

Seguindo a lógica, a atividade “Android_OnTouchEventActivity” que codificaremos a seguir, possuirá uma chamada para o lay-out “criarimagem.xml” que dentro do mesmo teremos a classe “CriarImagem.java”, na qual está localizada toda a codificação de nosso exemplo.

package pct.Android_OnTouchEvent;

import android.app.Activity;
import android.os.Bundle;

public class Android_OnTouchEventActivity extends Activity
{            
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.criarimagem);
    }
    
}
O “SetContentView()” será o método que irá chamar a tela de nosso aplicativo. Ver Imagem 06.

Exemplo em “Run-Time”

Figura 06: Movimentando a Imagem “The Club”.

Exemplo 2 – Pintando e escrevendo na Tela com o toque do dedo

Este segundo exemplo irá pintar e desenhar na tela de nosso aplicativo com o toque do dedo. Aprenderemos também a trabalhar com a classe “Paint”, “Path” entre outras. Os passos são idênticos ao do exemplo anterior, por isto detalharei um pouco menos para ficar menos cansativo abordando apenas os conceitos inéditos.
Chega de conversa e vamos ao trabalho!

Classe “PintarTela.java”

Utilizaremos algumas bibliotecas adicionais para desenvolver este exemplo, veja abaixo:

package
pct.Android_OnTouchEvent;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

Temos a classe “android.graphics.Paint” sendo específica para pintura, estilo, cor. Ela também oferece base para desenhar geometrias, texto e bitmaps. Já a classe “android.graphics.Path” permite realizar contornos e formas geométricas como: segmentos de linhas retas, curvas quadráticas e cúbicas.

public class PintarTela extends View
{   
     private Paint paint = new Paint();
     private Path path = new Path();

Esta classe também herdará da classe “View” e utilizará dois objetos, do tipo “Paint” e “Path”.

      public PintarTela(Context context, AttributeSet attrs)
     {
        super(context, attrs);

        paint.setAntiAlias(true);
        paint.setStrokeWidth(6);
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeJoin(Paint.Join.ROUND);
      }

Temos o método “setAntiAlias()” que serve para suavizar as bordas de nosso toque, o “setStrokeWidh()” é o tamanho da linha, o “setColor()” para definir a cor, o “setStyle()” o estilo e o “setStrokeJoin()” é um tratamento específico para quando as linhas se juntarem.

        @Override
       protected void onDraw(Canvas canvas)
       {
         canvas.drawPath(path, paint);
       }

Método que irá desenhar usando as configurações definidas anteriormente na classe “Paint”.

        @Override
       public boolean onTouchEvent(MotionEvent motionEvent)
       {
           float eventX = motionEvent.getX();
          float eventY = motionEvent.getY();

          switch (motionEvent.getAction())
          {
              case MotionEvent.ACTION_DOWN:
                  path.moveTo(eventX, eventY);
                  return true;
              case MotionEvent.ACTION_MOVE:
                  path.lineTo(eventX, eventY);
                  break;
                  default:
                  return false;
           }

           invalidate();
           return true;
       }
Usaremos apenas as constantes “ACTON_DOWN” para dar início do movimento de acordo com as coordenadas definidas fazendo o uso do método “MoveTo()” e a “ACTION_MOVE”para adicionar uma linha a partir do último ponto especificado pelas coordenadas (x, y).

Configurações no “AndroidManifest.xml”

Adicionaremos algumas configurações básicas neste arquivo já comentado mais acima, veja o trecho:
...
<activity android:name=".Android_OnTouchEventActivity_2"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
...
Criando o “Lay-Out”

Teremos um “LinearLayout” onde conterá um “TextView” e um componente chamado desenho que faz referência à classe “Pintartela”. Ver Imagem 07.

Figura 07: Lay-Out do exemplo 2.

O XML correspondente:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView 
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
   
<pct.Android_OnTouchEvent.PintarTela
     android:id="@+id/desenho"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     />   
</LinearLayout>

Codificando o Exemplo 2

Seguindo os passos que já foram descritos acima foi necessário criar uma classe estendendo de uma “Activity” para invocarmos o XML contendo a classe “PintarTela”. O método “SetContentView()” irá chamar a tela de nosso aplicativo. Ver Imagem 08.
Abaixo código completo.

package pct.Android_OnTouchEvent;
import android.app.Activity;
import android.os.Bundle;

public class Android_OnTouchEventActivity_2 extends Activity
{   
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.pintartela);
    }
}

Exemplo em “Run-Time”

Figura 08: Desenhando e Pintando em “Run-Time”.

Referências


Conclusões

Uma das formas de interceptar os eventos de interação do usuário com o aplicativo é capturar estes eventos dentro de uma região chamada “View”. É a partir desta classe que discorremos o artigo deste mês. Trabalhamos com o evento “OnTouchEvent()”, que é o evento que capta o toque do dedo na tela do dispositivo e a classe “MotionEvent”, classe base para suporte para esta tarefa junto com métodos e constantes. No primeiro exemplo procurei demonstrar como movimentar uma imagem com o “TouchScreen”, já no segundo como pintar e escrever na tela do aplicativo. Esta é uma das inúmeras formas de usar os recursos “TouchScreen” na programação Android.

Espero que tenham gostado! Deixo aqui um abraço e nos vemos no mês que vem!