Usando Styles, Themes e Painters com LWUIT



por Biswajit Sarkar
25/09/2008


O Lightweight User Interface Toolkit  (LWUIT) introduz um número impressionante de funcionalidades para o desenvolvedor IU Java ME. Styles, themes e painters são três funcionalidades que facilitam o desenvolvimento de highly attractive e elementos visuais device-independent. Neste artigo, veremos como utilizar e explorar algumas questões importantes. A aplicação de demonstração foram desenvolvidas no  Sprint Wireless Toolkit 3.3.1. Este suporte ao kit de ferramentas LWUIT não só faz isso mas tem também uma interessante variedade de emulador de dispositivos como aqueles para HTC Touch e Samsung Instinct. Se você pretende experimentar LWUIT, a sugestão é que você instale a Sprint WTK em seu computador.

Style O conceito de style é o alicerce no qual theming é construído. A idéia por trás do style é definir e centralizar os atributos visuais para cada componente. Além do seu desenho físico, tais como a sua forma, o surgimento de um widget pode ser definido em termos de um número de características comuns:

  • Cores em background e foreground : Cada componente tem quatro atributos de cores: dois para cada background e foreground. Um componente é considerado selecionado quando ele está pronto para a ativação. Quando um botão recebe foco, por exemplo, está no estado selecionado e pode ser ativado ao ser clicado. Um componente pode ter uma cor background para o estado selecionado e outra para o desmarcado. Do mesmo modo, a cor foreground (normalmente a cor usada para o texto no componente) pode ser definido individualmente para os dois estados.
  • Fonte text: O texto pode ser renderizado usando estilos de fonte padrão como suportado pela plataforma, bem como fontes bitmap. O fonte de cada componente pode ser definido através do seu estilo de objeto.
  • Transparência background: a transparência de um componente background pode ser ajustado para variar de totalmente opaco (a configuração padrão) para a totalmente transparente. O valor inteiro 0 corresponde a uma transparência total e 255 para completar opacidade.
  • Imagem background: Por padrão, o background de um componente não tem qualquer imagem. No entanto, esta configuração pode ser usada para especificar uma imagem para ser utilizada como background.
  • Margin e padding: O layout visual de um componente (derivado do CSS Box Model) define margin e padding. A Figura 1 mostra o significado dos termos margin e padding no contexto da LWUIT. Note que o content area é usada para exibir o conteúdo básico, como texto ou imagem. Style permite margin e padding de cada uma das quatro direções (superior, inferior, esquerda e direita), a ser fixado individualmente.


Figura 1. Componente layout

  • Background painters: Objetos painter special-purpose  podem ser utilizados para personalizar o background de um ou de um grupo de componentes.

A classe Style representa o conjunto de todos estes atributos para cada componente que é utilizado em uma aplicação e tem métodos acessores adequados. Além disso, esta classe também tem a capacidade de informar um ouvinte registrado quando o objeto style associado a um determinado componente é alterado.
Quando um componente é criado, um objeto style padrão fica a ela associado. Para qualquer aplicação não trivial, os atributos visuais terão que ser modificados. Uma maneira de fazer isso é utilizar os métodos setter individuais. Se, por exemplo, a cor foreground de um componente (thiscomponent) tem que ser alterado para vermelho, o código seguinte pode ser utilizado:
 

 thiscomponent.getStyle().setFgColor(0xff0000);

Um outro caminho para modificar as definições do padrão style é a de criar um novo estilo e agregá-lo acima do respectivo componente. A classe style tem construtores que permitem a maioria dos atributos serem especificados. O código seguinte define um novo estilo para um componente:


 Font font = Font.createSystemFont
(Font.FACE_SYSTEM,Font.STYLE_BOLD,Font.SIZE_LARGE);
byte tr = (byte)255;
Style newstyle = new Style
(0x00ff00, 0x000000, 0xff0000, 0x4b338c, font, tr);
thiscomponent.setStyle(newstyle);

Este código define novascores em foreground e background, fonte de texto, e transparência background. O construtor aqui utilizado tem a forma:

 Style(int fgColor, int bgColor, int fgSelectionColor,
int bgSelectionColor, Font f, byte transparency)

