domingo, 30 de janeiro de 2011

Problema ao iniciar emulador do Android

Olá povo,

Meu aluno Rogério Casal do curso de Android do Unibratec me mandou esse problema e a respectiva solução.

Ao criar um AVD (Android Virtual Device) para que possamos executar um emulador do Android, essas configurações ficam armazenadas no seguinte diretório:

Para Windows:
C:\Usuário\.android\avd\
C:\Documents and Settings\Usuário\.android\avd

Para Linux e OS X:
/home/Usuário/.android/avd/

Porém, se o nome do seu usuário contém acentuação ou cedilha, o emulador não inicia corretamente com AVDs que contenham esses caracteres. Normalmente acontece o seguinte erro:

emulator: ERROR: no search paths found in this AVD's configuration.

A solução encontrada foi mover seu arquivo de AVD para um caminho que não contenha acentuação. Para fazer isso, após criar o AVD, abra o prompt e digite o seguinte comando:

android move avd -n nome_do_avd -p C:\AVDs

O caminho especificado deve sempre criar um diretório ao invés de usar uma pasta já existente (no exemplo acima seria criado o diretório AVDs).

4br4ç05,
nglauber

segunda-feira, 24 de janeiro de 2011

Usando tecla (diálogo) de busca

Olá povo,

Ontem estava eu deitadão assitindo televisão quando chega minha digníssima esposa e me solta essa pérola: "Ei seu blog está desatualizado. A última postagem foi do começo do mês...". Sendo assim, cá estou para mais um post :)

Hoje vou mostrar pra vocês, como utilizar o botão de busca na sua aplicação, que é padrão em todos os na maioria dos aparelhos Android. Esse botão permite que você implemente a funcionalidade de busca de uma maneira padrão à do sistema operacional.

Crie no seu projeto um arquivo chamado busca.xml dentro do diretório res/xml. Nesse arquivo ficarão as configurações do diálogo de busca:


<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_name"
android:hint="@string/app_hint" >
</searchable>

Notem que no arquivo acima, estamos usando duas strings que devem estar definidas no arquivo /res/values/strings.xml. Tentei usar valores hard coded e não funcionou.

Em seguida, devemos informar no AndroidManifest.xml que a atividade tratará o botão de busca. Para isso, devemos adicionar um IntentFilter com a ação android.intent.action.SEARCH e o meta-data passando nosso XML criado acima:

<activity android:name=".MinhaAtividade" >
<intent-filter>
<action
android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/busca"/>
</activity>


Feito isso, basta tratar a Intent com a busca. Coloque o código abaixo no método onCreate() da sua Activity. Caso não queira que seja aberta uma nova instância da Activity a cada nova chamada a tela de busca, marque-a no AndroidManifest.xml como android:launchMode="singleTop" e trate o evento de busca no método onNewIntent().

Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
//faça sua busca com a variável query
}



Mais informações aqui.

4br4ç05,
nglauber

sexta-feira, 7 de janeiro de 2011

ANT + Antenna

Olá povo,

Esse post vai em homenagem a primeira turma de pós-graduação em Tecnologias para Desenvolvimento de Aplicações Movéis do CESAR.edu que aguentaram o mico que eu paguei quando o exemplo que eu vou mostrar nesse post não funcionou na sala. Então lá vai...

Vou demonstrar nesse post como utilizar o Ant e o Antenna. O Ant é uma ferramenta para automatização de scripts desenvolvida pela Apache Foundation. Baseia-se em tarefas a serem executadas e descritas em um arquivo XML (chamado de build file).

O Antenna é uma biblioteca que pode ser adicionada ao Ant para prover um conjunto de tarefas exclusivas para o desenvolvimento com Java ME. As tarefas realizadas pelo Antenna executam o trabalho que o WTK realiza, porém, com elas o processo de deploy (geração de um JAR/JAD) da aplicação fica mais flexível e independente de IDE.

Uma das grandes vantagens da utilização dessas ferramentas é auxiliar a prover portabilidade para a diversidade de fabricantes e modelos de aparelhos existentes no mercado, uma vez que tamanho de tela, quantidade e disponibilidade de teclas, formato de áudio e vídeo suportados são algumas das características que variam bastante de modelo para modelo.

