Dispositivos Móveis para as Massas: Palavras e gestos com Overheard Word

Integre de forma programática código de terceiros na sua UI Android

Você pode se sentir como uma criança em uma loja de doces pegando códigos de terceiros no GitHub ou outro repositório, mas ainda há alguns truques para integrar esse código com sua UI Android. Este mês, Andrew Glover mostra como melhorar o aplicativo demo Overheard Word com um mecanismo de palavra JSON e um pouco de funcionalidade de gestos integrada. De fato, o Android acomoda facilmente códigos de outras pessoas, mas você ainda precisa fazer uma lógica cuidadosa se quiser que a UI do seu aplicativo opere sem problemas.

Andrew Glover, Author and developer, App47

Andrew GloverAndrew Glover é desenvolvedor e entusiasta da tecnologia remota. É atualmente CTO da App47 e é fundador da estrutura easyb behavior-driven development (BDD). Também é coautor de três livros: Continuous Integration, Groovy in Action e Java Testing Patterns. É possível acompanhar o Andrew lendo seu blog e segui-lo no Twitter.



25/Abr/2014

Se você está acompanhando os demos até agora nesta série, então já aprendeu alguns aspectos básicos sobre desenvolvimento para Android. Além de configurar um ambiente de desenvolvimento Android e criar seu primeiro aplicativo Hello World, você aprendeu a substituir toques de botão com gestos de deslizamento e a implementar menus (ou barras de ação) e ícones em um aplicativo remoto customizado. Neste artigo, você continuará no mesmo caminho e aprenderá como usar bibliotecas de terceiros para aumentar ou aprimorar a funcionalidade de um aplicativo. Primeiro vamos instalar algumas bibliotecas de software livre e ler os arquivos, e, em seguida, vamos integrar de forma programática a nova funcionalidade com a UI do aplicativo demo.

Como fiz em artigos anteriores, usarei o aplicativo Overheard Word para fins de demonstração. Se você ainda não o fez, deve clonar o repositório GitHub do Overheard Word para poder acompanhar.

Sobre esta Série

A distribuição de aplicativos remotos está explodindo, assim como o mercado para habilidades de desenvolvimento para dispositivos móveis. Esta nova série apresenta o cenário de dispositivos móveis para desenvolvedores com experiência em programação, mas que são iniciantes nessa área. Comece criando aplicativos nativos em código Java e depois expanda suas habilidades para linguagens de JVM, estruturas de script, HTML5/CSS/JavaScript, ferramentas de terceiros e mais. Passo a passo, você irá dominar as qualificações necessárias para atender praticamente qualquer cenário de desenvolvimento para dispositivo móvel.

Thingamajig: Um mecanismo de palavra plugável

Overheard Word é um aplicativo de inglês que ajuda os usuários a aprender novas palavras e a ampliar seu vocabulário em qualquer lugar. Em artigos anteriores, começamos com o desenvolvimento de um aplicativo básico e, em seguida, adicionamos gestos de deslizamento para facilitar a navegação e ícones para embelezar a UI. Até aqui tudo bem, mas este aplicativo não irá muito longe sem um ingrediente que está faltando: Overheard Word precisa de palavras!

Para que o Overheard Word tenha um grande vocabulário, eu criei um pequeno mecanismo de palavras, Thingamajig, que contém a noção de uma palavra e suas definições correspondentes. Thingamajig cria palavras e suas definições através de documentos JSON e não depende de forma alguma do Android. Assim como o aplicativo Overheard Word, meu mecanismo de palavra está hospedado no GitHub. É possível clonar o repositório ou fazer o download do código-fonte e executar ant.

Você terá então um arquivo JAR leve, de 8 KB, que pode ser copiado para libs do JViews Maps. Se o projeto Android está configurado corretamente, o IDE reconhece automaticamente qualquer arquivo no diretório libs como uma dependência.

Por ora, o código no mecanismo de palavra é inicializado por meio de uma instância de documento JSON. O documento JSON poderia ser um arquivo em um sistema de arquivos em um dispositivo, uma resposta a uma solicitação de HTTP, ou mesmo uma resposta a uma consulta de banco de dados — por ora isso não importa. O que importa é que você tem uma biblioteca legal que permite trabalhar com objetos Word. Cada objeto Word contém uma coleção de objetos Definition e uma parte do discurso correspondente. Embora palavras e suas definições sejam mais complexas do que esse relacionamento simples, isso servirá por ora. Depois podemos incluir frases de exemplo, sinônimos e antônimos.


Bibliotecas de terceiros no Android

