Componentes Canvas - Um botão tocável em JME usando Canvas

nokia_touch.jpgEnsinarei como criar um componente reutilizável em Canvas. Criaremos um botão tocável, que você poderá usar em qualquer tela Canvas em JME. Esse botão só deve ser usado em devices Touch Screen.

O funcionamento se dará da seguinte forma. Criaremos uma classe chamada ImageButtom que representará o botão em si. Ele será eficiente de tal forma que será capaz de se pintar na tela Canvas em que ele estiver, e de disparar eventos quando ele for tocado.

Ele pintará uma imagem de fundo, com o design de um botão. Essa imagem poderá ser carregada por ele mesmo, ou poderá ser passada como parâmetro para ele usar. No construtor do botão poderemos informar então o caminho da imagem que ele deve carregar, ou passaremos a imagem diretamente, já carregada, para ele usar. Qual a diferença? Bem, se temos 2 ou mais botões em uma mesma tela, devemos carregar a imagem apenas uma vez em memória e passá-la aos 2 botões. Assim teremos 2 botões usando a mesma imagem em memória. Se passássemos o caminho da imagem para os 2 botões, cada um carregaria a imagem uma vez na memória, desperdiçando memória. Devemos passar o caminho da imagem para o botão apenas quando teremos apenas 1 botão na tela.

Além da imagem, o construtor deverá pedir pela posição X e Y em que o botão deve ser pintado na tela, bem como o label ou texto que deve ser escrito no centro do botão.

Evento de Toque

Como o botão saberá que ele foi tocado? Bem, quando uma tela Canvas é tocada, é chamado o método pointerReleased(x,y) que tem como parâmetros a posição do toque. Isso acontece em toda tela Canvas naturalmente. Precisaremos apenas sobrescrever esse método da tela Canvas e verificar em qual botão da tela o toque aconteceu. Exemplo:

    protected void pointerReleased(int x, int y) {
        //Verifica se o primeiro botão Alerta foi tocado
        if (botaoAlerta.isTouched(x, y))
            return;

        //Verifica se o primeiro botão Sair foi tocado
        if (botaoSair.isTouched(x, y))
            return;
    }

Como verificar em qual botão o toque ocorreu?

Os botões serão inteligente o bastante para saberem se o toque em X,Y ocorreu dentro de si mesmo. Escreveremos um método no botão chamado isTouch(x,y) e esse método fará cálculos para verificar se a posição X,Y informada está dentro da imagem do botão que é pintada na tela. Como ele fará isso? Ora, cada botão sabe a posição X,Y que ele deve ser pintado. Ele sabe também a largura e altura da imagem que ele estará pintando, imagem esta que representa o botão visualmente. Com esses valores é fácil saber se o X,Y onde ocorreu o toque foi dentro dele mesmo. Esse método isTouch(x,y) nos botões deve retornar TRUE quando o toque ocorreu dentro dele e FALSE quando não ocorreu dentro dele.

Veja um exemplo:

public boolean isTouched(int x, int y) {
    //Verifica se o X e Y do clique está dentro da imagem do botão
    if (x >= this.x && y >= this.y && x <= this.x + getWidth() && y <= this.y + getHeight()) {
        //Visto que foi clicado no botão, chama o listener de evento se ele
        //não estiver nulo
        if (listener != null)
            listener.onButtomTouch(this);
        return true; //Retorna true para a tela, para que ela não verifique os próximos botões
    }
    //Retorna false para a tela, para que ela continue verificando os próximos botões
    return false; 
}

Assim, na tela Canvas que recebe o toque real no método pointerReleased(x,y), chamaremos o método isTouch(x,y) de cada um dos botões, para que cada botão verifique se o toque ocorreu dentro dele mesmo. Quando um dos botões retornar TRUE, pode-se parar de chamar o método dos outros botões, pois já foi encontrado o botão onde o toque ocorreu.

Veja novamente o evento de toque da tela Canvas. Note que ele chama o isTouch de cada um dos botões.

    protected void pointerReleased(int x, int y) {
        //Verifica se o primeiro botão Alerta foi tocado
        if (botaoAlerta.isTouched(x, y))
            return;

        //Verifica se o primeiro botão Sair foi tocado
        if (botaoSair.isTouched(x, y))
            return;
    }

