Introdução
No post anterior (Java from Scratch – Parte 05), separamos a aplicação da classe que desenha e anima as bolinhas, vimos o que é um package, e uma forma simples de gerar um arquivo único para a aplicação (JAR). Agora, vamos aproveitar esse código, e criar um Ping-Pong com interface gráfica 😀
Interface gráfica com Java
O Java tem suporte nativo para aplicações com interface gráfica (Janelas/GUI) através de duas bibliotecas: AWT e SWING. A AWT usa e depende de recursos do sistema operacional em uso no equipamento para desenhar as telas, enquanto a SWING é independente de plataforma — ela é a responsável pela renderização — entre outras diferenças. Embora existam diferenças de “peso” de execução entre os componentes de ambas, Swing possui mais funcionalidades, suporte a MVC, features avançadas, etc.
Como desenhar gráficos na interface ?
Bem, essa pergunta consumiu algumas horas de pesquisa… Os componentes padrão quando se cria uma aplicação com interface gráfica (GUI), normalmente usamos componentes que desenham-se na interface, e podem usar painéis e mecanismos de alinhamento… como eu quero desenhar uma ou mais “bolinhas”, existem duas alternativas: Usar uma imagem de uma bolinha, ou o modo “roots” : Fazer o desenho de uma bolinha 😀
Vamos no modo “roots”
A pesquisa e o modo encontrado foi — segundo as fontes pesquisadas — a forma mais “elegante” de se implementar o mecanismo de pintura da tela. Eu consigo desenhar um círculo usando o componente “java/awt/geom/Ellipse2D”, então eu crio uma classe especialista para o PingPong — chamada PingPongPanel, herdando um JPanel — acrescento o painel como o conteúdo do meu JFrame, e sobrescrevo o método responsável pela pintura dos componentes do painel, para também pintar os círculos que eu quero, na posição da tela desejadas para cada círculo. Ao contrário da implementação original em modo texto, eu não “apago” cada bolinha, eu simplesmente redesenho a tela com as bolinhas nas posições novas, no loop de execução do programa principal.
A classe PingPong2, que no post anterior virou um componente do pacote siga0984, também precisa de alterações. Primeiro, o método PingStep() inicialmente implementado também apagava e desenhava as bolinhas, e ele não previa um passo de movimento maior que um , e nem uma “margem” para verificar se uma bolinha vai bater no limite da tela … Então, vamos começar pela “nova classe” PingPong2:
*** arquivo siga0984/PingPong2.java ***
package siga0984; import java.util.Random; public class PingPong2 { private static Random rand = new Random(); private int row,col; private int ballsize = 1, step = 1; private int rowinc = 1 ,colinc = 1; private int maxrow = 25; private int maxcol = 80; public int getRow() { return row; }; public int getCol() { return col; }; public void setSize( int r ) { ballsize = r; } public void setStep( int s ) { step = s; } public void setBounds( int mrow, int mcol ) { maxrow = mrow; maxcol = mcol; } public void InitBall() { row = rand.nextInt(maxrow-ballsize)+1; col = rand.nextInt(maxcol-ballsize)+1; if ( rand.nextInt(2) > 0 ) rowinc = -1 ; if ( rand.nextInt(2) > 0 ) colinc = -1 ; } public void PingStep() { if ( row + ( step * rowinc ) < 1 || row + ballsize + ( step * rowinc ) >= maxrow ) rowinc = -rowinc; row += ( step * rowinc ); if ( col + ( step * colinc ) < 1 || col + ballsize + ( step * colinc ) >= maxcol ) colinc = -colinc; col += ( step * colinc ); } }
Agora a nossa classe de PingPong possui mais propriedades:
row e col — Já existentes, correspondem a coordenada de tela (linha e coluna respectivamente, em modo texto ou gráfico) onde a bolinha será desenhada.
ballsize — Corresponde ao tamanho da bolinha, valor dafault = 1.
step — Corresponde ao “passo”, isto é, quantas unidades serão acrescentadas nas coordenadas ao mover a bolinha em uma determinada direção — default = 1.
rowinc e colinc — permanecem iguais, eles apenas tem o valor indicador de direção ( 1 ou -1 )
maxrow — Numero de linhas disponíveis na tela, seja modo texto ou gráfico, default = 25 para modo texto.
maxcol — Numero de colunas disponíveis na tela, seja modo texto ou gráfico, default = 80 para modo texto.
Como agora temos mais parâmetros para considerar, e a classe não será mais responsável por desenhar as bolinhas, apenas fazer a “movimentação” delas, eu preciso disponibilizar mais alguns métodos, para consultar as coordenadas de uma bolinha, e para informar os valores a serem usados nas propriedades adicionais, necessárias para o modo gráfico.
Através dos métodos getRow() e getCol(), eu consigo de “fora” da classe — do programa que usa / consome a classe — pegar o número da linha e coluna atuais da bolinha, e através dos métodos setSize() e setStep(), definir o tamanho (diâmetro) da bolinha e o “passo” (incremento de movimento) da bolinha.
Usando o método SetBounds(), eu informo os limites de tamanho da minha interface, seja texto ou gr;afico, e ao invés de sobrescrever o construtor, eu criei um método de inicialização, que sorteia a posição inicial da bolinha levando em conta o tamanho da bolinha e da tela onde ela será pintada.
E, por fim, o método PingStep() agora não pinta mais nada, e faz as verificações de atingimento do limite de tela considerando também o tamanho flexível da interface, e o tamanho da bolinha.
Fonte final da aplicação
Vamos pro “finalmente”, e depois as explicações. Assim ficou o fonte PingPongGUI.java, feito para executar o Ping-Pong em interface gráfica:
// Importa tudo o que é necessário para essa aplicação import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Color; import java.awt.Rectangle; import javax.swing.JFrame; import javax.swing.JPanel; import java.awt.geom.Ellipse2D; // inclusive a classe PingPong2 que está no Package import siga0984.PingPong2; public class PingPongGUI { public static void main(String args[]) throws InterruptedException { int balls = 1; // Recebe o numero de bolinhas pela linha de comando if ( args.length > 0 ) balls = Integer.parseInt(args[0]); if ( balls < 1 ) balls = 1; // Cria a janela principal JFrame frame = new JFrame("Ping-Ping GUI in Java"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(800, 600); frame.setLocation(100,100); // Cria o array para guardar as bolinhas PingPong2[] aBalls = new PingPong2[balls]; // Cria o painel do PingPong para colocar como conteudo do JFrame PingPongPanel panel = new PingPongPanel(); frame.getContentPane().add(panel); // Passa a referencia do array de bolinhas para o painel panel.aBalls = aBalls; // Ativa a janela frame.setVisible(true); // Pega as dimensões internas do painel Rectangle r = panel.getBounds(); // Cria e Inicializa as bolinhas for ( int i = 0; i < balls; i++) { PingPong2 oBall = new PingPong2(); oBall.setBounds(r.width,r.height); oBall.setSize(10); oBall.setStep(i+1); oBall.InitBall(); aBalls[i] = oBall; } // Looping que invalida a tela // e repinta todas as bolinhas while ( true ) { panel.validate(); panel.repaint(); Thread.sleep(50); } } }; // Cria uma classe de painel para o Ping-Pong, herdando o JPanel class PingPongPanel extends JPanel { // Cria uma bolinha padrao , diametro de 10 pixels static Ellipse2D oCircle = new Ellipse2D.Double(0, 0, 10, 10 ); // Propriedade para guardar o array de bolinhas PingPong2[] aBalls; // Sobrescreve o evento de pintura de componentes, // Para que a pintura da janela pinte as bolinhas public void paintComponent (Graphics g) { // Seta que a cor de fundo será preta setBackground(Color.BLACK); // Propaga a pintura dos demais componentes para a classe superior super.paintComponent(g); // Faz um cast da interface gráfica // para ser possivel desenhar os círculos Graphics2D g2 = (Graphics2D) g; // Seta a cor do desenho g2.setColor(Color.WHITE); // E Para cada bolinha do array for ( PingPong2 oBall : aBalls ) { // Faz a movimentação oBall.PingStep(); // usa o objeto do círculo já montado // apenas setando as coordenadas atuais, e desenha o círculo na tela oCircle.setFrame(oBall.getRow(),oBall.getCol(),10,10); g2.draw(oCircle); } } };
Por hora, em ambiente gráfico, o diâmetro da bolinha está fixo em 10 pontos, a janela desenhada na tela possui 800 x 600 pontos gráficos (colunas x linhas), e uma coisa interessante: As bolinhas possuem velocidade diferente. Ao criar cada bolinha, eu defino que o “stepping” ( incremento de coordenadas de movimento ) é 1 para a primeira bolinha, 2 para a segunda, e assim por diante. Então, com 10 bolinhas , a décima bolinha “pula” de 10 em 10 pontos cada vez que ela é desenhada, fazendo ela parecer mais “rápida” que as outras … quando na verdade ela “pulou” um intervalo maior !
Conclusão
Pode parecer um pouco “complicado” no início … depois piora, e depois melhora ! São muitos eventos e componentes envolvidos em uma sequência hierárquica para criar e interagir com componentes de interface, tudo ao seu tempo, em passos 😀
Espero que vocês estejam aproveitando este conteúdo, e lhes desejo TERABYTES DE SUCESSO !!!
Referências
- Stechies – Difference between AWT and Swing
- StackOverflow – Java Swing revalidate() vs repaint()
- Java SE 8 – Graphics2D
- Java SE 8 – Ellipse2D
- Java SE 8 – Rectangle
- Java SE 8 – JFrame
- Java SE 8 – JPanel