Supondo que já temos o Eclipse configurado para Java ME, precisamos apenas baixar o JAR do Antenna que está disponível aqui. Depois, basta configurar no Eclipse o local onde você baixou o JAR. Para isso, acesso o menu Window > Preferences. No lado esquerdo, selecione Java ME, e preencha o campo Antenna JAR com o nome e local onde está o JAR do Antenna.

Vamos utilizar um projeto Java ME bem simples no Eclipse para demonstrar a utilização do Ant+Antenna. A estrutura final do projeto ficará como na imagem abaixo:


Vamos começar detalhando a classe PrincipalMIDlet.java:

public class PrincipalMIDlet extends MIDlet {

protected void startApp() {
Display.getDisplay(this).
setCurrent(new MeuCanvas());
}

protected void destroyApp(boolean uncond){
notifyDestroyed();
}

protected void pauseApp() {
}
}

class MeuCanvas extends Canvas {
//#ifdef operadora
//#expand private String OPERADORA = "%operadora%";
//#else
private String OPERADORA = "Indefinido";
//#endif

//#ifdef background
//#expand private int COR_BACKGROUND = %background%;
//#else
private int COR_BACKGROUND = 0;
//#endif

private static final int COR_TEXTO = 0xFFFFFF;

private Image icon;

public MeuCanvas() {
try {
icon = Image.createImage("/icon.png");
} catch (IOException e) {
}
}

protected void paint(Graphics g) {
int w = getWidth();
int h = getHeight();

g.setColor(COR_BACKGROUND);
g.fillRect(0, 0, w, h);

g.drawImage(icon, w/2, h/2,
Graphics.BOTTOM | Graphics.HCENTER);

g.setColor(COR_TEXTO);
g.drawString(OPERADORA, w/2, h/2,
Graphics.TOP | Graphics.HCENTER);
}
}

A parte bacana do código acima, está na definição das constantes OPERADORA e COR_BACKGROUND. Notem que elas estão envolvidas por trechos de código que aparentemente são comentários do Java, mas na verdade elas são diretivas de pré-processamento. Esse recurso é possível graças ao Antenna, que faz com que essas diretivas tenham funcionalidade similar as diretivas de compilação da linguagem C.

A instrução //#ifdef verifica se uma determinado símbolo foi definido, em caso positivo, o código-fonte que estiver dentro dessa diretiva é o que será compilado, caso contrário será compilado o código que está dentro da diretiva //#else. Toda diretiva //#ifdef deve ser fechada com a diretiva //#endif.

A diretiva //#expand substituirá qualquer símbolo que estiver entre "%" com seu respectivo valor. No nosso exemplo, definimos dois arquivos de símbolos que contêm os símbolos "operadora" e "background". Esses arquivos são mostrados abaixo:

#Claro.symbols
operadora='Claro'
background='0xFF0000'

#Vivo.symbols
operadora='Vivo'
background='0x0000FF'

Um outro detalhe a ser observado é que quando a imagem icon.png foi carregada, ela não foi carregada do diretório Vivo ou Claro. Isso porque, geraremos um JAR e um JAD da nossa aplicação para cada operadora, então os recursos de cada uma delas ficará na raiz do JAR.

Vamos agora ao script de deploy. A estrutura do build.xml, está conforme a imagem abaixo:


<?xml version="1.0" encoding="UTF-8"?>

<project name="ExemploAnt" basedir="." >

<!-- Carrega as tarefas do Antenna -->
<taskdef classpath="${antenna.lib}"
resource="antenna.properties"/>

<!-- Carrega o arquivo de propriedade -->
<property file="meubuild.properties"/>

<!-- Realiza o deploy para a Vivo -->
<target name="deployVivo">
<!-- Atribui a propriedade 'operadora' que
será utilizada no restante do script -->
<property name="operadora" value="Vivo"/>

<!-- Chama a tarefa fazerDeploy -->
<antcall target="gerarJAR" />

<!-- Chama a tarefa fazerDeploy -->
<antcall target="executarMIDlet" />
</target>

<!-- Realiza o deploy para a Claro -->
<target name="deployClaro">
<property name="operadora" value="Claro" />
<antcall target="gerarJAR" />
<antcall target="executarMIDlet" />
</target>

