domingo, 7 de agosto de 2011

MANIFESTO ANDROID

    Eu me chamo Cleverton Heusner, tenho 29 anos, sou aficionado por tecnologia e vou falar um pouco do meu relacionamento com Java.
    Moro no sul do Pará há muitos anos e em 2008, comecei a estudar Java. Nessa época, trabalhava em um escritório de contabilidade durante o dia e à noite cursava Análise de Sistemas, com sua grade curricular tapa-buracos. Eu sempre queria estar a sós com minha bíblia de Java e conhecer cada vez mais a linguagem.
    O tempo livre dedicado a praticar tudo o que eu lia sobre Java era relativamente curto. Logo, eu me contentava com códigos simples. Tudo em que pensava era desenvolver games e aplicativos para celulares profissionalmente. O mercado de softwares onde moro é nulo, mas não dei bola.
    A formatura se aproximava e logo eu teria mais tempo para um segundo e apetitoso livro que me esperava. Era sobre desenvolvimento de games pra celulares com JavaME.
    Depois de me socializar com algumas novas ferramentas e avançar algumas páginas no novo livro, eu estava pronto para exibir um personagem na tela e fazê-lo reagir a meus comandos. Quando consegui isso, só posso dizer que foi uma das maiores alegrias que já senti. Passei o build para um celular que tinha comprado apenas pra isso e comecei a exibir aquela façanha para todos em minha volta, ignorando se tinham ou não algum interesse pelo assunto.
    Essa experiência melhorou o ritmo de meus estudos. Eu acompanhava reportagens sobre o mercado de games com ansiedade, pois sabia que ainda não estava pronto. Quando esse momento chegasse, eu deveria estar preparado para deixar a cidade.
    Aos poucos, eu ia me dando conta do quão complexo e extenso era o código de um game, ainda que simples. Queria programar um jogo completo não apenas por prazer e conhecimento, mas porque eu precisava de um portfólio.
    Como precisava de mais tempo, fiz uma aposta arriscada e deixei meu emprego. Agora eu teria alguns meses para criar um game.
    Por aspirar a uma vaga de programador, priorizei o código. Os gráficos e sons peguei emprestados de games antigos, já que não contava com um artista. Não havia pra mim jeito mais divertido de se exercitar Java.
    A meta inicial era um game completo. Porém, já havia se passado três meses e eu tinha programado apenas uma fase. Como não me restava muito tempo, resolvi usar aquele simples e suado demo como portfólio. Filmei a gameplay e publiquei no youtube. Logo em seguida, corri para comunidades, blogs e fóruns pedindo críticas. Para minha surpresa, a maioria delas foi calorosa e até consegui formar contatos com os mesmos interesses.
    Meu próximo passo era contatar estúdios no Brasil, na esperança de uma oportunidade. Antes que eu fizesse isso, uma empresa gringa achou meu trabalho no youtube e me convidou a participar de projetos como freelancer. Agora eu tinha em mãos o que queria, mesmo morando no fim do mundo. Embora se tratasse de trabalho, eu estava agora em um ambiente nerd e interativo. Era meu primeiro projeto real, o que me levou a buscar muitos conhecimentos que me faltavam, amadurecer meu espírito de equipe e de quebra, afiar o inglês.
    Um mês depois, tudo ainda ia bem. Era agosto, quando começa a chover nesta região. O que eu não lembrava é que os serviços de energia elétrica e internet são duramente castigados por aqui em períodos de chuva. Blackouts e quedas de conexão passaram a comprometer drasticamente o ritmo do trabalho.
    Após muitos atrasos, o projeto finalmente havia chegado à fase de porting, na qual um mesmo aplicativo precisa ser testado em vários celulares diferentes. Por diversas vezes, enquanto o tester me reportava algum bug em determinado aparelho, a net e/ou a energia elétrica resolvia me deixar na mão.
    Como eu seria pago apenas após a conclusão do game, aquela experiência estava se tornando cara, apesar de todo o conhecimento adquirido até então. Fui obrigado a deixar o grupo e procurar por uma oportunidade similar no Brasil, mas desta vez bem longe do Pará.
    Agora eu tinha a engine completa para um game como portfólio. Infelizmente, o visual e a trilha sonora não pertenciam a mim. Antes de me unir ao grupo que acabara de deixar, conheci um artista promissor enquanto divulgava meu demo. Embora focado em arte 3D, substitui toda a carcaça 2D de forma brilhante.
    Lembro que quando havia finalizado o game oficial, sua velocidade deixava a desejar em certos aparelhos, mesmo com imagens e sons otimizados ao máximo. Suspeitei que devesse melhorar o código, mas não sabia onde. Enquanto Juliano se ocupava com a arte, eu li o que pude sobre técnicas de otimização de código. Depois de uma semana lapidando o código com base no que havia estudado, tive uma surpresa. A velocidade do game havia aumenado de forma esmagadora, me ensinando o efeito do código sobre a mesma.
    Após algumas pesquisas, vi o quanto a demanda por JavaME tinha diminuído. Eu entendi que precisaria dominar Android ou iPhone. Há quatro meses, tenho estudado e praticado a plataforma Android. Minha simpatia pelo Android me levou a erguer este blog, que inicialmente será um portfólio. Com o tempo, pretendo fazer deste lugar um grande acervo de artigos cobrindo o desenvolvimento Android, aspectos da linguagem Java e a criação de games unindo estas duas armas.
    Este é meu primeiro blog e seu visual está um tanto capenga, mas irei melhorar. Só peço que leiam e comentem os artigos. Abaixo, arte e video de Juliano Silveira para o game de celular Cura em Gaia, que desenvolvemos a partir de minha antiga engine, a qual futuramente adaptaremos para Android.