É muito fácil integrar uma biblioteca de terceiros em projetos Android. De fato, cada projeto inicial Android inclui um diretório especial, libs, onde é possível colocar JARs de terceiros. Durante o processo de construção do Android, arquivos JVM normais e JARs de terceiros são convertidos para que sejam compatíveis com Dalvik, a VM especializada desse sistema.

Restrição ambiental

É possível incluir qualquer biblioteca de terceiros imaginável no aplicativo, mas não esqueça das limitações de um ambiente de dispositivo móvel. Afinal de contas, o dispositivo que está executando o aplicativo não é um poderoso servidor da web! Os usuários agradecerão se você economizar energia de processamento e minimizar o tamanho do download de complementos de terceiros.

Além de plugar a biblioteca do mecanismo de palavras no aplicativo, irei incluir outro aplicativo de terceiros chamado Gesticulate. Assim como a biblioteca do mecanismo de palavra, é possível obter o Gesticulate por clonagem ou download do código-fonte no GitHub. Em seguida, execute ant e você terá um arquivo JAR, que pode ser colocado na pasta libs do projeto.


Atualizando a UI

Agora chegamos à parte principal deste artigo, que trata de atualizar a UI para integrar todo o código de terceiros que você pegou de graça. Felizmente eu fiz meus preparativos para esse momento com o aplicativo Overheard Word. Quando escrevi a primeira iteração do aplicativo, defini um layout simples que consistia em uma palavra, sua parte de discurso e uma definição, como mostra a Figura 1:

Figura 1. Visualização padrão do Overheard Word

O layout é definido com texto de marcador, que irei substituir com palavras reais provenientes do mecanismo de palavra plugável. Portanto, irei inicializar uma série de palavras, pegar uma e usar seus valores para atualizar a UI.

Para atualizar a UI, é preciso obter um manipulador para os elementos individuais de visualização e fornecer valores a eles. Por exemplo, uma palavra exibida, como Pedestrian, é definida como um TextView cujo ID é word_study_word, como podemos ver na Lista 1:

Lista 1. TextView definido em um layout
 <TextView
        android:id="@+id/word_study_word"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="60dp"
        android:textColor="@color/black"
        android:textSize="30sp" 
        android:text="Word"/>

Se você olhar atentamente, verá que eu defini o texto como "Word" no XML, mas é possível definir esse valor programaticamente obtendo uma referência para a instância TextView e chamando setText.

Antes de prosseguir, preciso criar uma lista de palavras. Você talvez lembre que o mecanismo de palavra pode criar instâncias Word através de documentos JSON, portanto, basta configurar o aplicativo Android para incluir e ler esses arquivos customizados.

Trabalhando com arquivos: Os diretórios res and raw

Um aplicativo Android, por padrão, tem acesso para ler o sistema de arquivos subjacente de um dispositivo, mas você apenas tem acesso ao sistema de arquivos local através do próprio aplicativo. Assim, é possível incluir arquivos no aplicativo e referenciá-los. (Isso significa que é possível ler e gravar arquivos locais do aplicativo, mas para gravar no sistema de arquivos fora do aplicativo, como em um cartão SD, é preciso ter permissões especializadas.) Como o mecanismo de palavras pode tomar um documento JSON para inicializar uma lista de palavras, nada me impede de incluir um documento JSON que o aplicativo lerá no tempo de execução.

Assim como os ativos de ícones que apresentei no artigo anterior, é possível armazenar arquivos no res do JViews Maps. Caso sejam necessários arquivos customizados, eu prefiro incluí-los em um diretório chamado raw. Qualquer arquivo colocado nesse diretório pode ser referenciado por meio do arquivo R gerado, que já usei algumas vezes no Overheard Word. Basicamente, a plataforma Android pega ativos do diretório res e constrói uma classe chamada R, que em seguida fornece um manipulador para esses ativos. Se o ativo é um arquivo, o arquivo R fornece uma referência para abrir o arquivo e obter seu conteúdo.

Eu crio um diretório raw dentro da estrutura de diretório res e coloco um documento JSON nele, como mostra a Figura 2:

Figura 2. O diretório raw com novas palavras

Em seguida, o Eclipse reconstrói o projeto, e o arquivo R recebe uma referência útil para um novo arquivo, como mostra a Figura 3:

Figura 3. O arquivo R atualizado

Com um manipulador do arquivo, é possível abrir, ler e por fim criar um documento JSON para servir como base para a geração de uma lista de palavras.

Desenvolvendo uma lista de palavras

Quando o aplicativo inicializar, eu dispararei uma série de etapas que carregam o documento JSON e constroem uma lista de palavras. Criarei um método chamado buildWordList para lidar com isso, como podemos ver na Lista 2:

Lista 2. Lendo o conteúdo de um arquivo no Android
private List<Word> buildWordList() {
  InputStream resource = 
    getApplicationContext().getResources().openRawResource(R.raw.words);
  List<Word> words = new ArrayList<Word>();
  try {
    StringBuilder sb = new StringBuilder();
    BufferedReader br = new BufferedReader(new InputStreamReader(resource));
    String read = br.readLine();
    while (read != null) {
        sb.append(read);
        read = br.readLine();
    }
    JSONObject document = new JSONObject(sb.toString());
    JSONArray allWords = document.getJSONArray("words");
    for (int i = 0; i < allWords.length(); i++) {
        words.add(Word.manufacture(allWords.getJSONObject(i)));
    }
  } catch (Exception e) {
    Log.e(APP, "Exception in buildWordList: " + e.getLocalizedMessage());
  }
  return words;
}

Você perceberá algumas coisas acontecendo em buildWordList. Primeiro, observe como InputStream é criado usando chamadas da plataforma Android que fazem referência em última instância ao arquivo words.json localizado em raw do JViews Maps. Eu não uso um String para representar um caminho, o que faz com que o código possa ser usado em vários dispositivos. Em seguida, eu uso uma biblioteca JSON incluída na plataforma Android para converter o conteúdo (representado por um String) em uma série de documentos JSON. O método estático manufacture em Word toma um único documento JSON que representa uma palavra.

O formato JSON para uma palavra é mostrado na Lista 3:

Lista 3. JSON representa uma palavra
{
  "spelling":"sagacious",
  "definitions": [
     {
      "part_of_speech":"adjective",
      "definition":"having or showing acute mental discernment
        and keen practical sense; shrewd"
     }
    ]
}

Minha classe WordStudyEngine é a fachada principal do Thingamajig. Essa classe produz palavras aleatórias e funciona com base em uma lista de instâncias Words. Portanto, a próxima etapa é inicializar o mecanismo com a WordList, recém-criada, como mostra a Lista 4:

Lista 4. Inicializando WordStudyEngine
List<Word> words = buildWordList();
WordStudyEngine engine = WordStudyEngine.getInstance(words);

Quando houver uma instância inicializada do mecanismo, é possível pedir a ela uma palavra (que é escolhida a esmo, de forma aleatória) e atualizar os três elementos da UI de forma correspondente. Por exemplo, eu posso atualizar a porção palavra de uma definição, como mostra a Lista 5:

Lista 5. Atualizando um elemento de UI programaticamente
Word aWord = engine.getWord();
TextView wordView = (TextView) findViewById(R.id.word_study_word);
wordView.setText(aWord.getSpelling());

findViewById na Lista 5 é uma chamada da plataforma Android que toma um ID número inteiro, que pode ser obtido do arquivo R do aplicativo. É possível definir programaticamente o texto para um TextView. Também é possível configurar o tipo ou cor da fonte ou o tamanho da exibição de texto desta forma: wordView.setTextColor(Color.RED).

Na Lista 6, eu sigo essencialmente o mesmo processo para atualizar a definição e elementos de parte de discurso da UI do aplicativo:

Lista 6. Mais atualizações programáticas
Definition firstDef = aWord.getDefinitions().get(0);
TextView wordPartOfSpeechView = (TextView) findViewById(R.id.word_study_part_of_speech);
wordPartOfSpeechView.setText(firstDef.getPartOfSpeech());

TextView defView = (TextView) findViewById(R.id.word_study_definition);
defView.setText(formatDefinition(aWord));

Observe na Lista 5 e a Lista 6 como o arquivo R é útil para referenciar elementos de layout por nome. O método formatDefinition, que reside na classe Activity, toma uma sequência de caracteres que representa uma definição e coloca o primeiro caractere em maiúscula. O método também coloca um ponto final ao final da frase, se não houver um. (Observe que Thingamajig não tem nada a ver com formatação — é apenas um mecanismo de palavra!)

Eu terminei de atualizar esses elementos de UI, então agora irei iniciar o aplicativo para verificar os resultados. Et voilà! Agora eu tenho uma palavra de verdade para estudar!

Figura 4. Overheard Words tem palavras!

Adicionando gestos: Associando deslizamentos a palavras

Agora que é possível exibir uma palavra, quero que os usuários possam deslizar rapidamente por todas as palavras no mecanismo. Para facilitar minha vida, irei usar o Gesticulate, minha própria biblioteca que calcula a velocidade e a direção de deslizamentos. Também coloquei a lógica do deslizamento em um método chamado initializeGestures.