<!-- Gera o JAR -->
<target name="gerarJAR" depends="compilar" >
<wtkpackage
autoversion="${do-autoversion}"
bootclasspath="${wtk.libs}"
jadfile="${work.dir}/deployed/${project.name}.jad"
jarfile="${work.dir}/deployed/${project.name}.jar"
libclasspath=""
obfuscate="${do-obfuscate}"
preverify="true">

<fileset dir="${work.dir}/bin/"/>
<fileset dir="${work.dir}/resources/"/>
</wtkpackage>
</target>

<!-- Compila os arquivos *,java-->
<target name="compilar" depends="preverificar" >
<wtkbuild
bootclasspath="${wtk.libs}"
destdir="${work.dir}/bin/"
encoding="UTF-8"
preverify="false"
source="1.3"
sourcepath=""
srcdir="${work.dir}/preverified/"/>
</target>

<!-- Efetua o pré-processamento dos arquivos
*.java utilizando as diretivas -->
<target name="preverificar"
depends="copiarCodigoFonte" >

<wtkpreprocess
debuglevel="info"
destdir="${work.dir}/preverified/"
printsymbols="true"
srcdir="${work.dir}/classes"
verbose="true">

<!--Carrega o arquivo de
símbolos da operadora-->
<symbols_file name="${operadora}.symbols/"/>
</wtkpreprocess>
</target>

<!-- Copia os arquivos *.java para
o diretório de build -->
<target name="copiarCodigoFonte"
depends="copiarRecursos" >

<copy overwrite="true" todir="${work.dir}/classes/">
<fileset dir="src" includes="**/**.java"/>
</copy>
</target>

<!-- Copia os arquivos de recurso para o diretório de build -->
<target name="copiarRecursos"
depends="criarDiretorios">

<copy file="Application Descriptor"
tofile="${work.dir}/deployed/${project.name}.jad"/>

<copy todir="${work.dir}/resources/">
<fileset dir="res/${operadora}"
excludes="**/**.java"/>
</copy>
</target>

<!-- Cria os sub-diretórios do diretório de build -->
<target name="criarDiretorios" depends="limparTudo" >
<!-- Dir onde ficarão JAR e JAD -->
<mkdir dir="${work.dir}/deployed/"/>
<!-- Dir onde *.java pré-processados ficarão -->
<mkdir dir="${work.dir}/preverified/"/>
<!-- Dir onde *.java serão copiados de /src -->
<mkdir dir="${work.dir}/classes/"/>
<!-- Dir dos recursos (imagens, áudio, etc) -->
<mkdir dir="${work.dir}/resources/"/>
<!-- Dir onde os *.class serão compilados -->
<mkdir dir="${work.dir}/bin/"/>
</target>

<!-- Apaga todo o diretório de build -->
<target name="limparTudo">
<delete dir="${work.dir}" failonerror="false"/>
</target>

<!-- Executa o JAR/JAD no emulador -->
<target depends="" name="executarMIDlet">
<wtkrun
jadfile="${work.dir}/deployed/${project.name}.jad" />
</target>

</project>