Arte promocional de Cura em Gaia, game pra celulares em J2ME


Gameplay de Cura em Gaia

    Você pode ficar a par de outros trabalhos de Juliano Silveira acessando os links abaixo:

terça-feira, 2 de agosto de 2011

Recuperando e Lendo Arquivos de um Cartão de Memória - PARTE 1

    Embora o objetivo do artigo seja este, serão englobados outros conceitos importantes:
  • Reproduzir músicas, videos, imagens e texto;
  • Construir um layout básico utilizando Java;
  • Exibir uma animação de progresso;
  • Arquivos e Fluxos;
  • Elaboração de Menus;
  • Janela de alerta;
  • Intents;
  • ListViews.
    
    Por simplicidade, a aplicação reconhecerá apenas arquivos de vídeo (3gp), sonoros (mid, mp3), texto (txt) e imagens (png, gif, jpg). Para poder acompanhar esta série de artigos, é importante que você saiba instalar arquivos no emulador. Primeiro, abra o emulador em que
irá trabalhar. Após sua inicialização, será criado o seu sistema de arquivos, o qual você acessará com a ferramenta File Explorer. Para encontrá-la no eclipse, vá até a barra de ferramentas e siga o caminho: Windows > Show View > Other > File Explorer. A imagem a seguir mostra onde fica o cartão de memória, bem como os arquivos que foram instalados:
      No canto direito superior da janela acima, você deve usar o seguinte ícone para inserir arquivos na pasta que estiver selecionada:


     Sendo este projeto composto de duas telas, começaremos pela classe que representa a primeira delas.

ListaArquivos.java

1. private static int formatoSelecionado;
2. private static String nomeArqSelecionado;
3. private static ListView lista;
4. private static java.io.File sdcard;

    Começaremos pelas variáveis globais usadas nesta classe. campo formatoSelecionado guardará o tipo do arquivo correntemente selecionado na lista. nomeArqSelecionado, por sua vez, armazenará o nome do arquivo em si. A referência lista aponta para uma ListView, componente Android usado para listar tipos primitivos e objetos. A variável sdcard, por ser do tipo File, embora se refira tanto a diretórios quanto arquivos, neste caso armazenará o caminho para um cartão de memória.