Existe uma outra forma deste construtor que permite a imagem ser definida, além dos atributos acima. Os atributos não suportados pelo construtor, no entanto, precisaram ser definidos através dos respectivos métodos setter.

Finalmente, atributos visuais também podem ser definidos por uma classede componentes inteira (ou seja, por todos os marcadores em um aplicativo) usando um theme como veremos um pouco mais tarde.
Usando Style
Agora vamos construir um visor simples e ver como o estilo pode ser usado para especificar a aparência de um componente.Nossa aplicação terá um único formulário com combo box e será parecido com a Figura 2:

Figura 2. Um simples combo box
Todos os atributos do combo box que aparecem aqui, têm valores padrão. A única excepção é a cor de seleção foreground, que teve que ser alterada para melhorar a visibilidade do item selecionado. Do mesmo modo, o formulário que contém o combo box tem apenas um atributo modificado - a sua cor de fundo. O código a seguir mostra como a formulário é criado:

 .
.
.
//create a form and set its title
Form f = new Form("Simple ComboBox");

//set layout manager for the form
//f.setLayout(new FlowLayout());

//set form background colour
f.getStyle().setBgColor(0xd5fff9);
.
.
.

As duas primeiras linhas do código são auto-explicativas e deve ser familiar para os desenvolvedores AWT / Swing. A terceira linha define o atributo de cor de fundo para o formulário.
O combo box também é instanciado de forma semelhante:

 // Create a set of items
String[] items = { "Red", "Blue", "Green", "Yellow" };

//create a combobox with String[] items
ComboBox combobox = new ComboBox(items);

ComboBox é uma subclasse de List e precisa de uma estrutura de dados suportada. Aqui usamos uma string array para representar esta estrutura de dados.
Depois do nosso combo box pronto, nós mudaríamos a sua cor de seleções foreground para melhorar a legibilidade. Então escreveríamos uma linha de código, assim como tínhamos feito para o formulário:

 combobox.getStyle().setFgSelectionColor(0x0000ff);

No entanto, quando compilar o código e executá-lo, o resultado acaba por ser surpreendente - a cor foreground  permanece inalterada! Ela trabalha para o formulário, desta forma, por que ela não trabalha com um combo box? Para responder a essa pergunta, precisamos ter em mente a arquitetura básica da LWUIT. Como Swing, LWUIT foi projetado em torno do conceito MVC. Assim, a entidade que a torna um componente é logicamente separada do componente próprio. Além disso, o objeto renderizado para o combo box (entre outros), deve ser uma subclasse de Component, o que significa que terá seu próprio Style. Cada combo box é criado com o seu renderizador padrão, que é uma instância de DefaultListCellRenderer. Quando um combo box é desenhado, o estilo utilizado é que pertencem ao renderizador e que é por isso que fixa a cor de seleção foreground no objeto Style para o combo box não funcionar. Para tornar a definição eficaz, temos que modificar o objeto Style para o renderizador:

 //set foreground selection colour for
//the default combobox renderer
//this will work
DefaultListCellRenderer dlcr =
(DefaultListCellRenderer)combobox.getRenderer();
dlcr.getStyle().setFgSelectionColor(0x0000ff);

Desta vez, quando o código é compilado, ele funciona.
Theme
Na seção anterior, vimos como definir atributos visuais individuais para um componente. Em uma aplicação com um grande número de componentes da IU, a definição de atributos de cada componente pode ser uma tarefa tediosa e também pode levar a erros. Um theme nos permite definir, em um único lugar, os atributos de uma classe inteira de componentes. Isto não só simplifica a tarefa de definir os atributos para todos os componentes de um tipo particular, mas também garante que qualquer novo componente adicionado parecerá exatamente a todos os outros do mesmo tipo na aplicação. Um theme assim, estabelece uma coerência visual em todos as telas de uma aplicação.
Um theme é uma lista de pares key-value com um atributo a ser uma chave e seu valor é o valor correspondente. Uma entrada na lista pode ter esta aparência:

 Form.bgColor= 555555