Depois de inicializar os gestos de deslizamento, a primeira etapa é mover a lógica que exibe uma única palavra para um novo método, que posso chamar para exibir uma nova palavra quando alguém deslizar os dedos. O método onCreate atualizado (chamado inicialmente quando o Android cria a instância do aplicativo) é mostrado na Lista 7:

Lista 7. onCreate exibe agora uma palavra enquanto inicializa gestos
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  Log.d(APP, "onCreated Invoked");
  setContentView(R.layout.activity_overheard_word);
  
  initializeGestures();
  
  List<Word> words = buildWordList();
  
  if (engine == null) {
    engine = WordStudyEngine.getInstance(words);
  }
  Word firstWord = engine.getWord();
  displayWord(firstWord);
}

Observe a variável engine, que eu defini como uma variável de membro private status da própria OverheardWord Activity. Em breve eu explicarei porque fiz isso.

Em seguida, eu vou ao método initGestureDetector, que como você pode acompanhar no código que clonou, é referenciado em initializeGestures. Você deve lembrar que o método initGestureDetector possui a lógica para realizar uma ação quando um usuário desliza o dedo para cima, para baixo, direita ou esquerda na tela do dispositivo. Em Overheard Word, quero exibir uma nova palavra quando o usuário desliza da direita para a esquerda (que é um deslizamento para a esquerda). Para começar, removo a mensagem Toast, que era o marcador do código, e substituo por uma chamada para displayWord, como mostra a Lista 8:

Lista 8. initGestureDetector agora exibe uma palavra quando o usuário desliza o dedo para a esquerda
private GestureDetector initGestureDetector() {
  return new GestureDetector(new SimpleOnGestureListener() {
    public boolean onFling(MotionEvent e1, MotionEvent e2, 
      float velocityX, float velocityY) {
      try {
       final SwipeDetector detector = new SwipeDetector(e1, e2, velocityX, velocityY);
       if (detector.isDownSwipe()) {
         return false;
       } else if (detector.isUpSwipe()) {
         return false;
       } else if (detector.isLeftSwipe()) {
         displayWord(engine.getWord());
       } else if (detector.isRightSwipe()) {
         Toast.makeText(getApplicationContext(), 
           "Right Swipe", Toast.LENGTH_SHORT).show();
       }
      } catch (Exception e) {}
     return false;
    }
  });
}

O mecanismo de palavra, representado pela variável engine, precisa estar acessível ao longo de Activity. Foi por isso que a defini como uma variável de membro, garantindo que uma nova palavra seja exibida a cada vez que houver um deslizamento para a esquerda.


Deslizar o dedo para frente e para trás

Agora, quando abro o aplicativo e começo a deslizar o dedo, vejo uma nova palavra e uma definição sempre que deslizo para a esquerda. Mas ao deslizar na outra direção, aparece apenas uma pequena mensagem que informa que eu fiz o gesto. Não seria melhor se fosse possível deslizar o dedo para a direita para retornar à palavra anterior?

Retornar não parece ser difícil. Talvez seja possível usar uma pilha e retirar o elemento superior que foi colocado pelo deslizamento para a esquerda anterior? Mas essa ideia cai por terra quando um usuário desliza o dedo para a esquerda após ter retornado. Se a posição superior fosse retirada, uma nova palavra seria exibida em vez daquela que o usuário viu anteriormente. Pensando nisso, minha tendência é tentar uma abordagem de lista vinculada, na qual as palavras visualizadas anteriormente podem ser escolhidas conforme o usuário desliza o dedo para frente e para trás.

Começo criando uma LinkedList de todas as palavras que foram exibidas, como mostra a Lista 9. Em seguida, manterei um ponteiro para o índice do elemento na lista que possa ser usado para recuperar palavras que foram vistas. Obviamente, essas variáveis de membro serão static. Também irei inicializar o ponteiro com o valor -1 e incrementá-lo sempre que adicionar uma palavra a LinkedList. Assim como a maioria das coleções semelhantes a array em qualquer linguagem, LinkedList começa o índice a partir de 0.

Lista 9. Novas variáveis de membro
private static LinkedList<Word> wordsViewed;
private static int viewPosition = -1;

Na Lista 10, eu inicializei LinkedList no método onCreate do aplicativo:

Lista 10. Inicializando LinkedList
if (wordsViewed == null) {
  wordsViewed = new LinkedList<Word>();
}

Agora, ao exibir a primeira palavra, é preciso adicioná-la à instância LinkedList (wordsViewed) e incrementar a variável de ponteiro, viewPosition, como mostra a Lista 11:

Lista 11. Não se esqueça de incrementar a posição de visualização
Word firstWord = engine.getWord();
wordsViewed.add(firstWord);
viewPosition++;
displayWord(firstWord);

A lógica do deslizamento

Agora estou no centro da lógica, o que exige um pouco de raciocínio. Quando um usuário desliza o dedo para a esquerda, o aplicativo deve mostrar a próxima palavra, e quando ele desliza para a direita, o aplicativo deve mostrar a palavra anterior. Se o usuário desliza o dedo para a direita novamente, então a segunda palavra anterior deve ser mostrada e assim por diante. Isso deve continuar até que o usuário chegue na primeira palavra exibida. Se o usuário então começa a deslizar o dedo para a esquerda novamente, as palavras devem aparecer na mesma ordem que antes. Essa última parte é complicada, pois a implementação padrão de WordStudyEngine é retornar palavras em ordem aleatória.

Na Lista 12, acho que descobri a lógica para o que quero fazer. A primeira avaliação booleana está contida em um método chamado listSizeAndPositionEql, que é simplesmente: wordsViewed.size() == (viewPosition + 1).

Se o valor de listSizeAndPositionEql for verdadeiro, então o aplicativo exibirá uma nova palavra através de displayWord. Se, no entanto, listSizeAndPositionEql for falso, o usuário deve ter deslizado o dedo para trás; assim o ponteiro viewPosition é usado para recuperar a palavra correspondente na lista.

Lista 12. Uma LinkedList para rolar para frente e para trás
public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) {
 try {
  final SwipeDetector detector = new SwipeDetector(e1, e2, velX, velY);
  if (detector.isDownSwipe()) {
   return false;
  } else if (detector.isUpSwipe()) {
   return false;
  } else if (detector.isLeftSwipe()) {
    if (listSizeAndPositionEql()) {
     viewPosition++;
     Word wrd = engine.getWord();
     wordsViewed.add(wrd);
     displayWord(wrd);
    } else if (wordsViewed.size() > (viewPosition + 1)) {
       if (viewPosition == -1) {
        viewPosition++;
       } 
      displayWord(wordsViewed.get(++viewPosition));
    } else {
      return false;
   }
  } else if (detector.isRightSwipe()) {
    if (wordsViewed.size() > 0 && (listSizeAndPositionEql() || (viewPosition >= 0))) {
      displayWord(wordsViewed.get(--viewPosition));
    } else {
      return false;
    }
  }
 } catch (Exception e) {}
 return false;
}

Observe que, se viewPosition é -1, o usuário deslizou o dedo para trás até retornar ao começo — nesse caso, o ponteiro para a lista é incrementado de volta para 0. Isso porque não existe elemento -1 em uma implementação Java de LinkedList. (Em algumas outras linguagens, valores posicionais negativos começam no final da lista, então -1 seria o elemento final.)

Lidar com a lógica de deslizamento para a direita é bem fácil depois de entender como funciona o deslizamento para a esquerda. Basicamente, se há uma palavra na instância wordsViewed, é preciso usar um valor decrementado do ponteiro para acessar essa palavra.

Experimente: Inicie uma instância do Overheard Word e deslize para frente e para trás para estudar mais palavras. Cada vez que você deslizar o dedo, a UI será atualizada de forma apropriada.


Conclusão

Overheard Word está ficando muito bom agora que começamos a incluir mais recursos interessantes em cima do shell Android básico. Ainda há muito a se fazer, é claro, mas agora temos um aplicativo funcional que lida com deslizamentos, possui ícones e menus e até mesmo lê um arquivo customizado de terceiros contendo palavras. No próximo mês, irei mostrar como alterar ainda mais o estilo do Overheard Word e implementar um questionário.

Recursos

Aprender

Obter produtos e tecnologias

  • Overheard Word: Um aplicativo Android demo em construção, hospedado no GitHub.
  • Thingamajig: Um mecanismo simples de palavras e definições para Overheard Word.
  • Gesticulate: Faz os cálculos da detecção de deslizamentos do Android para você.
  • Faça o download do Android: O SDK Android oferece as bibliotecas de API e as ferramentas de desenvolvedor necessárias para construir, testar e depurar aplicativos para Android.

Discutir

  • Participe da comunidade do developerWorks . Conecte-se a outros usuários do developerWorks enquanto estiver explorando os blogs, fóruns, grupos e wikis direcionados a desenvolvedores.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java, Desenvolvimento móvel
ArticleID=969507
ArticleTitle=Dispositivos Móveis para as Massas: Palavras e gestos com Overheard Word
publish-date=04252014