Como vocês puderam notar, diversas linhas do scrpit utilizam a notação ${variavel}. Isso representa uma propriedade ou um símbolo. Os símbolos estão definidos nos arquivos *.symbols e são usados para o pré-processamento. Já as propriedades são definidas no arquivo meubuild.properties (ou individualmente como é feito com a propriedade "operadora". O arquivo meubuild.propreties ficou da seguinte forma:

# Diretório raiz do WTK
wtk.home=C\:\\WTK2.5.2_01
# Deve obfuscar?
do-obfuscate=false
# Versão do MIDP e do CLDC
wtk.midp.version=2.0
wtk.cldc.version=1.1
# MIDlet deve ser auto versionado?
do-autoversion=false
# JAR do Antenna
antenna.lib=C\:\\nglauber\\J2ME\\antenna-bin-1.2.1-beta.jar
# JARs necessários na compilação
wtk.libs=C\:\\WTK2.5.2_01\\lib\\midpapi20.jar:C\:\\WTK2.5.2_01\\lib\\cldcapi11.jar:
# Nome do projeto
project.name=ExemploAnt
# Diretório de build
work.dir=output

Para executar o script de build, arraste esse arquivo para a view Ant do Eclipse. Se não estiver visível, acesse o menu Window > Show View > Other..., na janela que for exibida escolha Ant. Feito isso, basta dar um duplo-clique sobre a tarefa deployVivo ou deployClaro.

O resultado será similar a figura abaixo:



Quem quiser mais detalhes pode dar uma olhada no artigo que escrevi pra Web Mobile 29.

4br4ç05,
nglauber

terça-feira, 4 de janeiro de 2011

Curso de Android em Recife

Olá povo,

Amanhã começa a quinta turma de Google Android da Especializa Treinamentos. O curso tem carga horária de 40 horas e as aulas serão ministradas por mim, de Segunda a Quinta, das 19:00 as 22:00.

No curso, estudaremos a revolucionária plataforma para smartphones e tablets da Google e como desenvolver aplicativos para ela. Os conceitos de Activities, Handlers, ContentProviders e Services serão aplicados na prática. Vamos ver como armazenar informações no banco de dados do celular com SQLite. Comunicação com WebServices e com o GoogleMaps. Envio de SMS. E muito mais.

Quem tiver interesse, pode entrar na página do curso para obter mais informações e fazer sua inscrição. Corram que ainda há tempo!

4br4ç05,
nglauber

segunda-feira, 3 de janeiro de 2011

Áudio no Android

Olá povo,

Para tocar qualquer som no Android, podemos utilizar a classe MediaPlayer. Com ela podemos executar arquivos de áudio que estejam dentro do APK (na pasta assets ou res/raw) ou ainda no sistema de arquivos do aparelho (como SD card por exemplo).
// Carregando audio do diretório res/raw
MediaPlayer player = 
  MediaPlayer.create(this, R.raw.explosion);
player.start();

// Carregando audio do cartão de memória
MediaPlayer mp = new MediaPlayer();
mp.setDataSource("/sdcard/explosion.mp3");
mp.prepare();
mp.start();


Porém, em um dos aplicativos que desenvolvemos aqui no trabalho, nós precisávamos tocar dois sons simultâneamente: a música do jogo e o efeito sonoro. A classe MediaPlayer não é aconselhável para efeitos sonoros uma vez que eles precisam de um tempo de resposta baixo. Para esse trabalho, podemos utilizar as classes SoundPool e AudioManager (do pacote android.media) que facilitam o trabalho com sons, permitindo tocá-los concorrentemente.

Vejam o exemplo comentado abaixo:
public class SoundManager {
  // Total de sons no pool
  private static final int MAXSTREAMS = 4;
  // Instância única 
  private static SoundManager instance;
  
  // Pool de sons
  private SoundPool mSoundPool;
  // AudioManager para controlar o volume do som
  private AudioManager mAudioManager;
  // Lista com os ids dos sons adicionados
  private ArrayList<Integer> mSoundPoolMap;
  // Pilha que armazena as transações 
  // de execução dos sons
  private Stack<Integer> mSongsTransactions;
  
  private Context mContext;

  // Construtor privado pra implementar o 
  // Singleton Design Pattern
  private SoundManager(Context ct) {
    mContext = ct;
    mSoundPoolMap = new ArrayList<Integer>();
    mSongsTransactions = new Stack<Integer>();
    
    // Criando o pool de sons
    mSoundPool = new SoundPool(
        MAXSTREAMS, AudioManager.STREAM_MUSIC, 0);
    
    // AudioManager é um serviço de sistema
    mAudioManager = (AudioManager) 
        mContext.getSystemService(
            Context.AUDIO_SERVICE);
  }

  // Método estático para obter a instância única
  public static SoundManager getInstance(Context ct) {
    if (instance == null){
      instance = new SoundManager(ct);
    }
    return instance;
  }

  // Adiciona um som ao pool
  public void addSound(int soundId) {
    mSoundPoolMap.add(
      /* Carrega e obtém o id do som no pool
       * O segundo parâmetro o id do recurso
       * E o terceiro não serve pra nada :) ,
       * Mas na documentação diz pra colocar 1 */
      mSoundPool.load(mContext, soundId, 1));
  }

  // Manda tocar um determinado som
  public void playSound(int index) {
    /* O AudioManager é usado aqui pra obter
     * o valor atual do volume do aparelho para
     * não tocar o som nem baixo nem alto demais.
     * A divisão que é feita aqui é pq o método 
     * requer um valor entre 0.0 e 1.0. */
    float streamVolume = 
      mAudioManager.getStreamVolume(
          AudioManager.STREAM_MUSIC);
    streamVolume /= 
      mAudioManager.getStreamMaxVolume(
          AudioManager.STREAM_MUSIC);
    
    /* playId, armazena o id da requisição do som 
     * a ser tocado. Ele é usado para parar um 
     * determinado som a qualquer momento. */
    int playId = mSoundPool.play(
      mSoundPoolMap.get(index), // ID do som
      streamVolume, // volume da esquerda
      streamVolume, // volume da direita
      1, // prioridade 
      0, // -1 toca repetidamente, 
         // n = número de repetições)
      1  // pitch. 0.5f metade da velocidade
         // 1 = normal e 2 = dobro da velocidade
      );
    
    // adiciona o id da transação na pilha
    mSongsTransactions.push(playId);
  }

  public void stopSounds() {
    // Percorre todos os ids da pilha e manda
    // parar todos os sons
    while (mSongsTransactions.size() > 0)
      mSoundPool.stop(mSongsTransactions.pop());
  }

  // Libera os recursos alocados
  public void cleanup() {
    mSoundPool.release();
    mSoundPool = null;
    mSoundPoolMap.clear();
    mSongsTransactions.clear();
    mAudioManager.unloadSoundEffects();
  }
}

Com a classe acima, podemos executar duas mídias concorrentemente e com um tempo de resposta muito bom. Vejam como utiliza-la abaixo:
public class TesteSomActivity 
  extends Activity 
  implements OnClickListener{

  SoundManager sm;
 
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    // Informa que a aplicação controlará o 
    // volume da media do telefone pelos botões
    // de volume do aparelho
    setVolumeControlStream(
      AudioManager.STREAM_MUSIC);
        
    sm = SoundManager.getInstance(this);
    sm.addSound(R.raw.explosao);
    sm.addSound(R.raw.musica);
        
    ((Button)findViewById(R.id.btnStart1)).
      setOnClickListener(this);
    ((Button)findViewById(R.id.btnStart2)).
      setOnClickListener(this);
    ((Button)findViewById(R.id.btnStop)).
      setOnClickListener(this);
  }

  public void onClick(View v) {
    if (v.getId() == R.id.btnStart1){
      sm.playSound(0);

    } else if (v.getId() == R.id.btnStart2){
      sm.playSound(1);

    } else if (v.getId() == R.id.btnStop){
      sm.stopSounds();
    }
  }
}