Esta entrada especifica que a cor background de todas os formulários na aplicação será (hex) 555555 no formato RGB. Um theme é embalado em um arquivo de recursos que também podem carregar outros itens como imagens e bitmaps por fontes. A baixa do pacote LWUIT inclui um editor de recurso (resource editor), que oferece um caminho simples de criar um theme, e este pacote em um arquivo de recursos. O editor está disponível no diretório do pacote util. Lançá-lo clicando duas vezes sobre o ícone e o editor será aberto, conforme mostrado abaixo. O Resource Editor também é integrado no Sprint WTK 3.3.1 e pode ser acedido através da seleção File -> Utilities -> LWUIT Resource Editor, como visto na Figura 3.


Figura 3. Editor de recursos (Resource Editor)

Para criar um novo tema, clique no botão + no painel da esquerda e um diálogo para entrar com o nome do tema será aberta. Isto é demonstrado na Figura 4.

Figure 4. Criando um novo tema
Quando você clica em OK, o nome do novo tema aparece no painel da esquerda. Clique neste tema para obter um rótulo em branco sobre o tema do painel, como pode ser visto na Figura 5.


Figura 5. O tema vazio
Para preencher o tema vazio, clique no botão Add e o diálogo Add será aberto. Você pode selecionar um componente e um atributo do topo combo box neste diálogo. Na Figura 6, o elemento selecionado é um formulário e é selecionado o atributo background. O valor da cor RGB pode ser entrado como uma string hexadecimal no espaço fornecido. Você também pode clicar no box colorido próximo ao espaço para digitar o valor da cor.Feito isto, abrirá uma escolha de cor (color chooser), pelo qual o valor da cor selecionada será diretamente entrada na caixa de diálogo.

Figure 6. Adicionando uma entrada para o tema
Clique no botão OK e a entrada será exibida no painel direito do editor principal da janela. Repare que as entradas podem ser editadas ou removidas usando o botão apropriado. Uma vez que todas as entradas foram feitas, você pode salvá-la, selecionando File -> Save As. Se você estiver usando o Sprint WTK, então, o arquivo recurso para uma aplicação tem que ser, na sua pasta res.
Agora que já vimos como criar um tema, vamos ver uma demonstração que ilustra a sua utilização. Nossa demonstração para esta seção também terá combo boxes, mas ficará um pouco mais polida do que a uma já vista. A Figura 7 mostra esta tela demonstração. Note que agora o formulário tem uma imagem background e os combo boxes são construídos em torno de boxes. Além disso, a barra de título (no topo do formulário) e da barra de menu (ao fim do formulário) têm cores diferentes do padrão (branca).


Figure 7. Tela demonstração com dois combo boxes


//initialise the LWUIT Display
//and register this MIDlet
Display.init(this);

try
{
//open the resource file
//get and set the theme
Resources r = Resources.open("/SDTheme1.res");
UIManager.getInstance().
setThemeProps(r.getTheme("SDTheme1"));
}
catch (java.io.IOException e)
{
//if there is a problem print a message on console
//in this case default settings will be used
System.out.println
("Unable to get Theme " + e.getMessage());
}

//create a form and set its title
Form f = new Form("ComboBox Example");

//set layout manager for the form
f.setLayout(new FlowLayout());

//create two sets of items
String[] items = { "Red", "Blue", "Green", "Yellow" };
String[] items2 =
{"Sky", "Field", "Ocean", "Hill", "Meadow"};

//create two comboboxes with these items
ComboBox comboBox = new ComboBox(items);
ComboBox comboBox2 = new ComboBox(items2);

//create new instances of CbPainter
//and set them to combo boxes
//so that a checkbox will be
//the basic building block
CbPainter cbp = new CbPainter();
comboBox.setListCellRenderer(cbp);

CbPainter cbp2 = new CbPainter();
comboBox2.setListCellRenderer(cbp2);


//add the two combo boxes to the form
f.addComponent(comboBox);
f.addComponent(comboBox2);

//create an "Exit" command and add it to the form
f.addCommand(new Command("Exit"));

//set this form as the listener for the command
f.setCommandListener(this);

//show this form
f.show();

