SyntaxHighlighter

sexta-feira, 1 de outubro de 2010

SplashScreen e mais um java blog - Parte 2

Demorou mas retornei (smiles). Essa semana foi bastante agitada e o pouco tempo que sobrou pro blog fiquei navegando em busca de informações básicas pra edição e configuração do blog ... pois é, tomou mais tempo do que eu esperava e ainda não tenho todas as respostas que preciso ... o lado bom é que encontrei bons sites, na verdade metablogs, que dão diversas dicas (referências no final do artigo).

Nessa segunda parte do post pretendo mostrar duas técnicas utilizadas para atualizar informações na splash screen, a primeira delas desenhando diretamente sobre a splash e a segunda criando um frame que substitui a splash original e acrescenta qualquer componente disponível ... bastante elegante.


Antes de falar sobre as duas técnicas é importante conhecemos um pouco da classe "SplashScreen". É através dessa classe que conseguimos acesso a splash, carregada via linha comando ou através do arquivo manifest. A classe SplashScreen possui um conjunto bastante simples de métodos  pra controle e consulta de informações da splash. Um ponto muito importante é que esta classe não possui nenhum construtor público e por essa razão não é possível criar um objeto splash screen,  isso faz sentido já que a splash é criada uma única vez na inicialização do aplicativo. Como prática comum, através de um do método da classe SplashScreen "getSplashScreen" solicitamos acesso ao objeto criado e a partir dele conseguimos aplicar as duas técnicas seguintes.

public static void main(String[] args){
//O código abaixo não irá compilar, já que a classe SplashScreen
//não possui um construtor público.
//SplashScreen minhaSplash = new SplashScreen();

//O único modo de obter uma referência ao objeto SplashScreen é
//usando o método "getSplashScreen" da classe.
//Você só irá utilizar isso caso queira complementar sua splash screen
//colocando por exemplo informações de status da operação, versão, etc.
SplashScreen minhaSplash = SplashScreen.getSplashScreen();
}
A primeira técnica é bastante trabalhosa no sentido de criar o que queremos mostrar na splash, por exemplo, se pretendemos mostrar uma barra de progresso, atualizada a medida que a operação de inicialização avança, precisaremos desenha-la, isso mesmo, literalmente desenha-la na splash.

A idéia básica dessa primeira técnica é solicitar ao objeto SplashScreen que crie um objeto gráfico 2d que sobreponha a imagem e com isso nos permita desenhar sobre a splash, tendo, obviamente, a imagem da splash como fundo. Uma vez criado um objeto Graphics2D basta desenharmos qualquer forma geométrica permitida por ele (quem sabe um post futuro sobre ele :-). Desenhado o elemento gráfico, basta atualizarmos a splash pra que ele apareça. Vale a pena ressaltar que o posicionamento do elemento desenhado tem que bater com a área da splash, caso contrário, mesmo atualizando a splash você não conseguirá visualizar nada. Você consegue as coordenadas e o tamanho da splash através do método getBounds da classe SplashScreen.

O código abaixo exemplifica o que seria a implementação dessa primeira técnica.Deixei o código bastante comentádo para guia-lo passo a passo no que foi feito.