Comandos a serem exectados quando o botão é tocado

Quando os botões forem tocados eles devem executar trechos de códigos na aplicação. Por exemplo, quando o botão “Mostrar Mensagem” for tocado, ele deve executar algum código que mostre uma mensagem na tela. Quando o botão “Sair” for tocado, ele deve executar um trecho de código que feche a aplicação. Esse trecho de código deve ser executado de acordo com o botão que foi tocado.

Para isso, o botão precisará de um listener que será chamado quando ele for tocado. Funcionará de forma semelhante ao Command e o CommandListener usados no JME. Ou seja, passamos para o botão a classe que implementa o ButtomListener. Esse listener terá um método chamado onButtomTouch. A tela Canvas que contém os botões deve implementar esse listener, escrevendo em na sua implementação do método onButtomTouch o que deve acontecer quando cada botão for tocado. Quando o botão é tocado, ele chamará um método onButtomTouch desse listener.

Quando um botão sabe que foi tocado? Quando é chamado o isTouch dele. Por isso, o botão chamará o listener dentro do isTouch quando ele verificar que foi tocado.

Veja um exemplo bem básico da tela implementado esse listener:

public class TelaCanvas extends Canvas implements ButtomListener {
    public TelaCanvas() {
    }
    public void onButtomTouch(ImageButtom buttom) {
        if (buttom == botaoAlerta) {
            //Trecho que deve ser executado quando o botão Alerta for tocado
        } else if (buttom == botaoSair) {
            //Trecho que deve ser executado quando o botão Sair for tocado
        }
    }

Resumindo o funcionamento

Então, quando o usuário toca na tela Canvas, a tela Canvas executa o método pointerReleased(x,y):

    protected void pointerReleased(int x, int y) {
        //Verifica se o primeiro botão Alerta foi tocado
        if (botaoAlerta.isTouched(x, y))
            return;

        //Verifica se o primeiro botão Sair foi tocado
        if (botaoSair.isTouched(x, y))
            return;
    }

Esse método chama o isTouch de cada um dos botões que a tela possui, até um deles retornar True. Quando um botão retorna true, significa que o toque ocorreu dentro dele. Então ele executa o método onButtomTouch do listener, passando ele mesmo (o próprio botão) como parâmetro:

public boolean isTouched(int x, int y) {
    //Verifica se o X e Y do clique está dentro da imagem do botão
    if (x >= this.x && y >= this.y && x <= this.x + getWidth() && y <= this.y + getHeight()) {
        //Visto que foi clicado no botão, chama o listener de evento se ele
        //não estiver nulo
        if (listener != null)
            listener.onButtomTouch(this);
        return true; //Retorna true para a tela, para que ela não verifique os próximos botões
    }
    //Retorna false para a tela, para que ela continue verificando os próximos botões
    return false; 
}

O método onButtomTouch pertencente ao Listener foi implementado na tela com o trecho de código que deve ser executado quando cada um dos botões for clicado. Então ele verifica qual dos botões veio no parâmetro e executa o código de acordo.

    public void onButtomTouch(ImageButtom buttom) {
        if (buttom == botaoAlerta) {
            //Trecho que deve ser executado quando o botão Alerta for tocado
        } else if (buttom == botaoSair) {
            //Trecho que deve ser executado quando o botão Sair for tocado
        }
    }

E assim você tem um botão componentizável, reutilizável.

Mas como o botão é pintado na tela?

Bem, toda tela Canvas tem que implementar o método paint, responsável por pintar ela mesma. Assim, os botão devem ser pintados dentro desse método. No entanto, para que fique facilmente reutilizável, criaremos um métod paint no próprio botão, para que ele mesmo se pinte. O próprio botão deve saber se pintar na tela. Bastará então chamarmos esse método paint de cada botão dentro no método paint da tela Canvas. Isso fará com que cada um dos botões que estão na tela se pintem dentro da própria tela que eles estão.

Veja um exemplo do método paint da tela Canvas chamando o método paint dos botões:

    protected void paint(Graphics g) {
        //Pede aos botões para eles se pintarem no Graphics da tela
        botaoAlerta.paint(g);
        botaoSair.paint(g);
    }

Agora veja o método paint de cada um dos botões:

    public void paint(Graphics g) {
        //Pinta a image do botão no graphics
        g.drawImage(image, x, y, 0);
        //Define a fonte que será usada no label
        g.setFont(font);
        //Define a cor da fonte que será usada no label
        g.setColor(fontColor);
        //Pinta o label centralizado usando a fonte e a cor definidas
        int labelX = x + getCenterWidth();
        int labelY = y + getCenterHeight() - (font.getHeight() / 2);
        g.drawString(label, labelX, labelY, Graphics.HCENTER | Graphics.TOP);
    }

Como instancio os botões na tela?

É fácil, basta chamar o construtor passando os parâmetros que ele precisa, como a imagem, o label, a cor, etc. Mas é importante que a variável que guardará o botão seja um campo da classe, para que outros métodos da classe possam chamar métodos os botões, e para que a tela saiba qual botão foi clicado.

Note os parâmetros que o construtor do botão precisa:

public ImageButtom(int x, int y, Image image, ButtomListener listener, 
                   String label, int fontColor, Font font)

Notamos aí que ele precisa das posições X,Y, precisa da imagem que ele deve pintar no fundo, precisa de uma instância do listener que executará os eventos de toque quando ele for tocado, precisa do label e da fonte e cor do label.

Veja um exemplo que instancia 2 botões no construtor da tela Canvas.

public class TelaCanvas extends Canvas implements ButtomListener {

    public ImageButtom botaoAlerta, botaoSair;

    public TelaCanvas() {
        //Define a cor que será usada no label dos botões
        int corVermelha = 0×00ff0000;

        //Define a fonte que será usada no label dos botões
        Font fonteBotao = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE);

        //Define a imagem que será usada no fundo dos botões
        Image imagemBotao = getImagemBotao(); //Método criado na tela apenas para carregar a imagem

        //Instancia o botão passando o X, Y, a imagem que ele deve mostrar,
        //e o listener que executará o evento touch do botão. O Listener é a
        //própria classe dessa tela, pois ela está implementando a interface que
        //criamos para disparar os eventos, chamada ButtomListener.
        botaoAlerta = new ImageButtom(5, 30, imagemBotao, this, “Alerta”, corVermelha, fonteBotao);
        botaoSair = new ImageButtom(5, 80, imagemBotao, this, “Sair”, corVermelha, fonteBotao);
    }

Código fonte completo

Colocarei aqui o código fonte completo do projeto. No final você encontra ele para download.

Arquivo ButtomListener.java
Contém o listener dos botões:

/**
 * Listener dos eventos do ImageButtom. Sempre que se instancia um ImageButtom
 * deve-se informar a classe que está implementando esta interface. É comum
 * fazer com que a própria tela que contém os botões implemente esta interface
 * pois ela deve tratar os eventos dos botões.
 * 
 * @author Nelson Pereira Junior
 */
public interface ButtomListener {