Logo no início, vemos como extrair o tema a partir de um arquivo de recursos. O tema é então definido para a instancia UIManager. Aqui temos instalado o tema no início. Mas, quando um tema é definido no ato, alguns dos componentes do formulário no tela podem não ser visíveis e os efeitos da definição de um tema nestes componentes não é previsível. Para ter certeza de que mesmo os componentes que não são visíveis têm seus estilos devidamente atualizados, você deve chamar o método refreshTheme:


Display.getInstance().getCurrent().refreshTheme();

O formulário e os combo boxes são criados apenas como no nosso exemplo em preceding section (seção anterior). Não existe nenhum código que adiciona brilho visual a esta demonstração, como todos os atributos são especificados em um Theme. O que é diferente aqui é que, em vez de deixar os combo boxes serem desenhados pelo renderizador padrão, temos a definição de nossos renderers próprio. Isto é mostrado pelas parte destacada do código. Esses renderers personalizados fazem os combo boxes se comportar diferente.
O próprio renderizador é muito simples. Tudo que tem ser feito, é aplicar os métodos especificados na interface ListCellRenderer. Assim, queremos nosso combo box para encapsular um checkbox, o renderizador extends CheckBox. O método drawComboBox da classe DefaultLookAndFeel utiliza este renderizador para obter o componente a ser usado para desenhar o combo box. Neste caso, o componente assim obtido é uma checkbox, como podemos ver a partir do código abaixo.


//objects of this class will be used to paint the combo boxes
class CbPainter extends CheckBox implements ListCellRenderer
{
public CbPainter()
{
super("");
}

//returns a properly initialised component
//that is ready to draw the checkbox
public Component getListCellRendererComponent
(List list,Object value,int index,boolean isSelected)
{
setText("" + value);
if (isSelected)
{
setFocus(true);
setSelected(true);
}
else
{
setFocus(false);
setSelected(false);
}

return this;
}

//returns the component required for drawing
//the focussed item
public Component getListFocusComponent(List list)
{
setText("");
setFocus(true);
setSelected(true);

return this;
}
}

Não é necessário que um combo box deva olhar apenas como uma simples lista ou um checkbox,. Pode ser construído em torno de algum outro componente normal ou mesmo em torno de um elemento totalmente novo com o seu olhar próprio. A Figura 8 mostra um combo box que tem um botão radio como o seu renderizador.


Figure 8. ComboBox com um renderizador botão radio Para ver o tema que define o aspecto da nossa demonstração, você vai precisar do Resource Editor no seu computador. Launch quer o Resource Editor que vem com a baixa do LWUIT ou a um integrado no Sprint Toolkit. Uma vez que o Resource Editor abre, select File -> Open para localizar e abrir o arquivo recurso. O Resource Editor irá mostrar SDTheme1 no painel esquerdo sob Themes. Clicando SDTheme1 exibirá os detalhes do tema no painel direito como mostrado na Figura 9.


Figure 9. O tema para demonstração

O primeiro ponto a salientar é que existe uma entrada no rodapé, que aparece em letras negrito. Todas estas entradas são os padrões. No nosso exemplo, o único fonte de component-specific (componente específico) definido é botão soft - o botão Exit no canto inferior esquerdo. As fontes para o título do formulário e a string combo box não estão definidos. Essas fontes serão renderizadas de acordo com a configuração padrão.
Em nosso exemplo anterior, vimos que a cor de seleção para o texto teve que ser fixado no renderer. No presente exemplo, sabemos que a renderização está sendo feita por um renderizador checkbox. Portanto, as cores background e foreground foram definidas para checkboxes e, na verdade, as cores para renderizar o texto e o background do texto (ambos para os estados centrado e não centrado) são como por estas definições. Isto pode ser visto na Figura 10.



Figure 10. Cores foreground e background
Na figura acima, também podemos ver o efeito de transparência checkbox valor de 127 (semi-transparente). As três entradas não selecionados na lista drop-down tem uma tonalidade escura, devido a esta definição de transparência. Você pode experimentar com esse valor para ver como alterar estes backgrounds. Aliás, quando você fizer uma alteração no tema, não é necessário reconstruir a aplicação. Salve o arquivo de recurso e clique em Executar.
Quando um novo tema é instalado, todos os estilos são atualizados, exceto os atributos que foram alterados manualmente, usando um dos métodos acessor da classe Style discutido earlier (anteriormente). No entanto, se você deseja que o novo tema seja eficaz, mesmo para os atributos que foram alterados manualmente, em seguida, utilize uma das definições no Style que pegue dois argumentos, o segundo sendo uma variável Boolean. Por exemplo:

 setFgColor(int fgColor, boolean override);