public static void modoUm(){
        
  SplashScreen minhaSplash = SplashScreen.getSplashScreen();

  //Antes de prosseguir vamos verificar se a imagem da splash foi
  //passada via linha de comando ou através do arquivo manifest.mf
  if(minhaSplash == null){
    System.err.println("Especifique o nome da imagem através do parâmetro \"-splash:\" " 
                    + "ou dentro do arquivo manifest. Certifique que a imagem exista.");
            System.exit(1);
  }
  //Se chegamos até aqui é pq está tudo em ordem, a splash screen existe!
  //Primeiro método - Desenhando diretamente na Splash
  Graphics2D g = minhaSplash.createGraphics();
  //Em width e Heigh vc consegue as dimensoes (em px) da imagem
  Rectangle bordas = minhaSplash.getBounds();

  int altura = 20;
  int x = 0;
  int y = bordas.height - altura;

  //Desenha uma mensagem na splash
  g.drawString("Versão do aplicativo: 1.00.00", x, y-5);

  //Simula o tempo gasto por algum processo demorado (carga dos plugins,
  //consulta e processamento de dados, atualização de informações vindas
  //de equipamentos, etc).
  try
  {
    for(int i=0; i<=100;i=i+2){
      g.setColor(Color.YELLOW);
      g.fillRect(x, y, bordas.width * i/100, altura);
      minhaSplash.update();
      Thread.sleep(100);
    }
    Thread.sleep(1000);

  } 
  catch (InterruptedException ex)
  {
    Logger.getLogger(JavaMilk.class.getName()).log(Level.SEVERE, null, ex);
  }

  //A Splash screen desaparece assim que que a primeira janela estiver visível.
  //Caso não exista nenhuma janela no aplicativo, vc pode forçar o termino
  //da splash usando o método close (código comentado abaixo)
  //minhaSplash.close();

  java.awt.EventQueue.invokeLater(new Runnable() {
    public void run() {
      new JavaMilkGui().setVisible(true);
    }
  });
}

A segunda técnica embora mais elegante e menos trabalhosa exige um pouco mais de conhecimento na implementação. A idéia básica por trás dessa segunda técnica é criar um frame não decorado, ou seja, que não se pareça com uma janela tradicional do sistema operacional (borda, botões de minimizar, maximizar, barra de título, etc), criar um painel cuja imagem de fundo seja a imagem da splash, posiciona-la exatamente onde a splash foi colocada e com o mesmo tamanho. Uma vez criado este frame podemos adicionar qualquer componente awt, swing existente sem reinventarmos a roda :-).

O Código abaixo exemplifica essa segunda técnica. O código foi bastante comentádo para ajuda-lo a entender as peripécias dessa implementação. Alguns assuntos merecem posts exclusivos.

public static void modoDois(){
  //Criar uma referencia pro objeto SplashScreen
  SplashScreen minhaSplash = SplashScreen.getSplashScreen();
  //Se carregado corretamente então vamos em frente
  if(minhaSplash == null){
    System.err.println("Especifique o nome da imagem através do parâmetro \"-splash:\" " 
                    + "ou dentro do arquivo manifest. Certifique que a imagem exista.");
    System.exit(1);
  }

  //Pra este modo, vamos precisar das informações da imagem carregada
  final Image img = Toolkit.getDefaultToolkit().getImage(minhaSplash.getImageURL());
  //Vamos também criar um frame sem borda que será colocado sobre a splash
  final JFrame splashFrame = new JFrame();
  //Vamos forçar que este frame não tenha uma aparência convecional, ou
  //seja, que ele não se pareça com uma janela tipica do sistema operacional
  splashFrame.setUndecorated(true);

  //Vamos criar um painel pra janela que acabamos de criar
  final JPanel splashPanel = new JPanel(){
    //Vamos redefinir o método painComponent, pro nosso caso,
    //colocando o imagem da splash que capturamos anteriormente
    @Override
    public void paintComponent(Graphics g)
    {
      g.drawImage(img, 0, 0, null);
    }
  };

  //Vamos colocar um progress bar no painel que acabamos de criar
  final JProgressBar progressBar = new JProgressBar();
  //O componente JProgressBar nos permite escrever uma mensagem dentro
  //do componente, informando o status da operação.
  progressBar.setStringPainted(true);

  //O painel que criamos JPanel precisar de um gerenciador de layout,
  //isso nos permitirá posicionar corretamente os componentes que
  //adicionamos no painel, neste caso, o progress bar
  splashPanel.setLayout(new BorderLayout());
  //Agora que já temos um gerenciador de layout pro painel, vamos
  //adicionar o componente progress bar e posiciona-lo - vamos posiciona-lo
  //na parte inferior da imagem (sul)
  splashPanel.add(progressBar, BorderLayout.SOUTH);
        
  //Criamos o painel com a imagem + o componente progress bar. Vamos agora
  //adicionar este painel em nossa janela
  splashFrame.add(splashPanel);
  //Vamos definir a posição e tamanho desta janela
  splashFrame.setBounds(minhaSplash.getBounds());
  //e é claro, torna-la visivel
  splashFrame.setVisible(true);

  //A classe SwingWorker é utilizada aqui para atualização do SplashPanel
  //Essa classe é sempre utilizada quando se tem uma tarefa rodando em
  //background e necessita atualizar alguma interface de usuário.
  //Obs: É a primeira vez que estou vendo o uso da classe SwingWorker,
  //podemos em uma outra ocasião criar um post específico pra ela.
  new SwingWorker(){

    @Override
    protected Void doInBackground() throws Exception{
      try{
        for(int i=0; i<=100; i=i+2){
          publish(i);
          Thread.sleep(100);
        }
      }
      catch(InterruptedException e)
      {
      }
      return null;
    }

    //É na redefinição do método process que atualizamos o componente
    //progress bar: valor e mensagem.
    @Override
    protected void process(List passos){
      for(Integer i : passos){
        progressBar.setString("Carregando módulo: " + i);
        progressBar.setValue(i);
        splashPanel.repaint();
      }
    }

    //Utilizamos o método done pra finalizamos a operação de nossa
    //janela (falsa splash). Diferente da splash original que desaparece
    //quando uma nova janela se torna visivel, precisamos forçar nossa
    //falsa janela splash a desaparecer no termino do processo.
    @Override
    protected void done(){
      splashFrame.setVisible(false);
      java.awt.EventQueue.invokeLater(new Runnable() {
        public void run() {
          new JavaMilkGui().setVisible(true);
        }
      });
    }
  }.execute();
}