CRIACAO DA TELA PRINCIPAL

01. @Override
02. public void onCreate(Bundle savedInstanceState)
03. {
04.     super.onCreate(savedInstanceState);
05.
06.     lista = new ListView(this);
07.     setContentView(lista);
08.
09.     lista.setOnItemSelectedListener(this);
10.
11.     sdcard = Environment.getExternalStorageDirectory();
12.
13.     alimentarLista();
14. }

    Agora destacaremos alguns pontos do método onCreate, cuja principal tarefa é criar a Activity em que vamos trabalhar. Esta tela contém um único view, portanto iremos declará-la em Java, dispensando um arquivo XML de layout, que é voltado a construção de telas mais complexas.
    Depois, precisaremos fazer com que nossa lista responda a seleções do usuário. Para isso, vamos associá-la ao método onItemSelected conforme linha 09:
    A linha 11 irá recuperar o diretório onde se encontra o cartão de memória do dispositivo, isto é, um valor do tipo File, que será referenciado por sdcard.
    A última instrução deste método irá invocar outro método, alimentarLista(). Falaremos dele no tópico seguinte.


ALIMENTANDO A LISTA

01. private final void alimentarLista()
02. {
03.     String[] listaArqExistentes = sdcard.list();
04.     String[] formatosArquivos = getResources().getStringArray(R.array.formatos_arquivos);
05.     List<String> listaArqProcurados = new ArrayList<String>();
06.
07.     final int MAX_FORMATOS = formatosArquivos.length;
08.     final int MAX_ARQUIVOS = listaArqExistentes.length;
09.
10.     for(int i = 0; i < MAX_ARQUIVOS; i++)
11.     {
12.         for(int j = 0; j < MAX_FORMATOS; j++)
13.         {
14.             if((listaArqExistentes[i].endsWith(formatosArquivos[j])))
15.             {
16.                 listaArqProcurados.add(listaArqExistentes[i]);
17.             }
18.         }
19.     }
20.
21.     if(listaArqProcurados.size() > 0)
22.     {
23.         ArrayAdapter<String> adaptadorArquivos = new ArrayAdapter<String>(this,
24.         android.R.layout.simple_list_item_1, listaArqProcurados);
25.
26.         lista.setAdapter(adaptadorArquivos);
27.     }
28.     else
29.     {
30.         AlertDialog.Builder msgErro = new AlertDialog.Builder(this);
31.         msgErro.setMessage("Nenhum arquivo encontrado!");
32.         msgErro.show();
33.     }
34. }

Vejamos agora o método encarregado de alimentar a lista. Se pelo menos um arquivo for
encontrado, a tela do dispositivo se apresentará da seguinte maneira:
Arquivo(s) localizado(s)
  Na linha 03, o método list() retorna um array com os nomes de todos os arquivos encontrados no cartão. Porém, nossa lista esta interessada em exibir apenas arquivos de texto (txt), imagens (jpg, png e gif), músicas (mp3 e mid) e vídeos (3gp). Para nos ajudar a filtrar esses formatos, foi criado o array formatosArquivos, que guardará os nomes das extensões desejadas.  Poderíamos ter declarado esse array no próprio código Java. Ao invés disso, optei por armazená-lo em um arquivo XML para depois recuperá-lo no código Java em si. O arquivo XML é criado dentro da pasta res/values/formatos_arquivos.xml do projeto e pode ser visto a seguir:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="formatos_arquivos">
<item>.mid</item>
<item>.mp3</item>
<item>.png</item>
<item>.jpg</item>
<item>.gif</item>
<item>.txt</item>
<item>.3gp</item>
<item>.mp4</item>
</string-array>
</resources>
   O mais gratificante é poder recuperar esse array no código Java com apenas uma linha:
getResources().getStringArray(R.array.formatos_arquivos);
Um vetor em Java pode ocupar muitas linhas, prejudicando a estética e legibilidade do código. Portanto, torna-se uma boa prática embuti-lo em um arquivo XML para depois recuperá-lo com uma simples linha.
    A seguir, iremos usar a variável listaArqProcurados para guardar apenas os nomes dos arquivos suportados pela aplicação. Mas por que usar uma List ao invés de um simples array? Mesmo sabendo que existem arquivos no cartão e em certo número, não temos certeza de seus formatos. Considerando que muitos não conhecem uma List e a abrangência do assunto, darei uma explicação suficiente para prosseguir com o artigo. Um array comum tem tamanho fixo, obtido no momento de sua criação. Já uma List não tem tamanho fixo, podendo este ser aumentado dinamicamente, conforme as necessidades da aplicação. Neste caso, o tamanho da List irá sempre depender do teste nas linhas 10-18, que buscará na lista de arquivos disponíveis no cartão apenas os formatos requeridos pelo aplicativo:
    Na linha 21, há um segundo teste, que verifica se pelo menos um arquivo das extensões exigidas foi encontrado, para então vincular listaArqProcurados à referência lista. Isso é conseguido com a classe ArrayAdapter, uma classe nativa do Android projetada para associar listas de objetos a uma ListView e a outros componentes. Tenha em mente que ListView e ArrayAdapter trabalham em equipe.
    No construtor desse ArrayAdapter, temos três parâmetros: o contexto no qual ele está inserido (nesse caso a Activity atual), um estilo pré-definido de ListView do Android e finalmente a lista de arquivos encontrados. A marcação <String> após o nome da classe indica que os elementos da lista são do tipo String. Por último, precisaremos vincular os elementos de List a ListView, conforme linha 26.
    Se nenhum arquivo dos requisitados for encontrado, uma mensagem de erro é lançada com ajuda da classe AlertDialog.Builder:

Arquivo(s) não localizado(s)


NOTA: A classe List é um tipo de Coleção. Consultando a documentação oficial do Java, você saberá mais a respeito.

Recuperando e Lendo Arquivos de um Cartão de Memória - PARTE 2

01. @Override
02. public final void onItemSelected(AdapterView<?> arg0, android.view.View arg1, int posItem, long arg3)
03. {
04.     nomeArqSelecionado = null;
05.     nomeArqSelecionado (String)lista.getItemAtPosition(posItem);
06.
07.     if(nomeArqSelecionado.endsWith(".mp3") ||
08.       (nomeArqSelecionado.endsWith(".mid")))
09.     {
10.         formatoSelecionado = MUSICA;
11.     }
12.     else if(nomeArqSelecionado.endsWith(".png") ||
13.             (nomeArqSelecionado.endsWith(".jpg") ||
14.             (nomeArqSelecionado.endsWith(".gif"))))
15.     {
16.          formatoSelecionado = IMAGEM;
17.     }
18.     else if(nomeArqSelecionado.endsWith(".txt"))
19.     {
20.         formatoSelecionado = TEXTO;
21.     }
22.     else if(nomeArqSelecionado.endsWith(".3gp") ||
23.             (nomeArqSelecionado.endsWith(".mp4")))
24.     {
25.         formatoSelecionado = VIDEO;
26.     }
27. }

Em seguida, um passeio pelo método onItemSelected(AdapterView<?> arg0,
View arg1,int posItem, long arg3), que será invocado cada vez que um item da lista for
selecionado. Iremos nos ater apenas ao terceiro parâmetro do método, que guarda a
posição do item que estiver selecionado no momento. A linha 05 aponta que é
possível recuperar um elemento da lista em uma dada posição.
    Nesta aplicação, cada item da lista corresponde ao nome de um arquivo recuperado do cartão de memória. O próximo passo é checar se o nome do arquivo termina com alguma
extensão aceita pelo aplicativo e sendo o caso, avisar à variável formatoSelecionado. Quando o usuário chamar o menu da aplicação, o título do item direito irá mudar de acordo com o
elemento selecionado na lista.