Se o argumento Boolean é definido como true quando um atributo é alterado manualmente, então os valores especificados no novo tema irá sobrepor-se ao valor definido manualmente também. O código será parecido com a instrução que segue:

 thiscomponent.getStyle().setFgColor(0xff0000, true);

Painter
A interface Painter permite que o background de um componente analise a forma como você quer. Lembrando, nossa discussão em style, onde vimos que um dos atributos era background painter. Nesta seção vamos ver como um simples background painter pode ser usado.
Referindo-se à nossa demo screenshot (imagem demonstração), a cor do background através do qual o texto foi desenhado não pode ser alterada através de style ou theme. A razão para isto, se torna clara quando analisamos a estrutura de um combo box, como mostrado na Figura 11, e a sequência de renderizá-la.


Figure 11. Estrutura do nosso combo box Quando um combo box precisa ser redesenhado (digo, porque ele acaba de receber foco), a seguinte seqüência de eventos ocorre.

    1. O obsoleto combo box é apagado. Isto é feito desenhando um  retângulo preenchido do mesmo tamanho e com uma transparência de 0 (totalmente transparente).
    2. Então, a seleção do checkbox e o texto são desenhados.
    3. Em seguida, o botão combo é desenhado.
    4. E finalmente, a combinação das bordas é desenhada.
Vemos agora que o combo background não é redesenhado após o primeiro passo. Portanto, esta parte continua a ser uma camada totalmente transparente, e é o formulário background que aparece. Você pode alterar a  cor do formulário background no tema e verá que esta cor também torna a cor combo background.
Se agora quisermos ter uma cor diferente (ou um padrão ou uma imagem) no combo background, precisamos usar um Painter. Veremos o que parece ser um simples painter. e como usá-lo.

public class ComboBgPainter implements Painter
{
private int bgcolor;

public ComboBgPainter(int bgcolor)
{
this.bgcolor = bgcolor;
}

public void paint(Graphics g, Rectangle rect)
{
//probably redundant
//but save the current colour anyway
int color = g.getColor();

//set the given colour
g.setColor(bgcolor);

//get the position and dimension
//of rectangle to be drawn
int x = rect.getX();
int y = rect.getY();
int wd = rect.getSize().getWidth();
int ht = rect.getSize().getHeight();

//draw the filled rectangle
g.fillRect(x, y, wd, ht);

//restore colour setting
g.setColor(color);
}
}

O código é bastante simples - tudo que faz é desenhar um retângulo preenchido com a cor passada para o construtor. O retângulo é desenhado na posição e com as dimensões definidas por rect.
O que temos que fazer agora é ligar o painter para combo box, que deve ter seu background pintado. Fazemos isso, adicionando a linha destacada após o código instanciando as dois combo boxes. Note que apenas um combo box terá seu background pintado.

 //create two comboboxes with these items
ComboBox combobox = new ComboBox(items);
ComboBox combobox2 = new ComboBox(items2);

//set the painter
combobox.getStyle().setBgPainter
(new ComboBgPainter(0x4b338c));


A Figura 12 mostra que o background do combo box na esquerda tem sido pintado como esperado. Se quisessemos pintar o background do outro combo box também, seria utilizado o mesmo painter. De fato, nós poderíamos criar uma instância de painter e definir o mesmo exemplo em todas os combo box.


Figure 12. Combo box com background pintado
Conclusão
Vimos como é que podemos utilizar Style, Theme e Painter para criar um conjunto de componentes visualmente atraente e uniforme com a plataforma LWUIT. Recentemente, LWUIT foi código de fonte aberto. Um estudo detalhado do código-fonte é uma experiência muito fascinante e desenvolverá o tipo de visão necessária para a correta utilização da biblioteca e também para esta experimentação interessante. Recursos


Biswajit Sarkar é engenheiro elétrico com uma especialização programação de automação industrial.