    /**
     * Método chamado pelos botões quando são tocados.
     * As telas devem implementar este método para interceptar o evento de toque
     * nos botões.
     */
    public void onButtomTouch(ImageButtom buttom);

}

Arquivo ImageButtom.java
Contém a impleemntação da classe de botão:

import java.io.IOException;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
/**
 * @author Nelson Pereira Junior
 */
public class ImageButtom {
    public int x; //Referência à posição X que o botão deve ficar no Canvas
    public int y; //Referência à posição Y que o botão deve ficar no Canvas
    public Image image;  //Imagem que deve ser pintada no fundo de cada botão
    public ButtomListener listener; //Listener que implementa os eventos do botão, como o evento touch
    public String label; //Texto que será pintado centralizado no botão
    public int fontColor; //Cor de fonte usada no Label
    public Font font; //Fonte usada no Label
    /**
     * Instancia um ImageButtom informando o caminho da imagem que ele deve usar como fundo.
     */
    public ImageButtom(int x, int y, String imagePath, ButtomListener listener, String label, int fontColor, Font font) {
        this.label = label;
        this.x = x;
        this.y = y;
        this.listener = listener;
        this.fontColor = fontColor;
        this.font = font;
        try {
            image = Image.createImage(imagePath);
        } catch (IOException ex) {
        }
    }
    /**
     * Instancia um ImageButtom informando a imagem que ele deve usar como fundo.
     */
    public ImageButtom(int x, int y, Image image, ButtomListener listener, String label, int fontColor, Font font) {
        this.label = label;
        this.x = x;
        this.y = y;
        this.listener = listener;
        this.image = image;
        this.fontColor = fontColor;
        this.font = font;
    }
    public void paint(Graphics g) {
        //Pinta a image do botão no graphics
        g.drawImage(image, x, y, 0);
        //Define a fonte que será usada
        g.setFont(font);
        //Define a cor da fonte que será usada
        g.setColor(fontColor);
        //Pinta o label usando a fonte e a cor definidas
        int labelX = x + getCenterWidth();
        int labelY = y + getCenterHeight() - (font.getHeight() / 2);
        g.drawString(label, labelX, labelY, Graphics.HCENTER | Graphics.TOP);
    }
    public int getWidth() {
        return image.getWidth();
    }
    public int getHeight() {
        return image.getHeight();
    }
    public int getCenterWidth() {
        return image.getWidth() / 2;
    }
    public int getCenterHeight() {
        return image.getHeight() / 2;
    }
    /**
     * Esse método é chamado pela tela Canvas, quando ela recebe um toque.
     * Então ela chama esse método de todos os botões na tela, para cada um
     * deles verificar se ele foi clicado. Quando um botão verificar que foi
     * clicado ele deve retornar TRUE, para que a tela não continue verificando
     * os outros botões desnecessariamente.
     */
    public boolean isTouched(int x, int y) {
        //Verifica se o X e Y do clique está dentro da imagem do botão
        if (x >= this.x && y >= this.y && x <= this.x + getWidth() && y <= this.y + getHeight()) {
            //Visto que foi clicado no botão, chama o listener de evento se ele
            //não estiver nulo
            if (listener != null)
                listener.onButtomTouch(this);
            return true; //Retorna true para a tela, para que ela não verifique os próximos botões
        }
        return false; //Retorna false para a tela, para que ela continue verificando os próximos botões
    }
}

Arquivo TelaCanvas.java
Contém a impleemntação da classe de tela que estende Canvas:

import java.io.IOException;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

/**
 * Tela Canvas de exemplo, que mostra 2 botões na tela.
 * 
 * @author Nelson Pereira Junior
 */
public class TelaCanvas extends Canvas implements ButtomListener {

    public ImageButtom botaoAlerta, botaoSair;

    public TelaCanvas() {
        //Define a cor que será usada no label dos botões
        int corVermelha = 0×00ff0000;

        //Define a fonte que será usada no label dos botões
        Font fonteBotao = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE);

        //Define a imagem que será usada no fundo dos botões
        Image imagemBotao = getImagemBotao();

        //Instancia o botão passando o X, Y, a imagem que ele deve mostrar,
        //e o listener que executará o evento touch do botão. O Listener é a
        //própria classe dessa tela, pois ela está implementando a interface que
        //criamos para disparar os eventos, chamada ButtomListener.
        botaoAlerta = new ImageButtom(5, 30, imagemBotao, this, “Alerta”, corVermelha, fonteBotao);
        botaoSair = new ImageButtom(5, 80, imagemBotao, this, “Sair”, corVermelha, fonteBotao);
    }

    protected void paint(Graphics g) {
        //Pinta a tela Canvas normalmente
        g.setColor(0×00dddddd);
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setColor(0×00ff0000);
        g.setFont(Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE));
        g.drawString(”Exemplo de botão”, 10, 10, 0);