CRIANDO O MENU DA ACTIVITY PRINCIPAL

01. @Override
02. public final boolean onCreateOptionsMenu(Menu menu)
03. {
04.     SubMenu voltar = menu.addSubMenu("Sair");
05.     voltar.getItem().setOnMenuItemClickListener(this);
06.
07.     SubMenu lerArquivo = menu.addSubMenu("Ler Arquivo");
08.     lerArquivo.getItem().setOnMenuItemClickListener(this);
09.
10.     return super.onCreateOptionsMenu(menu);
11. }

    O método onCreateOptionsMenu(Menu menu) é chamado apenas uma vez, quando sua
Activity é criada. Como o próprio nome diz, ele cria um menu que será incorporado a Activity.
Analisando o corpo do método, vemos que cada um dos sub-menus é criado através dainstância da classe Menu com métodos bem intuitivos. Basicamente, damos a cada sub-menu um título e o preparamos para responder a eventos de clique/toque. Você também podeassociar a um sub-menu um ícone ao invés de um nome, assim como construir o menucompleto em um arquivo XML, mas isso não será abordado neste tutorial.


DEFININDO DINAMICAMENTE O TÍTULO DO MENU

01. @Override
02. public final boolean onPrepareOptionsMenu(Menu menu)
03. {
04.     final int LER_ARQUIVO = 1;
05.
06.     MenuItem lerArq = menu.getItem(LER_ARQUIVO);
07.
08.     switch(formatoSelecionado)
09.     {
10.     case MUSICA:
11.         lerArq.setTitle("Tocar Musica");
12.
13.         break;
14.     case VIDEO:
15.         lerArq.setTitle("Reproduzir Video");
16.
17.         break;
18.     case TEXTO:
19.         lerArq.setTitle("Visuazliar Texto");
20.
21.         break;
22.     case IMAGEM:
23.         lerArq.setTitle("Exibir Imagem");
24.
25.     break;
26.     }
27.
28.     return super.onCreateOptionsMenu(menu);
29. }

    Quando se seleciona a tecla menu, seja no emulador ou em um smarthphone, é chamado o método onPrepareOptionsMenu(Menu menu), que então exibe o menu completo na parte inferior da tela.  Aqui o título do sub-menu direito irá mudar de acordo com o item da lista que estiver selecionado.


ESCOLHENDO O ARQUIVO A REPRODUZIR

01. @Override
02. public final boolean onMenuItemClick(MenuItem item)
03. {
04.     if(item.getTitle() == "Sair")
05.     {
06.         finish();
07.     }
08.     else
09.     {
10.         Bundle arquivo = new Bundle();
11.
12.         arquivo.putInt("Formato", formatoSelecionado);
13.         arquivo.putString("DirArquivo", sdcard + "/" + nomeArqSelecionado);Selecionado);
14.
15.         startActivity(new android.content.Intent("REPRODUZIR_ARQUIVO").putExtras(arquivo));xtras(arquivo));
16.     }
17.
18.     return false;
19. }

É hora de entender o que ocorre quando se seleciona um dos itens do menu da tela
principal. Em primeiro lugar, é chamado o método onMenuItemClick(MenuItem item), que
recebe como parâmetro à referência ao item de menu selecionado. Para saber qual item de menu foi ativado pelo usuário, utilizamos o seu título, definido durante a criação do menu e alterado cada vez que um item da lista é selecionado.
    No código, vemos que se o título for igual ‘Sair’, é encerrado o programa com o método finish() da classe Activity. Internamente, ele chamará o método onDestroy(), que de fato destrói a Activity. Do contrário, a tela atual irá chamar por uma segunda Activity, a qual tem por  finalidade reproduzir o conteúdo do arquivo selecionado na lista.   Com a classe Bundle, passaremos informações extras ao chamar outra Activity, para dizer-lhe o que fazer quando esta for criada. A classe Bundle funciona como uma tabela de  registros, cada um com valores e chaves associadas. Note que depois de instanciar Bundle, nós alimentamos a tabela com dois registros: um com um valor inteiro, que avisará a Activity seguinte sobre o formato a reproduzir, e o segundo com o caminho completo do arquivo.
    Por fim, ao chamar a segunda Activity, note o parâmetro fornecido a sua Intent. Trata-se de uma String como uma referência mais amigável a outra tela. Essa nome é o mesma usado para registrar uma Activity no arquivo de manifesto do projeto:
    Chamando o método putExtras(Bundle arquivo) na Intent criada, nós estamos passando a tabela que acabamos de criar para a segunda tela.

