Componentes Canvas - Um botão tocável em JME usando Canvas
Ensinarei 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 tocadoif (botaoAlerta.isTouched(x, y))return;//Verifica se o primeiro botão Sair foi tocadoif (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ãoif (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 nuloif (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õesreturn 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 tocadoif (botaoAlerta.isTouched(x, y))return;//Verifica se o primeiro botão Sair foi tocadoif (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 tocadoif (botaoAlerta.isTouched(x, y))return;//Verifica se o primeiro botão Sair foi tocadoif (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ãoif (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 nuloif (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õesreturn 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 telabotaoAlerta.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 graphicsg.drawImage(image, x, y, 0);//Define a fonte que será usada no labelg.setFont(font);//Define a cor da fonte que será usada no labelg.setColor(fontColor);//Pinta o label centralizado usando a fonte e a cor definidasint 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õesint corVermelha = 0×00ff0000;//Define a fonte que será usada no label dos botõesFont fonteBotao = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE);//Define a imagem que será usada no fundo dos botõesImage 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 Canvaspublic int y; //Referência à posição Y que o botão deve ficar no Canvaspublic Image image; //Imagem que deve ser pintada no fundo de cada botãopublic ButtomListener listener; //Listener que implementa os eventos do botão, como o evento touchpublic String label; //Texto que será pintado centralizado no botãopublic int fontColor; //Cor de fonte usada no Labelpublic 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 graphicsg.drawImage(image, x, y, 0);//Define a fonte que será usadag.setFont(font);//Define a cor da fonte que será usadag.setColor(fontColor);//Pinta o label usando a fonte e a cor definidasint 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ãoif (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 nuloif (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õesint corVermelha = 0×00ff0000;//Define a fonte que será usada no label dos botõesFont fonteBotao = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE);//Define a imagem que será usada no fundo dos botõesImage 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 normalmenteg.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 telabotaoAlerta.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 mensagemAlert alert = new Alert(”Tocado no botão.”);//Mostra a mensagem na telaDisplay.getDisplay(Midlet.app).setCurrent(alert, this);} else if (buttom == botaoSair) {//Solicita ao MIDlet para sair da aplicaçãoMidlet.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 clicadoif (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 telapublic void startApp() {//Guarda a referência ao MIDlet para as telas usaremapp = this;//Mostra a tela canvas no display do celularDisplay.getDisplay(app).setCurrent(new TelaCanvas());}public void pauseApp() {}public void destroyApp(boolean unconditional) {}}
Imagem do botão usada no projeto:

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
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.

Junior:
Perfeito….. Valeu mesmo nelson ….
Isso relamente contribuiu com a comunidade…
19 Abril 2009, 12:34 amHermano Soares:
muito bom artigo!
20 Abril 2009, 11:50 amMota:
Nelson, este seu exemplo foi mais que perfeito…
9 Maio 2009, 1:13 pmTe agradeço muito pela idéia.
Zugaib:
Gostaria de saber como simular esses programas com touch.. usava o WTK, mas não achei um device touch…
21 Outubro 2009, 11:21 pmZugaib:
Consegui emular mas ainda não consegui clicar..
21 Outubro 2009, 11:34 pmMaiko 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
23 Outubro 2009, 2:23 pmCopying 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)