        //Pede aos botões para eles se pintarem no Graphics da tela
        botaoAlerta.paint(g);
        botaoSair.paint(g);
    }

    /**
     * Esse método é do próprio listener de evento bos botões. Ele é chamado pelos
     * dois botões, quando os botões são clicados. O parâmetro retornado no método
     * se refere à própria instância do botão.
     */
    public void onButtomTouch(ImageButtom buttom) {
        if (buttom == botaoAlerta) {
            //Cria o alerta de mensagem
            Alert alert = new Alert(”Tocado no botão.”);
            //Mostra a mensagem na tela
            Display.getDisplay(Midlet.app).setCurrent(alert, this);
        } else if (buttom == botaoSair) {
            //Solicita ao MIDlet para sair da aplicação
            Midlet.app.notifyDestroyed();
        }
    }

    /**
     * Método que carrega as imagens usadas nesta tela.
     */
    private Image getImagemBotao() {
        try {
            return Image.createImage(”/imagem_botao.png”);
        } catch (IOException ex) {
            return null;
        }
    }

    /**
     *  Esse método é chamado quando o usuário pressiona a tela touch com o dedo
     *  ou com a caneta. São informados nos parâmetros a posição X e Y que o
     *  toque ocorreu. Assim, deve-se pedir a cada um dos botões para eles
     *  verificarem se o toque X,Y ocorreu dentro deles. Se ocorrer dentro
     *  deles eles próprios chamarão o listener de evento onButtomTouch.
     */
    protected void pointerReleased(int x, int y) {
        //Verifica se o primeiro botão foi clicado
        if (botaoAlerta.isTouched(x, y))
            return;

        if (botaoSair.isTouched(x, y))
            return;
    }

}

Arquivo Midlet.java
Contém a impleemntação do Midlet:

import javax.microedition.lcdui.Display;
import javax.microedition.midlet.*;
/**
 * @author Nelson Pereira Junior
 */
public class Midlet extends MIDlet {
    public static Midlet app; //Referencia ao MIDlet para ser usada pela tela
    public void startApp() {
        //Guarda a referência ao MIDlet para as telas usarem
        app = this;
        //Mostra a tela canvas no display do celular
        Display.getDisplay(app).setCurrent(new TelaCanvas());
    }
    public void pauseApp() {
    }
    public void destroyApp(boolean unconditional) {
    }
}

Imagem do botão usada no projeto:

imagem_botao.png

Código fonte completo do projeto para ser usado no NetBeans 6.x: projeto.zip

Baixar JAR e JAD: JAR_e_JAD.zip



Sobre o Autor

Este artigo foi escrito por Nelson Pereira Junior.
Nelson é desenvolvedor há 12 anos. Hoje desenvolve aplicações Web e Móveis na Abacomm Brasil cuidando do desenvolvimento server-side J2EE, banco de dados, design de aplicações móveis, e desenvolvimento móvel usando várias plataformas como BlackBerry, J2ME, FlashLite, Android, etc. Para conversar com o autor use o e-mail, MSN e GTalk npereirajr@gmail.com.



Receba artigos em seu e-mail

Receba os novos artigos do blog em seu e-mail. E-Mail:



6 Comentários

  1. Junior:

    Perfeito….. Valeu mesmo nelson ….

    Isso relamente contribuiu com a comunidade…

  2. Hermano Soares:

    muito bom artigo!

  3. Mota:

    Nelson, este seu exemplo foi mais que perfeito…
    Te agradeço muito pela idéia.

  4. Zugaib:

    Gostaria de saber como simular esses programas com touch.. usava o WTK, mas não achei um device touch…

  5. Zugaib:

    Consegui emular mas ainda não consegui clicar..

  6. Maiko Cella:

    Olá tudo bem??
    gostaria que alguém me ajudasse….
    estah dando esse erro no meu netbeans….

    aguardo resposta

    Copying 1 file to C:\Users\Cella\Desktop\Globo\dist\nbrun15614
    Copying 1 file to C:\Users\Cella\Desktop\Globo\dist\nbrun15614
    Jad URL for OTA execution: http://localhost:8082/servlet/org.netbeans.modules.mobility.project.jam.JAMServlet/C%3A/Users/Cella/Desktop/Globo/dist//Globo.jad
    Starting emulator in execution mode
    *** Error ***
    Failed to connect to device 9!
    Reason:
    Emulator 9 terminated while waiting for it to register!
    ricoh-run:
    semc-icon-assembly:
    semc-ppro-emulator:
    semc-do-run:
    semc-run:
    savaje-run:
    sjmc-run:
    nokiaS80-run:
    nsicom-run:
    cdc-hi-run:
    profiler.check:
    open-profiler:
    run:
    CONSTRUÍDO COM SUCESSO (tempo total: 3 segundos)

Deixe um comentário

blogarama.com Globe of Blogs EatonWeb Blog Directory