ReproducaoArquivo.java 

01. private static int formato;
02. private final MediaPlayer player = new MediaPlayer();

Logo no início, alocamos duas variável globais. A variável formato permite a Activity atual saber que tipo de arquivo reproduzir e player tem o propósito de tocar uma música.
CRIACAO DA TELA DE REPRODUÇÃO DE ARQUIVOS

01. @Override
02. protected void onCreate(Bundle savedInstanceState)
03. {
04.     super.onCreate(savedInstanceState);
05.
06.     Bundle arquivo = getIntent().getExtras();
07.     formato = arquivo.getInt("Formato");
08.     String dirArq = arquivo.getString("DirArquivo");
09.
10.     switch(formato)
11.     {
12.     case IMAGEM:
13.         exibirImagem(dirArq);
14.      
15.        break;
16.     case MUSICA:
17.         tocarMusica(dirArq);
18.
19.         break;
20.     case VIDEO:
21.         reproduzirVideo(dirArq);
22.
23.         break;
24.     case TEXTO:
25.         visualizarTexto(dirArq);
26.
27.         break;
28.     }
29. }

Agora você ficará sabendo como a tela invocada intercepta as informações passadas pela tela chamadora. Na linha 06, a Activity recém-criada recuperou a Intent que lhe foi passada pela sua vizinha e ao mesmo tempo, a tabela anexada a ela, por meio de getExtras(). Com a tabela instanciada em mãos, obtemos os valores referentes ao formato e caminho do arquivo através das chaves associadas a eles na Activity anterior.
Após isso, um teste usará o a variável formato para determinar que tipo de arquivo será lido e a referência dirArq para saber onde o mesmo se encontra.
EXIBINDO A IMAGEM

01. private final void exibirImagem(String dirArq)
02. {
03.     ImageView imagem = new ImageView(this);
04.     imagem.setImageBitmap(android.graphics.BitmapFactory.decodeFile(dirArq));
05.
06.     setContentView(imagem, new LayoutParams(LayoutParams.FILL_PARENT,  LayoutParams.FILL_PARENT));
07. }

Estudaremos primeiro o que o método exibirImagem(dirArq) fará caso seja o escolhido. 
Na linha 03, criamos um componente ImageView, cuja função é exibir uma imagem na tela. Uma das formas de definir a imagem a ser exibida é por meio de um Bitmap, classe que representa uma imagem no Android.
Com a classe BitmapFactory, podemos obter um bitmap de diferentes fontes, sendo que nesse caso  a fonte adotada foi o caminho do arquivo passado pela Activity anterior, o que confirma a linha 04.
Passaremos a referência imagem para setContentView(View v, LayoutParams p), que 
configurará a aparência da tela. O primeiro parâmetro, como já sabemos, corresponde a
ImageView criada. Já o segundo é uma referência de LayoutParams, que determina como a
largura e altura devem aparecer na tela. A constante FILL_PARENT estica o componente ao máximo na horizontal ou na vertical. Isso quer dizer que mesmo que o tamanho real da imagem fosse de 50x50 pxs, ela seria alargada para preencher a tela inteira. Se quiséssemos exibi-la em seu tamanho real, substituiríamos FILL_PARENT por WRAP_CONTENT.
Imagem preenchendo uma Activity modal