O primeiro botão executará um efeito sonoro de explosão, enquanto que o segundo tocará a música do jogo. O terceiro parará ambos.
Se o aúdio for muito grande, é melhor optar por combinar o MediaPlayer para a música do jogo e SoundPool para os efeitos sonoros.

Para fazer com que sua aplicação controle o volume da mídia através dos botões de volume do aparelho (ou das teclas + e - no emulador) basta colocar a linha abaixo no onCreate da sua Activity:

setVolumeControlStream(AudioManager.STREAM_MUSIC);


Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

P.S.: Esse exemplo foi baseado em dois posts encontrados por dois colaboradores daqui do CESAR.
Post 1 e
Post 2

sábado, 1 de janeiro de 2011

LWUIT

Olá povo,

No primeiro post de 2011, falarei um pouquinho sobre o LWUIT (Lightweight UI Toolkit). Quem já desenvolveu alguma aplicação comercial com Java ME, sabe o como é complicado criar uma UI (User Interface) amigável com os componentes padrão. Os componentes LCDUI (Liquid Cristal Display UI) estão disponíveis no pacote javax.microedition.lcdui e trazem o benefício de manter a aparência da aplicação Java ME similar a uma aplicação nativa do aparelho. Porém, esses componentes são muito limitados dado o baixo poder de processamento dos dispositivos móveis na época em que esse framework foi concebido.

O LWUIT é um framework leve (como o próprio nome diz) e bastante poderoso para criação da interface gráfica de aplicações Java ME. Ele traz algumas similaridades com o Swing do Java SE e traz diversas funcionalidades interessantes como: uma boa quantidade de componentes; transições de tela; suporte a temas, estilos e fontes personalizadas; ajuste automático a diversos tamanhos de tela.

O LWUIT pode ser baixado separadamente a partir do site da Oracle. Clique aqui para baixar o ZIP com o JAR, documentação e exemplos de uso do framework.