Comparando as duas implementações podemos tirar algumas conclusões: a primeira técnica é bastante trabalhosa no sentido de desenhar cada elemento que pretendemos mostrar na splash, um ponto importante e desfavorável a essa técnica é a questão estética quando desenhamos por exemplo uma barra de progresso, independente do sistema operacional utilizado o formato (look & feel) será sempre o mesmo, não seguindo o padrão do sistema de janela utilizado. Um ponto a favor é a simplicidade quando precisamos colocar algo simples e não muito sofisticado na splash, por exemplo um texto informando a versão do aplicativo. A segunda técnica é mais elegante, nos dá liberdade de adicionar qualquer componente AWT ou Swing existente em nossa biblioteca. Algo muito importante é que a aparência (look & feel) segue exatamente o estilo do gerenciador de janelas em uso, certamente outra vantagem. Um ponto negativo pra segunda técnica é que a troca da splash real pelo frame splash (nossa splash forçada) é notável, bastante rápido, mas não  passa despercebido. Isso acontece pois a splash, conceitualmente, desaparece assim que qualquer janela AWT se tornar visivel.

Enfim podemos concluir que o uso de uma ou outra técnica na apresentação de uma splash vai depender muito do que realmente esperamos dessa tela de apresentação, se o que queremos é algo muito simples podemos utilizar apenas a imagem estática sem se preocupar com qualquer código dentro do aplicativo e se precisamos de algo mais informativo temos essas duas opções apresentadas.

O video a seguir foi realizado, ainda bastante amador, com o intuito de demonstrar a execução do código apresentado no post. Tente visualisar as diferenças nas duas técnicas:
 

Espero que tenham gostado e é claro fiquem a vontade para dar sugestões.

Grande abraço a todos e até o próximo post :-).

Referências técnicas:
1 - Livro Core Java Advanced (8a edição)
2 - Java docs - SplashScreen
3 - Java docs - Graphics2D

Referências para quem precisa conhecer mais sobre configuração dos blogs:
1 - Dicas blogger
2 - Templates para voce


Nenhum comentário:

Postar um comentário