Descompacte o conteúdo do arquivo em qualquer lugar da sua máquina e crie um novo projeto Java ME no Eclispe. Copie o arquivo LWUIT.jar (que está dentro do diretório lib) para seu projeto e adicione-o ao Build Path. Configure para que as classes do JAR do LWUIT sejam exportadas junto com o JAR da aplicação. Para fazer isso, clique com o botão direito sobre o projeto e clique em Properties. Ao abrir as opções do projeto, clique em Java Build Path no lado esquerdo, e na aba Order and Export, marque o checkbox do LWUIT.jar.

Uma vez com o projeto configurado, podemos executar o exemplo abaixo.


import java.io.IOException;

import javax.microedition.midlet.MIDlet;

import com.sun.lwuit.*;
import com.sun.lwuit.animations.CommonTransitions;
import com.sun.lwuit.events.*;
import com.sun.lwuit.layouts.*;
import com.sun.lwuit.plaf.UIManager;
import com.sun.lwuit.util.Resources;

public class TesteLWUIT_MIDlet extends MIDlet
// Interface genérica para tratar
// eventos dos componentes
implements ActionListener {

private Command cmdLista;
private Command cmdBack;
private Command cmdTabs;
private Command cmdExit;

private Button btn1;
private CheckBox checkbox;

private Form frmLista;
private Form frmPrincipal;
private Form frmTabs;

protected void startApp() {
iniciaLWUIT();
iniciaComandos();
iniciaFormPrincipal();
iniciaFormLista();
iniciaFormTabs();
}

protected void pauseApp() {
}

protected void destroyApp(boolean arg0){
notifyDestroyed();
}

private void iniciaLWUIT() {
// Inicializando o Display do LWUIT
Display.init(this);

try {
// Carrega o arquivo de recursos
Resources r = Resources.open("/LWUITtheme.res");

// Definindo o tema da aplicação
UIManager.getInstance().setThemeProps(
r.getTheme(r.getThemeResourceNames()[0]));

} catch (java.io.IOException e) {
}
}

// Iniciando comandos que são
// utilizados nos formulários
private void iniciaComandos() {
cmdLista = new Command("Lista");
cmdBack = new Command("Voltar");
cmdExit = new Command("Sair");
cmdTabs = new Command("Tabs");
}

// Criando tela principal
private void iniciaFormPrincipal() {
// Caixa de texto com capacidade de 20 caracteres
TextField txt = new TextField(20);

// Imagem para ser associada ao label
Image img = null;
Label imageLabel = null;

try {
// Carregando a imagem
img = Image.createImage("/duke.jpeg");
// Criando Label com a imagem
imageLabel = new Label(img);
// Define o texto do Label
imageLabel.setText("Label com Imagem");
// Define a posição do texto em relação a imagem
imageLabel.setTextPosition(Label.RIGHT);

} catch (IOException e) {
}

// Criando dois botões
btn1 = new Button("Botão 1");

// Definindo alinhamento do texto do botão
btn1.setAlignment(Label.CENTER);
// Informa que o botão pode
// receber foco com o direcional
btn1.setFocusable(true);

// Informa que a classe que
// tratará evento de clique
btn1.addActionListener(this);

// Criando RadioButtons
RadioButton rb1 = new RadioButton("Masculino");
RadioButton rb2 = new RadioButton("Feminino");
// Criando o RadioGrou e adicionando os botões
ButtonGroup group1 = new ButtonGroup();
group1.add(rb1);
group1.add(rb2);

// Checkbox
checkbox = new CheckBox("Não selecionado");
checkbox.addActionListener(this);

// Combobox
String[] content = {
"Vermelho", "Azul", "Verde", "Amarelo" };
ComboBox comboBox = new ComboBox(content);

// Inicializando e configurando o formulário
frmPrincipal = new Form();
frmPrincipal.setTitle("Formulário");
// Definindo o layout do formulário.
// BoxLayout adiciona os componentes um abaixo do
// outro (Y_AXIS) ou um ao lado do outro (X_AXIS)
frmPrincipal.setLayout(
new BoxLayout(BoxLayout.Y_AXIS));
// Adicionando componentes
frmPrincipal.addComponent(new Label("Nome:"));
frmPrincipal.addComponent(txt);
frmPrincipal.addComponent(imageLabel);
frmPrincipal.addComponent(btn1);
frmPrincipal.addComponent(rb1);
frmPrincipal.addComponent(rb2);
frmPrincipal.addComponent(comboBox);
frmPrincipal.addComponent(checkbox);
// Adicionando comandos
frmPrincipal.addCommand(cmdExit);
frmPrincipal.addCommand(cmdLista);
frmPrincipal.addCommand(cmdTabs);
// Definindo qual classe
// tratará eventos de comandos
frmPrincipal.addCommandListener(this);
// Definindo animação que será feita
// ao fechar e exibir o form
// Neste caso, Slide horizontal
frmPrincipal.setTransitionOutAnimator(
CommonTransitions.createSlide(
CommonTransitions.SLIDE_HORIZONTAL,
false, 200));
frmPrincipal.setTransitionInAnimator(
CommonTransitions.createSlide(
CommonTransitions.SLIDE_HORIZONTAL,
true, 200));
// Exibe o form
frmPrincipal.show();
}

// Inicializa uma tela de Lista
private void iniciaFormLista() {
String[] itens = {
"Opção 1", "Opção 2", "Opção 3",
"Opção 4", "Opção 5", "Opção 6",};
List lista = new List(itens);

frmLista = new Form("Exemplo Lista");
// Utilizando o BorderLayout
// Ele utiliza o conceito de NORTE,
// SUL, LESTE, OESTE e CENTRO
// Ao deixar um componente no centro
// ele ocupará o espaço restante da tela,
// como só tem um, ele preencherá toda a tela
frmLista.setLayout(new BorderLayout());
frmLista.addComponent(BorderLayout.CENTER, lista);
frmLista.addCommand(cmdBack);
frmLista.addCommandListener(this);
}

// Inicializa tela com abas
private void iniciaFormTabs() {
// Inicializa o painel de abas,
// mostrandos-as em cima
TabbedPane tabbedPane =
new TabbedPane(TabbedPane.TOP);
// Cria as abas adicionando um Label nas
// duas primeiras e um TextArea na terceira.
tabbedPane.addTab("Aba 1",
new Label("Label na primeira aba"));
tabbedPane.addTab("Aba 2", new Label("Aba 2"));
tabbedPane.addTab("Aba 3", new TextArea());

frmTabs = new Form("Exemplo Tabs");
frmTabs.setLayout(new BorderLayout());

frmTabs.addComponent(
BorderLayout.CENTER, tabbedPane);
frmTabs.addCommand(cmdBack);
frmTabs.addCommandListener(this);
}

// Tratando eventos das telas
public void actionPerformed(ActionEvent event) {
if (event.getSource() == cmdLista){
frmLista.show();

} else if (event.getSource().equals(cmdBack)){
frmPrincipal.show();

} else if (event.getSource() == btn1 ||
event.getSource() == cmdTabs){
frmTabs.show();

} else if (event.getSource() == checkbox){
if (checkbox.isSelected()) {
checkbox.setText("Selecionado");
} else {
checkbox.setText("Não selecionado");
}

} else if (event.getSource() == cmdExit){
destroyApp(true);
}
}
}


Abaixo temos um screenshot das telas da aplicação em execução:




Mas porque diabos a aplicação ficou com esse aspecto? E se eu quiser mudar o esquema de cores ou fontes ou qualquer outra coisa?

Se observarmos no método iniciaLWUIT, carregamos um arquivo de recursos chamado LWUITtheme.res. Esse arquivo já vem com o LWUIT e está localizado no diretório LWUIT_1_4\LWUITDemo\src. O LWUIT trabalha com um único arquivo de recursos que permite criar temas para definir as cores e fontes de cada elemento da UI de uma aplicação. Para criar e editar esses arquivos de recursos o LWUIT disponibiliza um editor visual que pode inclusive ser utilizado por designers. Esse editor está dentro do diretório util/ResourceEdit.exe (usuários não-windows podem executar o JAR diretamente que está no mesmo diretório). Abaixo temos um screenshot do editor, se quiser abrir o tema é só abrir o arquivo LWUITtheme.res com a ferramenta:



Alguns telefones podem não gostar do tamanho que o JAR pode ficar (em torno de 500KB) :) Para isso utilize o Proguard para obfuscar o código-fonte da aplicação e esse tamanho diminuirá sensivelmente.

4br4ç05,
nglauber