Google Android Uma abordagem prática e didática Rafael Guimarães Sakurai Esse livro está à venda em http://leanpub.com/google-android Essa versão foi publicada em 2018-03-02 Esse é um livro Leanpub. A Leanpub dá poderes aos autores e editores a partir do processo de Publicação Lean. Publicação Lean é a ação de publicar um ebook em desenvolvimento com ferramentas leves e muitas iterações para conseguir feedbacks dos leitores, pivotar até que você tenha o livro ideal e então conseguir tração. © 2015 - 2018 Rafael Guimarães Sakurai Conteúdo 1. Olá Android! . . . . . . . . . . . . . . . . . . . . . . 1.1 Criando um novo projeto no Android Studio 1.2 Executando um aplicativo Android . . . . . . 1.3 Personalizando o aplicativo OlaAndroid . . . 1.4 Resumo . . . . . . . . . . . . . . . . . . . . . 1.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 2 10 19 28 28 2. Utilizando LinearLayout, TextView e Button 2.1 Criando o layout da tela da Calculadora 2.2 Adicionando ação aos botões . . . . . . 2.3 Resumo . . . . . . . . . . . . . . . . . . 2.4 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 30 36 40 41 3. Utilizando ImageView e AlertDialog . . . . . 3.1 Criando layout da tela do Jogo da Velha 3.2 Implementando as regras do jogo . . . . 3.3 Resumo . . . . . . . . . . . . . . . . . . 3.4 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 42 47 55 55 4. Requisitando outra tela com Intent . . . 4.1 Criando uma tela de autenticação 4.2 Criando a tela da lista de tarefas . 4.3 Usando o Intent . . . . . . . . . . 4.4 Resumo . . . . . . . . . . . . . . . 4.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 56 60 64 69 69 5. Trocando dados entre telas via Bundle . . . . . . 5.1 Bundle . . . . . . . . . . . . . . . . . . . . 5.2 Criando o projeto de Valet . . . . . . . . . 5.3 Adicionando a tela de cadastro de veículos 5.4 Chamando a tela de cadastro de veículo . . 5.5 Obtendo o retorno da chamada da Activity 5.6 Removendo os elementos da lista . . . . . . 5.7 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 70 71 78 81 83 86 88 . . . . . . . . . . . . . . . . . . CONTEÚDO 5.8 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6. Persistindo dados com SQLite . . . . . . . . . 6.1 Montando o banco de dados no SQLite . 6.2 Adicionando a persistência do veículos 6.3 Resumo . . . . . . . . . . . . . . . . . . 6.4 Exercícios . . . . . . . . . . . . . . . . . . . . . 89 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 . 92 . 96 . 100 . 100 7. ListView personalizada . . . . . . . . . . . . . . . 7.1 Criando o layout da activity_main . . . . . 7.2 Criando o layout da ListView . . . . . . . . 7.3 Adicionando o comportamento da ListView 7.4 Resumo . . . . . . . . . . . . . . . . . . . . 7.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 103 105 108 112 113 8. Chamando Web Service REST . . . . . . . . . . . . . 8.1 Introdução sobre Web Service REST . . . . . . 8.2 Alterando o aplicativo Meus Produtos . . . . . 8.3 Criando a tela de cadastro de produto . . . . . 8.4 Adicionando arquivos no emulador do Android 8.5 Integração com Web Service REST . . . . . . . 8.6 Resumo . . . . . . . . . . . . . . . . . . . . . . 8.7 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 114 115 119 123 125 143 143 9. Action Bar . . . . . . . . . . . . . . . . . . . . . . . . 9.1 Configurando o Action Bar . . . . . . . . . . . 9.2 Criando o layout das telas . . . . . . . . . . . . 9.3 Implementando o comportamento do aplicativo 9.4 Alterando o ícone do aplicativo . . . . . . . . . 9.5 Resumo . . . . . . . . . . . . . . . . . . . . . . 9.6 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 145 147 154 169 169 169 Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento Java Developer Kit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Android Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Instalando o Android Studio no Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 170 178 181 1. Olá Android! Vamos criar uma aplicativo inicial, no estilo Olá Mundo! para Android. O objetivo é apresentar como criar um projeto Android e utilizar uma Activity para controlar a tela e os componentes TextView, ImageView e Toast. O aplicativo terá um texto Olá Android!!! e a figura do logo do Android, também será adicionado uma ação na figura que ao clicar nela aparece uma mensagem Olá!. No final, o aplicativo ficará conforme apresentado na Figura 1.1. Figura 1.1 - Aplicativo Olá Android! Para desenvolvimento dos aplicativos Android utilizaremos a IDE Android Studio¹. ¹http://developer.android.com/sdk/index.html 2 Olá Android! 1.1 Criando um novo projeto no Android Studio Para criar um projeto, selecione a opção Start a new Android Studio project (Iniciar um novo projeto Android Studio) na tela de bem vindo ao Android Studio, como apresentado na Figura 1.1.1. Figura 1.1.1 - Android Studio. Na tela Create Android Project, apresentada na Figura 1.1.2, configuramos as informações básicas do projeto, como: • Application name (Nome do aplicativo): OlaAndroid; • Company Domain (Domínio da empresa): seudominio.com.br, exemplo: googleandroid.leanpub.com ou se preferir livro.capitulo1; • Project location (Localização do projeto): Caminho do projeto no seu computador; 3 Olá Android! Figura 1.1.2 - Criando um novo projeto Android. Continuando, a tela Target Android Devices (Dispositivos alvo do Android), apresentada na Figura 1.1.3, permite escolher qual será o foco principal do aplicativo, que pode ser Celular e Tablet; Wear (como o relógio); TV; Android Auto (uso em automóveis); e para Android Things. 4 Olá Android! Figura 1.1.3 - Escolhendo o dispositivo e versão mínima do Android. Nesse exemplo, escolha a opção Phone and Tablet, também defina qual será a API mínima para poder executar o aplicativo, como a API 18: Android 4.3 (Jelly Bean). Clicando em Next, vamos continuar a criação do projeto. Na tela Add an Activity to Mobile (Adicionar uma Activity ao aplicativo móvel), apresentada na Figura 1.1.4, podemos informar se queremos criar uma Activity inicial para o projeto, de modo geral uma Activity é usada para realizar a ações de uma ou mais telas do aplicativo. Neste capítulo abordaremos apenas o básico para criar e iniciar um projeto, nos demais capítulos apresentamos outros modelos de Activities. 5 Olá Android! Figura 1.1.4 - Adicionando uma Activity ao projeto. Marque a opção Empty Activity (Activity em branco) e clique em Next para prosseguir. Essa opção cria uma Activity e tela com um texto de exemplo, algo bem simples que a partir dele é possível continuar a implementação de diversos aplicativos. Na tela Configure Activity (Configurar Activity), apresentada na Figura 1.1.5, temos a opção de customizar os dados da Activity inicial que será criada no projeto. As informações que temos são: • Activity Name (Nome da Activity): MainActivity • Layout Name (Nome do layout): activity_main A Activity representa o nome da classe que será criada para controlar as ações e atributos do layout da tela; e o Layout é um arquivo XML que possui a estrutura e componentes da tela. 6 Olá Android! Figura 1.1.5 - Configurando a Activity inicial do projeto. Na primeira vez que um projeto Android é criado, pode ser necessário fazer alguns downloads, como o apresentado na Figura 1.1.6. 7 Olá Android! Figura 1.1.6 - Atualizações para o projeto Android. Se após criar o projeto, aparecer uma mensagem de erro, como o da Figura 1.1.7, normalmente é porque tem mais alguma atualização necessária para compilar o projeto. 8 Olá Android! Figura 1.1.7 - Atualizações necessárias para compilar o projeto Android. Então clique no link que automaticamente inicia as atualizações necessárias, como mostrado na Figura 1.1.8. 9 Olá Android! Figura 1.1.8 - Atualizações necessárias para compilar o projeto Android. Após a criação do projeto e demais atualizações, o Android Studio abre uma visualização completa da estrutura do projeto, como mostrado na Figura 1.1.9. 10 Olá Android! Figura 1.1.9 - Projeto aberto no Android Studio. 1.2 Executando um aplicativo Android Para executar um aplicativo Android podemos utilizar um hardware físico como celular, tablet, etc, ou um emulador que simula o comportamento do aplicativo no dispositivo. Na barra de tarefa do Android Studio (Figura 1.2.1), temos a opção de executar o aplicativo clicando botão verde de Play ou usando o atalho CRTL + R. Figura 1.2.1 - Executar projeto Android. A tela Select Deployment Target (Selecione o alvo da publicação), apresentada na Figura 1.2.2, será aberta para escolher se deve executar o aplicativo no dispositivo (conectado via USB) ou no emulador. Se for utilizar seu smartphone ou tablet para testar os exemplos, ative no dispositivo a Depuração de USB, está opção está disponível em Configuração → Opções do 11 Olá Android! desenvolvedor. Isso é algo que inicialmente vem escondido, mas na Internet você encontra facilmente como ativar as Opções do desenvolvedor de acordo com o seu dispositivo. Na figura a seguir, meu smartphone está conectado no computador e não tenho nenhum emuladores criado. Se for utilizar seu dispositivo, selecione ele e click em OK para publicar este aplicativo diretamente no seu dispositivo. Para utilizar os emuladores do Android, clique no botão Create New Virtual Device (Criar novo dispositivo virtual). Figura 1.2.2 - Escolhendo um dispositivo virtual - emulador. Ma tela Virtual Device Configuration (Configuração virtual de dispositivos), apresentada na Figura 1.2.3, há as categorias do emulador como TV, Phone, Wear e Tablet, e para cada categoria temos a opção de usar as configurações de um hardware existente, como: Nexus 5, Nexus One, etc, ou um formato de acordo com o tamanho da tela 5.4”, 4”, etc. A escolha da configuração do emulador deixa definir o tipo de hardware (tamanho, resolução e densidade) que será o foco do aplicativo (mais adiante no livro será apresentado o que significa cada uma dessas características e como criar um aplicativo que funcione de modo agradável em várias resoluções). Por agora você pode criar um emulador com base no hardware do Nexus 5X ou criar um emulador que simule o comportamento do seu smartphone. 12 Olá Android! Figura 1.2.3 - Escolhendo o hardware do novo dispositivo virtual. No segundo passo precisamos informar qual a System Image (Imagem do Sistema), apresentado na Figura 1.2.4, será utilizado no emulador, nesse caso a versão do Android. 13 Olá Android! Figura 1.2.4 - Escolhendo a imagem (versão do Android) do dispositivo virtual. Neste exemplo, não há nenhuma imagem do Android, por tanto aparece a opção para fazer o download. Nessa tela há três categorias de imagens: Recommended (Recomendado) são as versões mais recentes do Android, x86 Images (Imagens x86) são outras versões do Android para arquitetura de hardware x86_64 e Other images (Outras imagens) são versões do Android para outros hardwares (normalmente antigos) como: armeabi, arm64 e mips, essas versões são mais lentas e podem ser utilizadas caso o seu computador não suporte as versões mais recentes com x86 ou x86_64. A instalação apresentada aqui será do Oreo, API Level: 26, ABI: x86 e Target: Android 8.0 (Google Play), mas se preferir a instalação de outra versão é muito similar ao passo a passo apresentado a seguir. Sugiro não fazer download de todas as versões, cada uma tem em média 1GB de tamanho. Quando for fazer o download de uma nova imagem do Android é apresentado a tela License Agreement (Contrato de Licença) (Figura 1.2.5), leia o contrato e se aceitar clique em Accept (Aceitar) e Next (Próximo) para continuar. 14 Olá Android! Figura 1.2.5 - Aceitando a licença de uso do Android. A tela Component Installer (Instalação dos componentes) (Figura 1.2.6) apresenta os componentes instalados desta nova imagem do Android. Clique em Finish (Finalizar) para terminar a instalação desta versão. 15 Olá Android! Figura 1.2.6 - Finalizando a instalação da imagem para o emulador. Voltando para a tela System Image (Imagem do Sistema) (Figura 1.2.7), selecione a imagem que será utilizada nesse emulador e clica em Next (Próximo) para continuar. 16 Olá Android! Figura 1.2.7 - Escolhendo a imagem do emulador. E, por fim, revisamos as configurações do emulador, como apresentado na Figura 1.2.8: • • • • O nome do AVD; Dispositivo que será simulado; Versão do Android; Escala e orientação. 17 Olá Android! Figura 1.2.8 - Revisando as configurações do dispositivo virtual. Clique em Finish (Finalizar) para terminar a criação do emulador e pode fechar a tela do Android Virtual Device Manager. Voltando para a tela Select Deployment Target (Selecione o alvo da publicação) (Figura 1.2.9), escolha o emulador que será iniciado e clique em OK. 18 Olá Android! Figura 1.2.9 - Iniciando um emulador. O emulador será iniciado, desbloqueie a tela e o aplicativo OlaAndroid será publicado e automaticamente iniciado, como apresentado na Figura 1.2.10. 19 Olá Android! Figura 1.2.10 - Aplicativo iniciado no emulador. Dica: O emulador demora um pouco para iniciar, então enquanto estiver desenvolvendo mantenha o emulador ligado. 1.3 Personalizando o aplicativo OlaAndroid Voltando ao Android Studio, vamos alterar o aplicativo para ficar um pouco mais interessante. Na estrutura do projeto temos alguns pacotes que são de uso frequente como: • • • • • • app → manifests - O arquivo AndroidManifest.xml contém a configuração do projeto; app → java - Estrutura de pacotes com códigos fonte e testes do aplicativo; app → res - Pacote de recursos; app → res → drawable – Pacote com as imagens de acordo com a resolução; app → res → layout – Arquivos no formato .xml com a estrutura do layout das telas; app → res → values – Pasta dimens.xml com configurações de dimensões e tamanho de fontes que podem ser reutilizados em todo projeto; o arquivo strings.xml com os textos strings; e o arquivo styles.xml com o estilo de telas do aplicativo. 20 Olá Android! A Figura 1.3.1 mostra a estrutura atual do projeto. Figura 1.3.1 - Estrutura do projeto. Para editar a tela do aplicativo, abra o arquivo app → res → layout → activity_main.xml. A edição de uma tela pode ser feita de modo Design (Figura 1.3.2) adicionando os componentes por meio da paleta e configurando pelas propriedades. 21 Olá Android! Figura 1.3.2 - Edição da tela no modo Design. Também podemos alterar o código da tela editando o arquivo activity_main.xml no modo Text (Figura 1.3.3). 22 Olá Android! Figura 1.3.3 - Edição da tela no modo Text. Note que em ambos modos, também temos mais opções para configurar o layout que é renderizado, podemos escolher o tipo de aparelho, se o aparelho está na vertical ou horizontal, o tema do aplicativo, a classe associada com essa tela, a localização para trabalhar com internacionalização de textos e a versão do Android utilizada quando for renderizar a tela do layout, como apresentado na Figura 1.3.4. Figura 1.3.4 - Opções de preview do aplicativo. No activity_main.xml altere apenas o código do TextView para: Olá Android! 1 2 3 4 5 6 7 8 9 23 <TextView android:id="@+id/texto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dp" android:gravity="center" android:text="@string/ola_android" android:textSize="40sp" app:layout_constraintTop_toTopOf="parent" /> Vai aparecer um erro na linha do android:text, mas não se preocupe que já corrigiremos. O TextView representa um texto na tela, como um label (rótulo), no qual pode ser definido o seu texto, como será o seu layout, tamanho do texto, cor do texto, entre outros. Esse TextView está utilizando uma string (texto) chamada ola_android, para criar o conteúdo desse texto, na estrutura do projeto abra o arquivo ** app → res → values → strings.xml** e adicione mais uma linha para a string ola_android e conteúdo Olá Android!!!. O arquivo strings.xml ficará com o seguinte conteúdo: 1 2 3 4 <resources> <string name="app_name">OlaAndroid</string> <string name="ola_android">Ola Android!!!</string> </resources> Agora vamos adicionar uma imagem nessa tela, para isso você pode utilizar uma imagem que exista no seu computador ou usar alguma imagem disponível na Internet, nesse exemplo estou usando um logo do robô Android disponível em: http://developer.android.com/images/brand/Android_Robot_100.png², esse logo pode ser usado, reproduzido e modificado gratuitamente. Salve a figura Android_Robot_100.png ou copie e cole dentro da pasta app → res → drawable. Outro detalhe importante é que o nome das imagens pode ter apenas os caracteres [a-z0-9_], portanto após adicionar a imagem Android_Robot_100.png aparece a seguinte mensagem de erro no Console: res/drawable-hdpi/Android_Robot_100.png: Invalid file name: must contain only [a-z0-9_.], porque no nome da figura tem caracteres maiúsculos. Para resolver isso clique em cima da figura Android_Robot_100.png e escolha no menu Refactor (Refatorar) a opção Rename (Renomear) ou clique na figura e pressione SHIFT + F6. Altere o nome do arquivo para android.png. No arquivo activity_main.xml após a declaração do TextView, adicione a declaração do ImageView que fará uso da imagem do android.png: ²http://developer.android.com/images/brand/Android_Robot_100.png Olá Android! 1 2 3 4 5 6 7 8 24 <ImageView android:id="@+id/imagem" android:src="@drawable/android" android:layout_width="match_parent" android:layout_height="250dp" android:contentDescription="@string/ola_android" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent" /> Após adicionar a figura na pasta drawable, o Android Studio gera automaticamente um registro para @drawable/android. A ideia é centralizar a figura para isso utilizamos as propriedades app:layout_constraintBottom_toBottomOf="parent" e app:layout_constraintTop_toTopOf="parent" que alinha a imagem em relação ao componente pai que nesse caso é o layout que controla toda a tela. O Android também sugere que descrevamos a imagem que está sendo apresentado, isso pode ser feito com a propriedade android:contentDescription= "@string/ola_android" que está utilizando a string ola_android, esse texto será utilizado apenas para acessibilidade ele não é apresentado na tela. A estrutura de código e aparência visual deve ser similar a Figura 1.3.5. 25 Olá Android! Figura 1.3.5 - Preview do projeto OlaAndroid. Vamos adicionar uma mensagem Olá! quando a imagem do Android for clicada, para isso altere no arquivo activity_main.xml a código XML da declaração do ImageView adicionando as últimas duas linhas: 1 2 3 4 5 6 7 8 9 10 <ImageView android:id="@+id/imagem" android:src="@drawable/android" android:layout_width="match_parent" android:layout_height="250dp" android:contentDescription="@string/ola_android" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent" android:clickable="true" android:onClick="mostrarMensagem"/> Foi adicionado a propriedade android:clickable="true" informando que essa imagem pode ser clicada e também a propriedade android:onClick="mostrarMensagem" informando que ao clicar na imagem será chamado o método mostrarMensagem que será declarado a seguir. Olá Android! 26 Abra a classe MainActivity, essa é a Activity responsável pela tela inicial do aplicativo, nela importe as classes: 1 2 import android.view.View; import android.widget.Toast; E adicione o método mostrarMensagem: 1 2 3 4 public void mostrarMensagem(View view) { Toast toast = Toast.makeText(this, "Olá!", Toast.LENGTH_SHORT); toast.show(); } O Toast é uma mensagem que aparece e desaparece da tela e o Toast.LENGTH_SHORT especifica que é uma mensagem que fica por um tempo curto na tela. O código completo da classe MainActivity ficará assim: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.leanput.googleandroid.olaandroid; import import import import android.support.v7.app.AppCompatActivity; android.os.Bundle; android.view.View; android.widget.Toast; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void mostrarMensagem(View view) { Toast toast = Toast.makeText(this, "Olá!", Toast.LENGTH_SHORT); toast.show(); } } E o código completo do activity_main.xml ficará assim: Olá Android! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 27 <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.leanpub.googleandroid.olaandroid.MainActivity"> <TextView android:id="@+id/texto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dp" android:gravity="center" android:text="@string/ola_android" android:textSize="40sp" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:id="@+id/imagem" android:src="@drawable/android" android:layout_width="match_parent" android:layout_height="250dp" android:contentDescription="@string/ola_android" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent" android:clickable="true" android:onClick="mostrarMensagem"/> </android.support.constraint.ConstraintLayout> Execute novamente o aplicativo no emulador do Android e clique sobre a figura para visualizar a mensagem como mostrado na Figura 1.3.6: 28 Olá Android! Figura 1.3.6 - Executando o projeto OlaAndroid no emulador. 1.4 Resumo Neste capítulo foi apresentado como criar um novo projeto Android usando o Android Studio, como criar e instalar um aplicativo no emulador. Vimos alguns componentes básicos como a Activity, e na tela os componentes TextView e ImageView, para terminar mostramos como um método da Activity pode ser chamado ao executar uma ação de click na figura. Esse conceito de chamada de método é o mesmo para botões e outras ações como veremos mais adiante. 1.5 Exercícios Exercício 1 - Crie um novo emulador para tablet, usando como base o hardware do Nexus 10 ou superior, e execute o aplicativo OlaAndroid nele. Exercício 2 - Altere o exemplo OlaAndroid para colocar em TexView com o seu nome logo embaixo da figura do logo do Android. Exercício 3 - Para alterar o título do aplicativo para Google Android devemos alterar em qual arquivo? Olá Android! 29 Exercício 4 - O que é o componente Toast? E o que significa definir ele com a propriedade Toast.LENGTH_LONG? Exercício 5 - Para definir tamanho dos componentes usamos algumas propriedades do Android como match_parent e wrap_content, para que serve cada um deles? 2. Utilizando LinearLayout, TextView e Button Vamos criar um aplicativo de Calculadora para o Android. Neste exemplo será abordado a criação de um aplicativo Android utilizando uma Activity e os componentes LinearLayout, TextView e Button. O aplicativo ficará conforme a Figura 2.1. Figura 2.1 - App Calculadora. 2.1 Criando o layout da tela da Calculadora Crie um aplicativo Android igual fizemos no capítulo anterior, ou por meio do menu File → New → New Project, chamada Calculadora. Vamos começar alterando o TextView que vem no layout da tela activity_main.xml, ele será utilizado para apresentar os números da calculadora no topo da tela: Utilizando LinearLayout, TextView e Button 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 31 <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.leanpub.googleandroid.calculadora.MainActivity"> <TextView android:id="@+id/resultado" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="8dp" android:textDirection="rtl" android:textSize="40sp" android:textAppearance="?android:attr/textAppearanceLarge" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout> Nesse TextView estamos informando que ele deve manter uma margem entre o topo (android:layout_marginTop="8dp"), o componente que estiver abaixo (android:layout_marginBottom="8dp") e com as laterais (android:layout_marginStart="8dp" e android:layout_marginEnd="8dp"); definirmos a direção do texto da direita para a esquerda com a propriedade android:textDirection="rtl"; o tamanho do texto com a propriedade android:textSize="40sp"; a aparência grande do texto com a propriedade android:textAppearance="?android:attr/textAppearanceLarge"; e que este componente deve ficar alinhado no topo da tela com a propriedade app:layout_constraintTop_toTopOf="parent". Dentro de um layout podemos adicionar outros layouts para definir como será organizado cada componente da tela. Vamos criar logo após a declaração do <TextView ... /> e antes de fechar o </android.support.constraint .ConstraintLayout>, um LinearLayout que permite organizar os componentes na horizontal ou vertical, servindo para colocarmos os botões e a operações da calculadora. O LinearLayout a seguir define os botões dos números 9, 8 e 7, e também a operação de divisão: Utilizando LinearLayout, TextView e Button 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <LinearLayout android:id="@+id/linha1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:gravity="center_horizontal" app:layout_constraintTop_toBottomOf="@+id/resultado"> <Button android:id="@+id/button7" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="1" android:text="7" android:textSize="40sp" /> <Button android:id="@+id/button8" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="1" android:text="8" android:textSize="40sp" /> <Button android:id="@+id/button9" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="1" android:text="9" android:textSize="40sp" /> <Button android:id="@+id/dividir" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="1" android:text="/" android:textSize="40sp" /> </LinearLayout> 32 Utilizando LinearLayout, TextView e Button 33 Este LinearLayout foi alinhado embaixo do TextView chamado resultado por meio da propriedade app:layout_constraintTop_toBottomOf="@+id/resultado". O ID deste layout foi definido como linha1 para servir como referência para o próximo layout. Cada botão tem um ID diferente e suas configurações. O exemplo a seguir mostra o botão dividir, o comprimento está sendo definido como android:layout_width="0dp", isso quer dizer que o valor do comprimento do botão será calculado de acordo com o peso de todos os botões dentro deste LinearLayout. 1 2 3 4 5 6 7 <Button android:id="@+id/dividir" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="1" android:text="/" android:textSize="40sp" /> A configuração do peso de cada componente é definido com a propriedade android:layout_weight="1". Como dentro deste LinearLayout há 4 botões e cada um deles está definido com o peso "1" , isso indica que cada botão deve ter o comprimento igual a 1/4 do componente pai. Embaixo do LinearLayout chamado linha1, vamos adicionar a segunda linha com os botões dos números 4, 5 e 6, e também a operação multiplicação: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <LinearLayout android:id="@+id/linha2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:gravity="center_horizontal" app:layout_constraintTop_toBottomOf="@+id/linha1"> <Button android:id="@+id/button4" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="1" android:text="4" android:textSize="40sp" /> <Button Utilizando LinearLayout, TextView e Button 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 android:id="@+id/button5" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="1" android:text="5" android:textSize="40sp" /> <Button android:id="@+id/button6" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="1" android:text="6" android:textSize="40sp" /> <Button android:id="@+id/multiplicar" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="1" android:text="*" android:textSize="40sp" /> </LinearLayout> Agora adicione os demais layouts e botões para deixarmos a tela como da Figura 2.1.1. 34 Utilizando LinearLayout, TextView e Button 35 Figura 2.1.1 - Layout do aplicativo da calculadora. Para aumentar o tamanho de um botão podemos utilizar a propriedade layout_height para aumentar a altura e a propriedade layout_width para aumentar o comprimento, se quiser utilizar o tamanho total do componente pai, pode ser dado o valor match_parent. Para deixar o botão 0 (zero) com o tamanho de 2 botões, deixei a propriedade android:layout_weight) do botão com valor 2, assim ele terá a largura ocupando 2/4 do espaço: 1 2 3 4 5 6 7 <Button android:id="@+id/button0" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="2" android:text="0" android:textSize="40sp" /> Mais adiante veremos outras formas de redimencionar melhor os componentes na tela. Utilizando LinearLayout, TextView e Button 36 E para deixar o botão de = (igual ou resultado) com o comprimento de quatro botões, deixei o tamanho com valor de match_parent, assim ele obtém o tamanho total do componente pai. 1 2 3 4 5 6 <Button android:id="@+id/igual" android:layout_width="match_parent" android:layout_height="80dp" android:text="=" android:textSize="40sp" /> 2.2 Adicionando ação aos botões Para adicionar uma ação ao clicar no botão, utilizamos a propriedade android:onClick que recebe como valor o nome do método que será chamado na Activity, podemos alterar o botão 7 para chamar o método adicionarNumero: 1 2 3 4 5 6 7 8 <Button android:id="@+id/button7" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="1" android:text="7" android:textSize="40sp" android:onClick="adicionarNumero"/> Quando clicar no botão será chamado o método adicionarNumero que deve ser declarado na classe MainActivity e irá adicionar o valor deste botão no TextView que representa o resultado. Adicione esse mesmo comportamento nos demais botões de números. Na classe MainActivity adicionar esses três imports: 1 2 3 import android.view.View; import android.widget.Button; import android.widget.TextView; E o método adicionarNumero: Utilizando LinearLayout, TextView e Button 1 2 3 4 5 37 public void adicionarNumero(View view) { String numero = ((Button) view).getText().toString(); TextView resultado = ((TextView) findViewById(R.id.resultado)); resultado.setText(resultado.getText() + numero); } O método adicionarNumero recebe como parâmetro um objeto do tipo View, esse objeto serve para representar os componentes da tela. Como sabemos que o componente da tela que chama esse método é um botão, podemos fazer a conversão desse objeto view para um objeto do tipo Button. Para obter o texto de um botão podemos chamar o método getText(), exemplo: 1 String numero = ((Button) view).getText().toString(); Após o botão ser pressionado o número deve aparecer escrito no TextView resultado. Para obter um componente da tela, utilizamos o seu identificador, note que na tela declaramos o campo de texto com a propriedade android:id="@+id/resultado" para o Android criar uma constante que aponta para esse componente, isso é feito automaticamente gerando o arquivo R.java. Observação: O arquivo R.java é gerado automaticamente e não devemos altera-lo. Caso tenha algum erro nas páginas de layout, os arquivos XML, figuras, etc, essa classe R.java não será gerada ou atualizada com os componentes da tela. É muito importante que o layout da tela tenha sido criado corretamente antes de usar as constantes declaradas nessa classe. Para obter um componente da tela podemos usar o método findViewById passando como parâmetro o id do componente que queremos obter da tela, por exemplo: 1 TextView res = ((TextView) findViewById(R.id.resultado)); Usamos o método getText() da classe Button para pegar o texto do botão, podemos usar o método getText() do TextView para obter o seu conteúdo e também temos o método setText() para alterar o seu conteúdo, então para mostrar o texto do Button no TextView podemos fazer assim: 1 resultado.setText(resultado.getText() + numero); Neste ponto, se executar o aplicativo, já deverá ser possível clicar em todos os botões de número da calculadora, como apresentado na Figura 2.2.1. Utilizando LinearLayout, TextView e Button 38 Figura 2.2.1 - Adicionando os botões de número da calculadora. Vamos implementar o comportamento dos botões das operações somar, subtrair, multiplicar e dividir que deve executar o calculo da operação e o botão igual (resultado) que apresenta o resultado na tela. Para adicionar ação nos botões das operações de soma, subtração, multiplicação e divisão, precisaremos de um atributo na classe MainActivity para guardar o valor que temos até o momento no TextView resultado e também um método que será executado pela operação de soma. Esses dois atributos podem ser declarados após a declaração da classe MainActivity: 1 2 private double total; private String operacao; E também adicione o método operacao, que guarda qual foi o valor do botão +, -, * ou / acionado, guarda o valor que foi armazenado no TextView resultado e depois limpa o seu valor: 1 2 3 4 5 6 public void operacao(View view) { operacao = ((TextView) view).getText().toString(); total = Double.valueOf(((TextView) findViewById(R.id.resultado)) .getText().toString().trim()); ((TextView) findViewById(R.id.resultado)).setText(""); } Altere os botões das operações na tela para que chamem o método operacao, exemplo: 1 2 3 4 5 6 7 8 <Button android:id="@+id/dividir" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="1" android:text="/" android:textSize="40sp" android:onClick="operacao"/> O método do botão igual que calcula o resultado: Utilizando LinearLayout, TextView e Button 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void calcular(View view) { double valor = Double.valueOf(((TextView) findViewById(R.id.resultado)) .getText().toString().trim()); if("+".equals(operacao)) { total = total + valor; } else if("-".equals(operacao)) { total = total - valor; } else if("/".equals(operacao)) { total = total / valor; } else if("*".equals(operacao)) { total = total * valor; } ((TextView) findViewById(R.id.resultado)).setText(String.valueOf(total)); } Um detalhe importante desse código é que não estamos tratando divisão por zero. E alterar o botão igual para chamar o método calcular: 1 2 3 4 5 6 7 <Button android:id="@+id/igual" android:layout_width="match_parent" android:layout_height="80dp" android:text="=" android:textSize="40sp" android:onClick="calcular"/> O método do botão CE que limpa os valores da calculadora: 1 2 3 4 5 public void limpar(View view) { total = 0; operacao = null; ((TextView) findViewById(R.id.resultado)).setText(""); } E agora só falta alterar o botão CE para chamar o método limpar: 39 Utilizando LinearLayout, TextView e Button 1 2 3 4 5 6 7 8 40 <Button android:id="@+id/limpar" android:layout_width="0dp" android:layout_height="80dp" android:layout_weight="1" android:text="CE" android:textSize="40sp" android:onClick="limpar"/> Execute o aplicativo no emulador e o resultado deve ser similar ao da Figura 2.2.2. Figura 2.2.2 - Usando o aplicativo da calculadora. 2.3 Resumo Neste capítulo foi abordado como criar atributos e métodos na Activity, e usar os componentes LinearLayout, TextView e Button. Lembre-se que os métodos declarado na Activity recebem uma View como parâmetro representando o componente da tela que está chamando o método. Utilizando LinearLayout, TextView e Button 41 2.4 Exercícios Exercício 1 - Altere o exemplo da calculadora e adicione um botão de ponto final (.) para representar as casas decimais de um número. Exercício 2 - Qual a diferença entre o LinearLayout e o ConstraintLayout? Exercício 3 - Como podemos tratar o erro de divisão por zero na calculadora? Altere o aplicativo e caso ocorro uma divisão por zero escreva a mensagem “ERRO” no TextView do resultado. 3. Utilizando ImageView e AlertDialog Vamos criar um Jogo da Velha para Android, neste exemplo será abordado o uso do componente ImageView e AlertDialog e também da criação de métodos na Activity. O aplicativo ficará conforme a Figura 3.1: Figura 3.1 - Jogo da Velha. 3.1 Criando layout da tela do Jogo da Velha Crie um novo projeto Android chamado JogoDaVelha. Observação: As imagens utilizadas neste capítulo estão disponíveis no GitHub¹. Adicione as figuras fundo.png, o.png e x.png na pasta res → drawable, como apresentado na Figura 3.1.1. Essas imagens representam o fundo do aplicativo, a imagem do jogador O e a imagem do jogador X, respectivamente. ¹https://github.com/rafaelsakurai/livro-google-android Utilizando ImageView e AlertDialog 43 Figura 3.1.1 - Estrutura de pastas do projeto. Vamos alterar a tela activity_main.xml para adicionar a imagem fundo.png como fundo da tela e também alterar o layout da tela para usar o LinearLayout: 1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.leanpub.googleandroid.jogodavelha.MainActivity" android:background="@drawable/fundo"> </android.support.constraint.ConstraintLayout> Note que utilizamos na propriedade android:background a imagem de fundo desse layout, veja como ficou na Figura 3.1.2. 44 Utilizando ImageView e AlertDialog Figura 3.1.2 - Layout de tela com fundo. Na aba de Preview, apresentada na Figura 3.1.3, podemos configurar uma visualização semelhante a esperada no emulador ou no nosso dispositivo. Como estamos usando um emulador com base na resolução do Nexus 5X de 1080x1920, altere o layout da versão visual para utilizar também um tela com esse mesmo tamanho. Figura 3.1.3 - Configuração do layout da tela. Dentro desse layout de fundo, será adicionando mais alguns LinearLayouts para representar cada linha do tabuleiro. Para representar cada linha do tabuleiro do jogo da velha, será utilizado um LinearLayout, e para representar cada área que o usuário pode clicar, será utilizar o ImageView. Utilizando ImageView e AlertDialog 45 Vamos criar a primeira linha, nela teremos um LinearLayout para organizar os componentes e três ImageViews que serão as imagens dos jogadores O e X: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <LinearLayout android:id="@+id/LinearLayout01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="170dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <ImageView android:id="@+id/ImageView01" android:layout_width="80dp" android:layout_height="80dp" android:clickable="true" android:onClick="selecionar" /> <ImageView android:id="@+id/ImageView02" android:layout_width="80dp" android:layout_height="80dp" android:layout_marginStart="50dp" android:clickable="true" android:onClick="selecionar" /> <ImageView android:id="@+id/ImageView03" android:layout_width="80dp" android:layout_height="80dp" android:layout_marginStart="50dp" android:clickable="true" android:onClick="selecionar" /> </LinearLayout> Observação: Não se preocupe com o erro na linha android:onClick="selecionar", daqui a pouco criaremos o método selecionar e isso será resolvido. Vamos fazer o mesmo com a segunda linha, mas note que agora estamos usando um espaçamento menor em relação ao topo android:layout_marginTop="24dp", pois esse segundo LinearLayout virá logo abaixo do primeiro LinearLayout. Utilizando ImageView e AlertDialog 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <LinearLayout android:id="@+id/LinearLayout02" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="24dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/LinearLayout01"> <ImageView android:id="@+id/ImageView04" android:layout_width="80dp" android:layout_height="80dp" android:clickable="true" android:onClick="selecionar" /> <ImageView android:id="@+id/ImageView05" android:layout_width="80dp" android:layout_height="80dp" android:layout_marginStart="50dp" android:clickable="true" android:onClick="selecionar" /> <ImageView android:id="@+id/ImageView06" android:layout_width="80dp" android:layout_height="80dp" android:layout_marginStart="50dp" android:clickable="true" android:onClick="selecionar" /> </LinearLayout> E para finalizar a tela, vamos criar a última linha: 46 Utilizando ImageView e AlertDialog 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 47 <LinearLayout android:id="@+id/LinearLayout03" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="24dp" android:layout_marginBottom="150dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/LinearLayout02"> <ImageView android:id="@+id/ImageView07" android:layout_width="80dp" android:layout_height="80dp" android:clickable="true" android:onClick="selecionar" /> <ImageView android:id="@+id/ImageView08" android:layout_width="80dp" android:layout_height="80dp" android:layout_marginStart="50dp" android:clickable="true" android:onClick="selecionar" /> <ImageView android:id="@+id/ImageView09" android:layout_width="80dp" android:layout_height="80dp" android:layout_marginStart="50dp" android:clickable="true" android:onClick="selecionar" /> </LinearLayout> 3.2 Implementando as regras do jogo Agora temos o layout da tela pronto, com fundo e com as imagens que serão adicionadas conforme o andamento do jogo. A classe MainActivity.java irá controlar as ações da tela do tabuleiro do jogo da velha. Por padrão o Android Studio criou essa classe Activity com o seguinte código: Utilizando ImageView e AlertDialog 1 2 3 4 5 6 7 8 9 10 11 12 13 14 48 package com.leanpub.googleandroid.jogodavelha; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } O método onCreate() é usado para criar e definir os componentes apresentados na tela. Nele é chamado o método setContentView(R.layout.activity_main) que associa essa Activity com a tela activity_main.xml. No Jogo da Velha, temos algumas regras, como: • um jogador só pode escolher uma posição que ainda está vazia; • se o jogador montar uma sequência com três símbolos (X ou O) iguais na mesma linha, ele vence; • se o jogador montar uma sequência com três símbolos (X ou O) iguais na mesma coluna, ele vence; • se o jogador montar uma sequência com três símbolos (X ou O) iguais em uma das diagonais, ele vence; • se preencher todas as nove posições do tabuleiro e nenhum jogador vencer, então é considerado um empate (deu velha). Adicione o atributo jogadorX na classe MainActivity antes do método onCreate(), esse atributo será utilizado para controlar a vez de cada jogador. 1 private boolean jogadorX = true; Adicione no começo da MainActivity os imports que serão utilizados: Utilizando ImageView e AlertDialog 1 2 3 4 import import import import 49 android.app.AlertDialog; android.content.DialogInterface; android.view.View; android.widget.ImageView; Vamos criar um método selecionar, que de acordo com a vez do jogador apresentará a imagem correspondente e verificará se o jogo terminou: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void selecionar(View view) { ImageView botao = (ImageView) view; if(botao.getContentDescription() == null || "".equals(botao.getContentDescription())) { if(jogadorX) { botao.setImageResource(R.drawable.x); botao.setContentDescription("X"); } else { botao.setImageResource(R.drawable.o); botao.setContentDescription("O"); } jogadorX = !jogadorX; verificarSeGanhouOuEmpatou(); } } Note que esse método selecione recebe como parâmetro um ImageView, pois ele será chamado cada vez um houver um clique em algum dos componentes ImageViews da tela. De acordo com a vez do jogador é definido a imagem e a descrição do conteúdo que será apresentada no ImageView. Note que da mesma forma que os componentes da tela possuem IDs, as imagens também possuem suas constantes criadas na classe R.java como R.drawable.x e R.drawable.o. A descrição do conteúdo será usado logo a seguir para identificar os ImageViews que foram selecionados. Para verificar se o jogo terminou, precisamos comparar o valor de cada componente ImageView para verificar se houve um vencedor e será necessário verificar se todos os componentes já foram selecionados caracterizando um empate, para fazer essa verificação, vamos criar o método verificarSeGanhouOuEmpatou(): Utilizando ImageView e AlertDialog 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 50 private boolean verificarSeGanhouOuEmpatou() { String[] opcoes = montaArrayOpcoes(); String vencedor = temVencedor(opcoes); /* Verifica se algum jogador venceu. */ if(vencedor != null && !"".equals(vencedor)) { apresentarMensagem("O vencedor foi o: " + vencedor); return true; } else { /* Se ainda não houver vencedor, verifica se deu velha. */ if(deuEmpate(opcoes)) { apresentarMensagem("Deu Velha!"); return true; } } return false; } Para percorrer os componentes ImageView da tela de forma mais fácil foi criado o método montaArrayOpcoes que cria um array de String com o conteúdo da descrições dos ImageView: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private String[] montaArrayOpcoes() { String[] opcoes = new String[9]; if(((ImageView) findViewById(R.id.ImageView01)).getContentDescription() != null) { opcoes[0] = ((ImageView) findViewById(R.id.ImageView01)) .getContentDescription().toString(); } if(((ImageView) findViewById(R.id.ImageView02)).getContentDescription() != null) { opcoes[1] = ((ImageView) findViewById(R.id.ImageView02)) .getContentDescription().toString(); } if(((ImageView) findViewById(R.id.ImageView03)).getContentDescription() != null) { opcoes[2] = ((ImageView) findViewById(R.id.ImageView03)) .getContentDescription().toString(); } Utilizando ImageView e AlertDialog 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 51 if(((ImageView) findViewById(R.id.ImageView04)).getContentDescription() != null) { opcoes[3] = ((ImageView) findViewById(R.id.ImageView04)) .getContentDescription().toString(); } if(((ImageView) findViewById(R.id.ImageView05)).getContentDescription() != null) { opcoes[4] = ((ImageView) findViewById(R.id.ImageView05)) .getContentDescription().toString(); } if(((ImageView) findViewById(R.id.ImageView06)).getContentDescription() != null) { opcoes[5] = ((ImageView) findViewById(R.id.ImageView06)) .getContentDescription().toString(); } if(((ImageView) findViewById(R.id.ImageView07)).getContentDescription() != null) { opcoes[6] = ((ImageView) findViewById(R.id.ImageView07)) .getContentDescription().toString(); } if(((ImageView) findViewById(R.id.ImageView08)).getContentDescription() != null) { opcoes[7] = ((ImageView) findViewById(R.id.ImageView08)) .getContentDescription().toString(); } if(((ImageView) findViewById(R.id.ImageView09)).getContentDescription() != null) { opcoes[8] = ((ImageView) findViewById(R.id.ImageView09)) .getContentDescription().toString(); } return opcoes; } Para verificar se houve vencedor, vamos criar o método temVencedor que recebe o vetor das opções jogadas e compara cada jogada verificando se houve um vencedor: Utilizando ImageView e AlertDialog 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 private String temVencedor(String[] opcoes) { String vencedor = null; /* Verificar se a primeira linha está marcada com o mesmo texto. */ if(opcoes[0] != null && !"".equals(opcoes[0]) && opcoes[0].equals(opcoes[1]) && opcoes[1].equals(opcoes[2])) { vencedor = opcoes[0]; } else /* Verificar se a segunda linha está marcada com o mesmo texto. */ if(opcoes[3] != null && !"".equals(opcoes[3]) && opcoes[3].equals(opcoes[4]) && opcoes[3].equals(opcoes[5])) { vencedor = opcoes[3]; } else /* Verificar se a terceira linha está marcada com o mesmo texto. */ if(opcoes[6] != null && !"".equals(opcoes[6]) && opcoes[6].equals(opcoes[7]) && opcoes[6].equals(opcoes[8])) { vencedor = opcoes[6]; } else /* Verificar se a primeira coluna está marcada com o mesmo texto. */ if(opcoes[0] != null && !"".equals(opcoes[0]) && opcoes[0].equals(opcoes[3]) && opcoes[0].equals(opcoes[6])) { vencedor = opcoes[0]; } else /* Verificar se a segunda coluna está marcada com o mesmo texto. */ if(opcoes[1] != null && !"".equals(opcoes[1]) && opcoes[1].equals(opcoes[4]) && opcoes[1].equals(opcoes[7])) { vencedor = opcoes[1]; } else /* Verificar se a terceica coluna está marcada com o mesmo texto. */ if(opcoes[2] != null && !"".equals(opcoes[2]) && opcoes[2].equals(opcoes[5]) && opcoes[2].equals(opcoes[8])) { vencedor = opcoes[2]; } else /* Verifica se a diagonal da esquerda para a direita está marcada com o mesmo \ texto. */ if(opcoes[0] != null && !"".equals(opcoes[0]) && opcoes[0].equals(opcoes[4]) 52 Utilizando ImageView e AlertDialog 43 44 45 46 47 48 49 50 51 52 53 54 55 53 && opcoes[0].equals(opcoes[8])) { vencedor = opcoes[0]; } else /* Verifica se toda a diagonal da direita para a esquerda está marcada com o m\ esmo texto. */ if(opcoes[2] != null && !"".equals(opcoes[2]) && opcoes[2].equals(opcoes[4]) && opcoes[2].equals(opcoes[6])) { vencedor = opcoes[2]; } return vencedor; } Para verificar se houve empate, vamos criar o método deuEmpate que recebe o vetor das opções e verifica se já foram feitas todas as jogadas possíveis: 1 2 3 4 5 private boolean deuEmpate(String[] opcoes) { return opcoes[0] != null && opcoes[1] != null && opcoes[2] != null && opcoes[3] != null && opcoes[4] != null && opcoes[5] != null && opcoes[6] != null && opcoes[7] != null && opcoes[8] != null; } Para apresentar uma mensagem na tela informando se houve vencedor ou deu empate, vamos adicionar o método apresentarMensagem(), que recebe a String da mensagem que deve ser apresentada: 1 2 3 4 5 6 7 8 9 10 11 12 13 private void apresentarMensagem(String msg) { AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setMessage(msg) .setNeutralButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { limparDados(); } }); AlertDialog alert = builder.create(); alert.setTitle("Jogo Da Velha"); alert.show(); } Neste exemplo usamos o componente AlertDialog para montar uma caixa de diálogo e apresentar a mensagem se houve um vencedor ou se deu velha. Após apresentar a mensagem, vamos chamar o método limparDados que reiniciará o jogo: Utilizando ImageView e AlertDialog 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 54 private void limparDados() { jogadorX = true; ((ImageView) ((ImageView) ((ImageView) ((ImageView) ((ImageView) ((ImageView) ((ImageView) ((ImageView) ((ImageView) findViewById(R.id.ImageView01)).setContentDescription(null); findViewById(R.id.ImageView02)).setContentDescription(null); findViewById(R.id.ImageView03)).setContentDescription(null); findViewById(R.id.ImageView04)).setContentDescription(null); findViewById(R.id.ImageView05)).setContentDescription(null); findViewById(R.id.ImageView06)).setContentDescription(null); findViewById(R.id.ImageView07)).setContentDescription(null); findViewById(R.id.ImageView08)).setContentDescription(null); findViewById(R.id.ImageView09)).setContentDescription(null); ((ImageView) ((ImageView) ((ImageView) ((ImageView) ((ImageView) ((ImageView) ((ImageView) ((ImageView) ((ImageView) findViewById(R.id.ImageView01)).setImageResource(0); findViewById(R.id.ImageView02)).setImageResource(0); findViewById(R.id.ImageView03)).setImageResource(0); findViewById(R.id.ImageView04)).setImageResource(0); findViewById(R.id.ImageView05)).setImageResource(0); findViewById(R.id.ImageView06)).setImageResource(0); findViewById(R.id.ImageView07)).setImageResource(0); findViewById(R.id.ImageView08)).setImageResource(0); findViewById(R.id.ImageView09)).setImageResource(0); } Execute o aplicativo no emulador do Android para verificar como ficou, deve ficar semelhante a Figura 3.2.1 e aproveite para jogar um pouco e descontrair. 55 Utilizando ImageView e AlertDialog Figura 3.2.1 - Jogo da Velha. 3.3 Resumo Neste capítulo usamos basicamente os mesmos componentes já apresentado, mas com algumas variações, na tela adicionamos componente ImageView que são carregados dinamicamente e também criamos uma forma mais comum de caixa de diálogo chamado AlertDialog. Também declaramos mais métodos na Activity para tentar deixar o código mais organizado. Mais adiante apresentaremos como organizar melhor os componentes da tela para que eles fiquem visivelmente alinhados em diversas resoluções diferentes. 3.4 Exercícios Exercício 1 - Esse jogo da velha foi criado para ser jogado com duas pessoas, altere o jogo para jogar contra o computador. 4. Requisitando outra tela com Intent Vamos criar um aplicativo Android chamado MinhasTarefas para registro de tarefas. Nesse projeto será abordado o uso de Intent para chamar uma Activity, teremos uma tela de autenticação, na qual usuário informa seu login e senha e uma tela para adicionar tarefas. Também utilizaremos os componentes EditText e ListView. Figura 4.1 - Minhas tarefas. Observação: A imagem utilizada neste capítulo estão disponíveis no GitHub¹. 4.1 Criando uma tela de autenticação Para começar, crie a tela que será utilizada para autenticação, como apresentada na Figura 4.1.1. Nessa tela haverá uma figura do logo, os rótulos de Login: e Senha:, os campos para digitação do ¹https://github.com/rafaelsakurai/livro-google-android 57 Requisitando outra tela com Intent login e senha e o botão para entrar na tela de tarefas. Figura 4.1.1 - Tela de autenticação. Monte na activity_main.xml a estrutura da tela, adicione: 1- Uma ImageView com o logo.png na parte superior da tela; 2- Um TextView com o rótulo Login:, lembre de guardar todas as Strings no arquivo res/values/strings.xml. 3- Um EditText (caixa de texto) para digitar a informação do login. 4- Um TextView com o rótulo Senha:, lembre de guardar todas as Strings no arquivo res/values/strings.xml. 5- Um EditText para digitar a informação da senha. Para representar um campo de senha utilize a propriedade android:inputType="textPassword" do componente EditText, exemplo: Requisitando outra tela com Intent 1 2 3 4 5 6 58 <EditText android:id="@+id/senha" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" android:inputType="textPassword"/> 6- Botão para Entrar, para representar o botão utilize o componente Button, exemplo: 1 2 3 4 5 6 7 8 9 <Button android:id="@+id/entrar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:onClick="entrar" android:text="@string/entrar" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" /> Observação: durante a criação da tela informe o atributo android:id dos componentes que serão usados pelas classes Activity, pois é através do id que obtemos esses atributos. Nesse momento temos o layout da primeira tela criado, mas ainda não é funcional. No código usado nessa tela, defini que a figura deve ficar alinhada no topo da tela, e os demais componentes estão alinhados com base no fundo da tela. Ficando assim: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.leanpub.googleandroid.minhastarefas.MainActivity" android:layout_marginBottom="8dp" android:layout_marginTop="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp"> <ImageView android:id="@+id/logo" Requisitando outra tela com Intent 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/logo" app:layout_constraintTop_toTopOf="parent" /> <LinearLayout android:id="@+id/linear1" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" app:layout_constraintBottom_toTopOf="@+id/linear2"> <TextView android:id="@+id/textLogin" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/login" android:textSize="20sp"/> <EditText android:id="@+id/login" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" app:layout_constraintTop_toBottomOf="@+id/logo" tools:layout_editor_absoluteX="81dp" /> </LinearLayout> <LinearLayout android:id="@+id/linear2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:gravity="center_vertical" android:orientation="horizontal" app:layout_constraintBottom_toTopOf="@+id/entrar"> <TextView android:id="@+id/textSenha" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/senha" 59 Requisitando outra tela com Intent 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 60 android:textSize="20sp" /> <EditText android:id="@+id/senha" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" android:inputType="textPassword"/> </LinearLayout> <Button android:id="@+id/entrar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:onClick="entrar" android:text="@string/entrar" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" /> </android.support.constraint.ConstraintLayout> Observação: Ficará um erro no Button porque ainda não está implementado o método entrar, mas daqui a pouco isso será resolvido. 4.2 Criando a tela da lista de tarefas Vamos criar uma nova tela para representar a listagem das tarefas, para criar um novo layout, clique com o botão direito do mouse na pasta app → res → layout e escolha New → Layout resource file. Na tela de New Resource File defina: • File name (Nome do arquivo): tarefa • Root element (Elemento raiz): ConstraintLayout Clique em OK para criar o arquivo de layout. A Figura 4.2.1 apresenta um exemplo de criação de layout de tela. 61 Requisitando outra tela com Intent Figura 4.2.1 - Novo layout de tela. O arquivo de layout criado representa uma tela do aplicativo Android, vamos colocar nessa tela um campo para digitar o texto da tarefa, um botão Adicionar e uma lista de elementos, como na Figura 4.2.2. Figura 4.2.2 - Tela de tarefas. Para adicionar uma lista na tela vamos utilizar o componente ListView, abra o arquivo tarefa.xml Requisitando outra tela com Intent e adicione o seguinte exemplo: 1 2 3 4 5 <ListView android:id="@+id/listaTarefas" android:layout_width="match_parent" android:layout_height="wrap_content" > </ListView> A seguir é apresentado o código para a tela tarefa.xml: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.andro\ id.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="8dp" android:layout_marginTop="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp"> <EditText android:id="@+id/tarefa" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="text" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/adicionar" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/adicionar" android:onClick="adicionarTarefa" app:layout_constraintTop_toBottomOf="@+id/tarefa" /> <ListView android:id="@+id/listaTarefas" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@+id/adicionar"/> </android.support.constraint.ConstraintLayout> 62 63 Requisitando outra tela com Intent A execução das ações desta tela de tarefas ficará em uma nova Activity. Vamos criar uma nova classe Activity, para isso clique com o botão direito do mouse no mesmo diretório em que está a MainActivity, escolha New (Novo) → Java Class, informe o Name (Nome) como TarefaActivity, em Superclass informe AppCompatActivity, e o nome do Package (Pacote), aqui provavelmente já deve aparecer o mesmo nome de package da MainActivity, caso contrário escreva o nome do package. A Figura 4.2.3 apresenta um exemplo de criação de uma Activity. Figura 4.2.3 - Criando uma nova Activity. Altere o código da classe TarefaActivity.java para ficar com o seguinte conteúdo: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.leanpub.googleandroid.minhastarefas; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; public class TarefaActivity extends AppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tarefa); } } A chamada do método setContentView(R.layout.tarefa) informa que esta Activity usa o layout de tela tarefa.xml. Requisitando outra tela com Intent 64 Para o aplicativo reconhecer que existe esta nova Activity, precisamos informar no arquivo AndroidManifest.xml, inclua <activity android:name=".TarefaActivity"/> após a declaração da tag activity MainActivity e dentro da tag application A declaração completa do AndroidManifest.xml fica assim: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.leanpub.googleandroid.minhastarefas" > <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".TarefaActivity"/> </application> </manifest> Um detalhe importante, quando colocamos o símbolo de ponto antes do nome da activity como ".TarefaActivity", é porque estamos dizendo que está activity está dentro do pacote mencionado na propriedade package="com.leanpub.googleandroid.minhastarefas" da tag manifest. Se a Activity for criada em outro pacote, é necessário mencionar o caminho completo como: <activity android:name="googleandroid.exemplo.TarefaActivity"/>. 4.3 Usando o Intent Agora que as duas telas estão prontas, podemos adicionar o comportamento nas telas. Na tela inicial temos o botão entrar que fará uma autenticação, verificando se o login e senha digitados são corretos. Na MainActivity adicione os seguintes imports: Requisitando outra tela com Intent 1 2 3 4 import import import import 65 android.content.Intent; android.view.View; android.widget.EditText; android.widget.Toast; Ao clicar no botão Entrar da activity_main.xml, chame o método entrar(View view) na MainActivity.java. 1 2 3 4 5 6 7 8 9 public void entrar(View view) { EditText login = (EditText) findViewById(R.id.login); EditText senha = (EditText) findViewById(R.id.senha); if ("admin".equals(login.getText().toString()) && "123".equals(senha.getText().toString())) { //chamar a tela de tarefas. } } O método entrar(View view) acessa os EditText de login e senha da tela e compara com os valores “admin” e “123” respectivamente, mais adiante será apresentado como guardar essas informações em um banco de dados para não deixar fixo no código. Observação: Note que no método findViewById precisamos passar o ID do componente XML, então se você não definiu nenhum ID no momento da criação do layout, altere o XML do layout adicionando os IDs de cada componente. Para chamar uma Activity utilizaremos a classe android.content.Intent, sempre que houver a intenção de chamar um componente do aplicativo, iniciar algum aplicativo do Android, reproduzir uma musica, entre outros, utilizamos o Intent. Para chamar uma tela utilizando a Intent, usamos o método startActivity() que a classe recebe via herança da classe Activity e passamos para esse método a Intent, exemplo: 1 startActivity(new Intent(this, TarefaActivity.class)); Então se o usuário informar os dados corretos de login e senha, será aberto a tela das tarefas. Mas se o usuário digitar alguma informação incorreta, podemos enviar uma mensagem informando que o login ou senha está incorreto, exemplo: 66 Requisitando outra tela com Intent 1 2 3 else { Toast.makeText(this, "Login ou senha inválida!", Toast.LENGTH_SHORT).show(); } Caso o usuário digite alguma informação incorreta, aparecerá uma mensagem Toast de aviso na tela. Figura 4.3.1 - Erro na autenticação. O código completo da MainActivity ficou assim: 1 2 3 4 5 6 7 8 9 10 11 package com.leanpub.googleandroid.minhastarefas; import import import import import import android.content.Intent; android.support.v7.app.AppCompatActivity; android.os.Bundle; android.view.View; android.widget.EditText; android.widget.Toast; public class MainActivity extends AppCompatActivity { Requisitando outra tela com Intent 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 67 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void entrar(View view) { EditText login = (EditText) findViewById(R.id.login); EditText senha = (EditText) findViewById(R.id.senha); if ("admin".equals(login.getText().toString()) && "123".equals(senha.getText().toString())) { startActivity(new Intent(this, TarefaActivity.class)); } else { Toast.makeText(this, "Login ou senha inválida!", Toast.LENGTH_SHORT).show(); } } } Com a tela de autenticação funcionando, vamos adicionar o comportamento na tela da lista de tarefas. Na classe TarefaActivity adicione os seguintes imports: 1 2 3 4 5 6 7 8 import import import import import android.view.View; android.widget.ArrayAdapter; android.widget.EditText; android.widget.ListView; android.widget.Toast; import java.util.ArrayList; import java.util.List; Também adicione uma lista de String, que serão as tarefas que devem ser apresentadas na lista e para adicionar elementos no ListView, vamos utilizar um ArrayAdapter, exemplo: 1 2 private List<String> tarefas; private ArrayAdapter<String> adapter; Altere o método onCreate para criar uma lista das tarefas: Requisitando outra tela com Intent 1 2 3 4 5 6 7 8 9 10 11 68 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tarefa); tarefas = new ArrayList<String>(); ListView lista = (ListView) findViewById(R.id.listaTarefas); adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, tarefas); lista.setAdapter(adapter); } No momento de instanciar um ArrayAdapter é informado a Activity atual com o atributo this. Para o layout da ListView, vamos utilizar um padrão já disponível no Android chamado android.R.layout.simple_list_item_1, mas posteriormente veremos como criar um layout personalizado para a ListView. E por último informamos a lista de Strings que contém aos itens que serão apresentados na lista. Quando o usuário clicar no botão Adicionar da tela de tarefas, faça uma chamada para o método adicionarTarefa(View view) que obtém o valor do campo descrição e se ele estiver preenchido, adiciona o na lista, caso contrário apresenta uma mensagem informativa. 1 2 3 4 5 6 7 8 9 10 public void adicionarTarefa(View view) { EditText descricao = (EditText) findViewById(R.id.tarefa); if(descricao.getText().toString().trim().length() > 0) { tarefas.add(descricao.getText().toString()); adapter.notifyDataSetChanged(); descricao.setText(""); } else { Toast.makeText(this, "Campo tarefa vazio!", Toast.LENGTH_SHORT).show(); } } Associe o método adicionarTarefa com o botão Adicionar que temos no layout da tarefa.xml. Execute o aplicativo e cadastre algumas tarefas. Requisitando outra tela com Intent 69 Figura 4.3.2 - Usando o aplicativo Minhas Tarefas. 4.4 Resumo Neste capítulo usamos o Intent para solicitar abrir um Activity e usamos o ListView para guardar as tarefas que foram criadas. No próximo capítulo apresentaremos como enviar informações na solicitação ou retorno de uma Intent usando o Bundle. 4.5 Exercícios Exercício 1 - Crie um aplicativo que na primeira tela possua a imagem de algum jogo de tabuleiro e um botão chamado Regras do Jogo, quando o usuário tocar no botão deve apresentar uma nova activity que apresenta as regras do jogo escolhido. 5. Trocando dados entre telas via Bundle No capítulo anterior foi abordado como uma Activity pode chamar outra Activity usando o Intent, mas dependendo da situação precisamos passar alguns dados nessa mesma chamada. 5.1 Bundle Para isto, podemos utilizar o Bundle que consegue guardar diversas informações e pode ser enviado no Intent. Exemplo: 1 2 3 4 5 6 7 Bundle bundle = new Bundle(); bundle.putString("string", valor); bundle.putInt("int", numero); Intent intent = new Intent(getBaseContext(), MinhaActivity.class); intent.putExtras(bundle); startActivity(intent); Nesse exemplo, um Bundle é criado e nele pode ser adicionado diversos tipos de dados por meio dos métodos putXxx(), como: putString(String chave, String valor) que recebe como parâmetro uma String de chave e outra String com o seu valor. Quando cria a Intent e passa o bundle para o método putExtras, essas informações estão sendo enviadas para a Activity que será chamada. Na activity chamada pelo Intent é possível pegar os dados do bundle, assim: 1 2 3 Bundle bundle = getIntent().getExtras(); String texto = bundle.getString("string"); int numero = bundle.getInt("int"); Desse modo, é possível por exemplo, obter os dados no bundle da Intent no método onCreate e usar para inicializar algum valor. Para isso use o método getIntent().getExtras() que devolve o Bundle enviado pela Intent e por meio dele é possível obter as informações com métodos getXxx(), como getString("string") que recebe como parâmetro a chave do valor adicionado no bundle. Observação: Veremos mais adiante que o Intent pode ser usado para diversas outras coisas além de chamar uma Activity. Trocando dados entre telas via Bundle 71 Um Bundle também pode ser usado para devolver uma resposta para chamada de uma Activity. Até os capítulos anteriores foi utilizado o método startActivity(Intent intent) para chamar uma Activity, mas também há o método startActivityForResult(Intent intent, int requestCode) e um outro método sobrecarregado que também recebe um Bundle como parâmetro. O método startActivityForResult é usado para obter uma resposta de quem foi chamado pela Intent e para isso é necessário implementar na Activity iniciou o Intent o método onActivityResult que tratará o retorno da Intent. Exemplo: 1 2 3 4 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); } 5.2 Criando o projeto de Valet Neste capítulo, crie um novo projeto chamado Valet, mas no momento de escolher qual Activity utilizar no projeto, escolha a opção Basic Activity, como mostrado na Figura 5.2.1. Nesse exemplo vamos utilizar este botão flutuante que tem na tela inicial para realizar a troca de telas. Trocando dados entre telas via Bundle 72 Figura 5.2.1 - Adicionando uma activity basica ao projeto. Após escolher a opção Basic Activity clique em Next para continuar. Na tela Configure Activity (apresentada na Figura 5.2.2) aparece algumas opções a mais, mas não iremos alterar, por tanto clique em Finish para terminar a criação do projeto. 73 Trocando dados entre telas via Bundle Figura 5.2.2 - Configurando a activity. Note na Figura 5.2.3 que agora o conteúdo da tela inicial está dentro do arquivo content_main.xml, mas ainda temos também o layout da activity_main.xml que iremos alterar mais adiante. Trocando dados entre telas via Bundle 74 Figura 5.2.3 - Estrutura do projeto Valet. Este projeto terá duas telas para controle de estacionamento. A primeira tela tem uma lista de mostrando o modelo, placa e data de entrada dos veículos no Valet. Esta tela também tem o menu flutuante com o símbolo de +, que redireciona para a tela que cadastra um novo veículo no Valet. A Figura 5.2.4 mostra como ficará esse aplicativo. 75 Trocando dados entre telas via Bundle Figura 5.2.4 - App Valet. A tela inicial content_main.xml servirá para mostrar uma lista de veículos com um TextView e um ListView, altere para que ela fique com a aparência apresentada na Figura 5.2.5. 76 Trocando dados entre telas via Bundle Figura 5.2.5 - Estrutura da tela inicial. O código fonte da content_main.xml ficou assim: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="com.leanpub.googleandroid.valet.MainActivity" tools:showIn="@layout/activity_main"> <TextView android:id="@+id/titulo" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:gravity="center" android:text="@string/app_name" android:textSize="30sp" app:layout_constraintTop_toTopOf="parent" /> Trocando dados entre telas via Bundle 21 22 23 24 25 26 27 28 29 77 <ListView android:id="@+id/lista" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" app:layout_constraintTop_toBottomOf="@+id/titulo" /> </android.support.constraint.ConstraintLayout> Agora vamos alterar o botão flutuante que está com um símbolo de carta de correio para um símbolo de +, como mostrado na Figura 5.2.6. Figura 5.2.6 - Alterando o botão flutuante. Adicione o ícone ic_add_48px.xml na pasta app → res → drawable do projeto. Este ícone está disponível no GitHub¹. No activity_main.xml procure pela tag FloatingActionButton: 1 2 3 4 5 6 7 <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" app:srcCompat="@android:drawable/ic_dialog_email" /> Altere a propriedade app:srcCompat="@android:drawable/ic_dialog_email" para android:src="@drawable/ic_add_48px". O código completo do FloatingActionButton ficou assim: ¹https://github.com/rafaelsakurai/livro-google-android Trocando dados entre telas via Bundle 1 2 3 4 5 6 7 78 <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" android:src="@drawable/ic_add_48px" /> Agora vamos trocar a cor de fundo do botão flutuante para um tom de azul, assim ficando similar a barra superior do aplicativo. Abra o arquivo colors.xml que está na pasta app → res → values. E altere a cor colorAccent para: 1 <color name="colorAccent">#92A8D1</color> 5.3 Adicionando a tela de cadastro de veículos Na pasta app → res → layout crie um arquivo layout chamado valet.xml possuindo o formulário para preencher o modelo e placa do veículo, similar a Figura 5.3.1. Figura 5.3.1 - Alterando o botão flutuante. O código completo dessa tela ficou assim: Trocando dados entre telas via Bundle 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginBottom="8dp"> <TextView android:id="@+id/titulo" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/novo_carro" android:textSize="30sp" android:gravity="center" app:layout_constraintTop_toTopOf="parent" /> <LinearLayout android:id="@+id/layout1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" app:layout_constraintTop_toBottomOf="@id/titulo"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/modelo"/> <EditText android:id="@+id/modelo" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="8dp"/> </LinearLayout> <LinearLayout android:id="@+id/layout2" 79 Trocando dados entre telas via Bundle 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 80 android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" app:layout_constraintTop_toBottomOf="@id/layout1"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/placa"/> <EditText android:id="@+id/placa" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textCapCharacters" android:layout_marginLeft="8dp"/> </LinearLayout> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/salvar" android:layout_gravity="center" android:textSize="20sp" android:onClick="adicionar" android:layout_marginTop="8dp" app:layout_constraintTop_toBottomOf="@id/layout2"/> </android.support.constraint.ConstraintLayout> O componente EditText pode ser personalizado para ajudar a digitação do usuário. Uma pequena modificação foi feita no segundo EditText que serve para digitar a placa, nele foi adicionado a propriedade android:inputType="textCapCharacters" para que todos os caracteres digitados fiquem em maiúsculo. No arquivo strings.xml adicione as strings de modelo, placa e salvar: 1 2 3 <string name="modelo">Modelo</string> <string name="placa">Placa</string> <string name="salvar">Salvar</string> Então, já está pronto layout das duas telas, agora vamos montar o comportamento destas telas. Trocando dados entre telas via Bundle 81 5.4 Chamando a tela de cadastro de veículo Dentro do pacote java do projeto, crie uma nova classe chamada ValetActivity que herda de AppCompatActivity e implementa o método onCreate, exemplo: 1 2 3 4 5 6 7 8 9 10 11 12 package com.leanpub.googleandroid.valet; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; public class ValetActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.valet); } } Esta activity é responsável pelo layout da tela valet.xml. Lembrando que para o aplicativo reconhecer que existe esta nova ValeActivity.class, é necessário informar no arquivo AndroidManifest.xml, após a declaração da tag activity MainActivity e dentro da tag application, a declaração completa fica assim: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.leanpub.googleandroid.valet"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> Trocando dados entre telas via Bundle 19 20 21 22 23 24 82 </intent-filter> </activity> <activity android:name=".ValetActivity"/> </application> </manifest> O botão flutuante da tela principal será utilizado para chamar a tela de cadastro do veículo. Na classe MainActivity procure no método onCreate a implementação do FloatingActionButton, por padrão essa implementação apresenta uma mensagem de aviso, altere o conteúdo para chamar outra TarefaActivity: 1 2 3 4 5 6 7 8 FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(getBaseContext(), ValetActivity.class); startActivityForResult(intent, 1); } }); Agora ao clicar no botão flutuante será criado um Intent para a ValetActivity, só que para iniciar esta activity foi usado o método startActivityForResult, pois a ValetActivity devolverá os dados do veículo que serão adicionados na lista de valet. Na ValetActivity adicione os imports: 1 2 3 4 import import import import android.app.Activity; android.content.Intent; android.view.View; android.widget.EditText; Também inclua na ValetActivity o método adicionar: Trocando dados entre telas via Bundle 1 2 3 4 5 6 7 8 9 83 public void adicionar(View view) { Intent intent = new Intent(); intent.putExtra("modelo", ((EditText) findViewById(R.id.modelo)) .getText().toString()); intent.putExtra("placa", ((EditText) findViewById(R.id.placa)) .getText().toString()); setResult(Activity.RESULT_OK, intent); finish(); } O método adicionar será executado quando o usuário clicar (tocar) no botão SALVAR do formulário. Um Intent é criado com os dados preenchidos de modelo e placa; a chamada do método setResult(Activity.RESULT_OK, intent) indica que a chamada desta tela foi feita com sucesso e que está retornando os dados para a tela inicial; também há uma chamada para o método finish() que finaliza está activity. Até este ponto, se você executar o aplicativo, verá que o fluxo está funcionando, da tela inicial, vai para a tela de cadastro do veículo e volta para a tela inicial, mas nenhum dado está sendo inserido na lista de valet. 5.5 Obtendo o retorno da chamada da Activity Na MainActivity adicione os seguintes imports: 1 2 3 4 5 6 7 8 import android.app.Activity; import android.widget.ArrayAdapter; import android.widget.ListView; import import import import java.text.DateFormat; java.util.ArrayList; java.util.Date; java.util.List; Para implementar o retorno da chamada da tela de cadastro de veículo, crie a lista de carros e o formatador de data/hora na MainActivity: Trocando dados entre telas via Bundle 1 2 3 4 5 84 private List<String> carros = new ArrayList(); private ArrayAdapter<String> adapter; private DateFormat df = null; private DateFormat hf = null; Para inicializar a ListView e os formatadores de data, altere o método onCreate: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(getBaseContext(), ValetActivity.class); startActivityForResult(intent, 1); } }); df = android.text.format.DateFormat.getDateFormat(getApplicationContext()); hf = android.text.format.DateFormat.getTimeFormat(getApplicationContext()); ListView lista = (ListView) findViewById(R.id.lista); adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, carros); lista.setAdapter(adapter); } Foi inicializado um formatador para data e outro para hora, esses formatadores estão obtendo o formato padrão de acordo as preferências de local da instalação do Android. Também está criando um ArrayAdapter que permitirá adicionar Strings na lista. Agora implemente o método onActivityResult que fará o tratamento do retorno da tela de cadastro de veículo. Trocando dados entre telas via Bundle 1 2 3 4 5 6 7 8 9 10 11 12 85 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (Activity.RESULT_OK == resultCode) { String modelo = data.getStringExtra("modelo"); String placa = data.getStringExtra("placa"); Date agora = new Date(); carros.add(modelo + " - " + placa + "\nEntrada: " + df.format(agora) + " " + hf.format(agora)); adapter.notifyDataSetChanged(); } } O método onActivityResult verifica se o resultCode é igual a Activity.RESULT_OK, se for, então pega as informações de modelo e placa passados pelo bundle do Intent e cria uma nova data, com esses dados adiciona uma nova string com o cadastro do veículo na lista. Pronto, agora teste o aplicativo e verá que agora está adicionando os veículos na lista, como mostrado na Figura 5.4.1. Figura 5.4.1 - Cadastro de vários veículos. Para melhorar esse exemplo, vamos permitir remover os veículos cadastrados. Trocando dados entre telas via Bundle 86 5.6 Removendo os elementos da lista Para fazer isso vamos criar uma ação de click na lista, que quando for acionado abre uma caixa de dialogo perguntando se deseja retirar o veículo. Na classe MainActivity adicione os imports: 1 2 3 import android.app.AlertDialog; import android.content.DialogInterface; import android.widget.AdapterView; Também adicione os atributos para representar a Caixa de Dialogo e o carro selecionado: 1 2 private AlertDialog dialog; private String carroSaida = null; No final do método onCreate adicione o listener de click da lista: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(getBaseContext(), ValetActivity.class); startActivityForResult(intent, 1); } }); df = android.text.format.DateFormat .getDateFormat(getApplicationContext()); hf = android.text.format.DateFormat .getTimeFormat(getApplicationContext()); ListView lista = (ListView) findViewById(R.id.lista); adapter = new ArrayAdapter<String>(this, Trocando dados entre telas via Bundle 24 25 26 27 28 29 30 31 32 33 34 35 36 87 android.R.layout.simple_list_item_1, carros); lista.setAdapter(adapter); lista.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { carroSaida = carros.get(position); dialog = confirmarSaida(); dialog.show(); } }); } O método setOnItemClickListener implementa um AdapterView.OnItemClickListener que controla o clique na lista. Quando algum carro da lista da lista for clicado, será montado uma caixa de dialogo através do método confirmarSaida: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public AlertDialog confirmarSaida() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.saida); builder.setMessage(carroSaida); builder.setPositiveButton(getString(R.string.sim), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { carros.remove(carroSaida); adapter.notifyDataSetChanged(); } }); builder.setNegativeButton(getString(R.string.nao), null); return builder.create(); } Este método cria um AlertDialog, informando um título, uma mensagem e a ação do botão Sim representado pelo positiveButton da caixa de dialogo. Se clicar no botão Sim o carro selecionado será removido da lista. No arquivo strings.xml adicione as strings de saida, sim e nao: 88 Trocando dados entre telas via Bundle 1 2 3 <string name="saida">Saída do veículo</string> <string name="sim">Sim</string> <string name="nao">Não</string> Execute novamente o aplicativo e teste a saída de um veículo, como mostrado na Figura 5.5.1. Figura 5.5.1 - Saída do veículo. Você já deve ter percebido que os dados do veículo ficam salvos apenas durante a execução do aplicativo, cada vez que você publicar o aplicativo novamente no emulador os dados são apagados. No próximo capítulo veremos como persistir as informações usando o SQLite. 5.7 Resumo Neste capítulo foi apresentado o uso do Bundle como forma de passar informações na chamada ou retorno de um Intent, vimos como podemos facilmente adicionar ou remover uma informação em uma ListView e também utilizamos o AlertDialog para montar uma caixa de dialogo. No próximo capítulo veremos como persistir dados usando o banco de dados interno do Android chamado SQLite. Trocando dados entre telas via Bundle 89 5.8 Exercícios Exercício 1 - Altere o projeto de Tarefas apresentado no Capítulo 3 implementando a opção de remover uma tarefa. Exercício 2 - Como podemos alterar o projeto do Valet para que ele calcule e apresente no momento da saída do veículo o valor que deve ser pago pelo período de estacionamento do veículo? 6. Persistindo dados com SQLite Neste capítulo, vamos alterar o aplicativo Android de Valet criado no capítulo 5, para persistir os veículos cadastrados no SQLite. Figura 6.1 - Lista de veículos salvos com SQLite. Crie uma classe Java chamada Valet no pacote do projeto, esta classe será usada para representar cada veículo no estacionamento: 1 2 3 4 5 6 7 8 9 package com.leanpub.googleandroid.valet; import java.util.Date; public class Valet { private Long _id; private String modelo; private String placa; private Date entrada; Persistindo dados com SQLite 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 private Date saida; private double preco; public Long getId() { return _id; } public void setId(Long _id) { this._id = _id; } public String getModelo() { return modelo; } public void setModelo(String modelo) { this.modelo = modelo; } public String getPlaca() { return placa; } public void setPlaca(String placa) { this.placa = placa; } public Date getEntrada() { return entrada; } public void setEntrada(Date entrada) { this.entrada = entrada; } public Date getSaida() { return saida; } public void setSaida(Date saida) { this.saida = saida; } 91 Persistindo dados com SQLite 52 53 54 55 56 57 58 59 60 92 public double getPreco() { return preco; } public void setPreco(double preco) { this.preco = preco; } } 6.1 Montando o banco de dados no SQLite O Android possui internamente um banco de dados SQLite, no qual é possível criarmos tabelas, criar relacionamentos, executar operações de inclusão, alteração, exclusão, consultas, entre outros. Para começar, crie uma classe Java chamada ValetDB no pacote do projeto, que será utilizada para criar a tabela no SQLite. Faça essa classe herdar de android.database.sqlite.SQLiteOpenHelper e implementar os métodos onCreate e onUpgrade: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.leanpub.googleandroid.valet; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class ValetDB extends SQLiteOpenHelper { @Override public void onCreate(SQLiteDatabase db) { } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } O método onCreate é chamado quando o aplicativo é instalado no celular e o método onUpgrade é chamado quando já tem uma versão do aplicativo instalado no celular e há novas estruturas do banco de dado para serem atualizadas. Dentro da classe ValetDB crie um construtor que receberá como parâmetro um android.context.Context e iniciará a base de dados chamada Valet e sua versão 1: Persistindo dados com SQLite 1 2 3 93 public ValetDB(Context context) { super(context, "Valet", null, 1); } Altere a implementação do método onCreate para criar a tabela que armazenará os veículos: 1 2 3 4 5 6 @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE Valet (_id INTEGER PRIMARY KEY " + " AUTOINCREMENT, modelo TEXT, placa TEXT, entrada TEXT, " + " saida TEXT, valor REAL);"); } Este método será usado para criar todas as tabelas do banco de dados logo que o aplicativo for iniciado pela primeira vez e também pode ser usado para adicionar algumas informações iniciais no banco de dados. Na classe ValetDB vamos implementar os métodos para salvar ou alterar os dados de um veículo, e também consultar todos veículos que ainda estão no estacionamento. Comece adicionando os imports: 1 2 3 4 5 6 7 import android.content.ContentValues; import android.database.Cursor; import import import import java.text.DateFormat; java.text.SimpleDateFormat; java.util.ArrayList; java.util.List; Adicione o método salvarValet para salvar ou atualizar os dados do veículo no banco de dados: 1 2 3 4 5 6 7 8 9 10 public Valet salvarValet(Valet valet) { SQLiteDatabase db = getWritableDatabase(); try { ContentValues values = new ContentValues(); values.put("modelo", valet.getModelo()); values.put("placa", valet.getPlaca()); values.put("entrada", df.format(valet.getEntrada())); if (valet.getId() == null) { long id = db.insert("Valet", null, values); Persistindo dados com SQLite 11 12 13 14 15 16 17 18 19 20 21 22 23 94 valet.setId(id); } else { values.put("saida", df.format(valet.getSaida())); values.put("valor", valet.getPreco()); String[] where = new String[]{String.valueOf(valet.getId())}; db.update("Valet", values, "_id = ?", where); } } finally { db.close(); } return valet; } A classe ValetDB que estende a classe SQLiteOpenHelper recebe via herança o método getWritableDatabase() que fornece um objeto SQLiteDatabase com acesso de escrita na base de dados. Este método verifica se o objeto valet não tiver um id significa que ele é novo e para inserir os registros no banco de dados SQLite, o SQLiteDatabase tem o método insert que recebe como parâmetro o nome da tabela e um objeto ContentValues com os valores que serão inseridos. Se houver id no objeto valet, então atualiza o registro e para isso utiliza o método update do SQLiteDatabase. Este método recebe como parâmetro o nome da tabela, valores do registro que será alterado, critério de atualização e o parâmetro do critério de atualização. Um detalhe importante é que para salvar as datas no SQLite está sendo usado uma String no padrão yyyy-MM-dd HH:mm:ss, porque o ContentValue e o Cursor não tem métodos para trabalhar com tipos Date. Para formatar a data no padrão declare também um atributo de DateFormat: 1 private DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Para consultar todos os veículos no valet que estão salvos no banco de dados, vamos criar o método consultarVeiculosValet(): Persistindo dados com SQLite 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 95 public List<Valet> consultarVeiculosValet() { List<Valet> lista = new ArrayList(); SQLiteDatabase db = getReadableDatabase(); try { Cursor cursor = db.rawQuery("SELECT _id, modelo, placa, entrada " + " FROM Valet where saida is null", null); cursor.moveToFirst(); for (int i = 0; i < cursor.getCount(); i++) { Valet valet = new Valet(); valet.setId(cursor.getLong(0)); valet.setModelo(cursor.getString(1)); valet.setPlaca(cursor.getString(2)); valet.setEntrada(df.parse(cursor.getString(3))); lista.add(valet); cursor.moveToNext(); } cursor.close(); } catch(Exception e) { e.printStackTrace(); } finally { db.close(); } return lista; } Para executar uma consulta no SQLite foi utilizado o método getReadableDatabase() da classe SQLiteOpenHelper que devolve um SQLiteDatabase com permissão de leitura na base de dados. Com o método rawQuery() é possível executar uma consulta e obter um Cursor que possui o resultado da consulta. Para percorrer os resultados do cursor podemos utilizar os métodos: • moveToFirst() que move o cursor para a primeira linha, se o cursor estiver vazio retorna false; • moveToLast() que move o cursor para a última linha, se o cursor estiver vazio retorna false; • moveToNext() que move o cursor para a próxima linha, se o cursor estiver na última linha retorna false; • moveToPosition(int position) que move o cursor para uma linha informada, retorna false se não encontrar a linha informada; • moveToPrevious() que move o cursor para a linha anterior, se o cursor estiver na primeira linha retorna false. Após terminar de utilizar o cursor lembre-se de chamar o método close(). Persistindo dados com SQLite 96 6.2 Adicionando a persistência do veículos Agora vamos alterar a classe MainActivity para salvar os dados do veículo, para isso inclua o atributo do ValetDB nela: 1 2 private ValetDB db; private List<Valet> valets = new ArrayList(); Também aproveite para alterar o tipo do atributo carroSaida de String para representar o objeto Valet selecionado. 1 private Valet carroSaida = null; Altere a implementação do método onCreate para instanciar um novo objeto ValetDB db, consultar todos os veículos no valets e montar a ListView. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(getBaseContext(), ValetActivity.class); startActivityForResult(intent, 1); } }); df = android.text.format.DateFormat.getDateFormat(getApplicationContext()); hf = android.text.format.DateFormat.getTimeFormat(getApplicationContext()); db = new ValetDB(this); valets = db.consultarVeiculosValet(); for (Valet v : valets) { carros.add(v.getModelo() + " - " + v.getPlaca() + "\nEntrada: " + df.format(v.getEntrada()) + " " + hf.format(v.getEntrada())); Persistindo dados com SQLite 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 97 } ListView lista = (ListView) findViewById(R.id.lista); adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, carros); lista.setAdapter(adapter); lista.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { carroSaida = valets.get(position); dialog = confirmarSaida(); dialog.show(); } }); } Altere agora o método onActivityResult para criar o objeto Valet e chamar o método salvarValet do ValetDB: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (Activity.RESULT_OK == resultCode) { Valet valet = new Valet(); valet.setModelo(data.getStringExtra("modelo")); valet.setPlaca(data.getStringExtra("placa")); valet.setEntrada(new Date()); db.salvarValet(valet); valets.add(valet); carros.add(valet.getModelo() + " - " + valet.getPlaca() + "\nEntrada: " + df.format(valet.getEntrada()) + " " + hf.format(valet.getEntrada())); adapter.notifyDataSetChanged(); } } Agora só falta atualizar quando o veículo sair do valet, para isso altere o método confirmarSaida para informar a data de saída e o preço que deve ser pago pelo serviço de valet. Persistindo dados com SQLite 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public AlertDialog confirmarSaida() { carroSaida.setSaida(new Date()); carroSaida.setPreco(calcularPreco(carroSaida)); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.saida); builder.setMessage("Saída do " + carroSaida.getModelo() + " - " + carroSaida.getPlaca() + "\nPreço: " + carroSaida.getPreco()); builder.setPositiveButton(getString(R.string.sim), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { db.salvarValet(carroSaida); valets.remove(carroSaida); carros.remove(carroSaida.getModelo() + " - " + carroSaida.getPlaca() + "\nEntrada: " + df.format(carroSaida.getEntrada()) + " " + hf.format(carroSaida.getEntrada())); adapter.notifyDataSetChanged(); } }); builder.setNegativeButton(getString(R.string.nao), null); return builder.create(); } Vamos também criar o método que calcula o preço que deve ser pago pelo serviço de valet: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public double calcularPreco(Valet valet) { double preco = 0; long tempo = (valet.getSaida().getTime() - valet.getEntrada().getTime()) / 1000 / 60; long horas = tempo / 60; long minutos = tempo % 60; if (horas > 0) { preco = horas * 3; } if (minutos > 0) { preco += 3; } 98 99 Persistindo dados com SQLite 18 19 return preco; } Execute novamente, o aplicativo agora está salvando, consultando e atualizando um valet, aproveite para fechar e abrir o aplicativo para verificar se os dados foram salvos. Figura 6.2.1 - Saída do veículo. Neste exemplo não foi necessário remover um registro do SQLite, mas se fosse necessário o código seria assim: 1 2 3 4 5 6 public void remover(Long idValet) { SQLiteDatabase db = getWritableDatabase(); String where [] = new String[] { String.valueOf(idValet) }; db.delete("Valet", "_id = ?", where); db.close(); } O método remover recebe qual o id do valet deve ser removido e usa o método delete do db para executar a operação no SQLite. Persistindo dados com SQLite 100 6.3 Resumo Neste capítulo foi implementado a persistência dos veículos no aplicativo de Valets utilizando o SQLite. No próximo capítulo veremos como criar uma lista personalizada, que permite organizar os componentes nela da forma que quisermos. 6.4 Exercícios Exercício 1 - Altere o exercício de Tarefas criado no exercício 1 do capítulo 5 para também utilizar a persistência dos dados com o SQLite. 7. ListView personalizada Neste capítulo vamos criar um aplicativo chamado Meus Produtos, a ideia é mostrar uma ListView personalizada com vários produtos e conforme for alterando a quantidade de cada item da lista já atualiza o valor total. Figura 7.1 - Meus Produtos. Na criação do projeto utilize uma Empty Activity, como mostrado nos primeiros capítulos. Após criar o projeto, vamos começar criando uma classe para representar o Produto, neste exemplo será utilizado doces como produto, mas você pode modificar e utilizar qualquer outro produto: 1 2 3 4 5 6 7 8 9 10 11 package com.leanpub.googleandroid.meusprodutos; public class Produto { private String nome; private int imagem; private Double preco; private int quantidade; public Produto(String nome, int imagem, Double preco) { this.nome = nome; this.imagem = imagem; ListView personalizada 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 102 this.preco = preco; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public int getImagem() { return imagem; } public void setImagem(int imagem) { this.imagem = imagem; } public Double getPreco() { return preco; } public void setPreco(Double preco) { this.preco = preco; } public int getQuantidade() { return quantidade; } public void setQuantidade(int quantidade) { this.quantidade = quantidade; } public double getPrecoTotal() { return preco * quantidade; } } A imagem será representada como int no objeto Produto, porque será utilizado a referência interna das figuras que estão na pasta drawable no projeto. ListView personalizada 103 Neste exemplo foi utilizado as figuras brigadeiros.png, cupcake.png e mousse_maracuja.png que estão disponíveis no GitHub¹. Faça o download dessa figuras e coloque na pasta res → drawable. 7.1 Criando o layout da activity_main O layout da tela activity_main.xml terá uma ListView e as TextView para mostrar o valor total. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.leanpub.googleandroid.meusprodutos.MainActivity"> <ListView android:id="@+id/produtos" android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginBottom="8dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toTopOf="@+id/fundo" app:layout_constraintTop_toTopOf="parent" /> <android.support.constraint.ConstraintLayout android:id="@+id/fundo" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent"> <TextView android:id="@+id/total" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginStart="8dp" ¹https://github.com/rafaelsakurai/livro-google-android ListView personalizada 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 android:text="@string/total" android:textSize="30sp" app:layout_constraintStart_toStartOf="parent" /> <TextView android:id="@+id/valorTotal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_toEndOf="@id/total" android:text="R$ 0,00" android:textSize="30sp" app:layout_constraintEnd_toStartOf="parent" /> </android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout> Adicione no arquivo strings.xml a nova String: 1 <string name="total">Total:</string> O layout da activity_main.xml ficará como mostrado na Figura 7.1.1. 104 105 ListView personalizada Figura 7.1.1 - Layout da activity_main. 7.2 Criando o layout da ListView Para personalizar a ListView criamos um novo layout que representa como cada linha deve ser apresentada. Clique com o botão direito do mouse na pasta layout, escolha no menu New a opção Layout resource file, como mostrado na Figura 7.2.1. Figura 7.2.1 - Criando um novo Layout. 106 ListView personalizada Na tela New Resource File defina o File name como produto e o Root element como LinearLayout, como mostrado na Figura 2.2.2. Figura 7.2.2 - Criando um novo Layout. Cada linha da lista terá uma ImageView que representa a figura do produto, um TextView com o nome do produto, um TextView com o preço e um EditText para informar a quantidade do produto, como mostrado na figura 7.2.3: Figura 7.2.3 - Layout que representa uma linha da ListView. O layout ficou assim: ListView personalizada 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/imagem" android:layout_width="0dp" android:layout_height="80dp" android:src="@drawable/brigadeiros" android:layout_marginRight="8dp" android:layout_weight="1"/> <LinearLayout android:id="@+id/descricao" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:layout_marginRight="8dp" android:layout_weight="2" android:layout_gravity="center_vertical"> <TextView android:id="@+id/nome" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Brigadeiro" android:textSize="20sp"/> <TextView android:id="@+id/preco" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="R$ 0,90" android:textSize="15sp"/> </LinearLayout> <EditText android:id="@+id/quantidade" android:layout_width="0dp" android:layout_height="80dp" 107 ListView personalizada 43 44 45 46 108 android:inputType="number" android:textSize="20sp" android:layout_weight="1"/> </LinearLayout> Para alinhar os componentes um ao lado do outro usando tamanhos proporcionais está sendo utilizado a propriedade android:layout_weight. Esta propriedade calculo o tamanho dos componentes com base no seu peso, neste exemplo para a ImageView e EditText foi utilizado peso 1 e para o RelativeLayout que está no meio foi utilizado peso 2, portanto o peso total é 4, então a organização será respectivamente 1/4, 1/4 e 2/4 do comprimento da tela, não importando o tamanho da tela. 7.3 Adicionando o comportamento da ListView Até agora, foi montado o layout da tela e da ListView, então vamos criar o comportamento. Para representar os itens da ListView vamos criar um Adapter dos produtos, para isso vamos criar a classe ProdutoArrayAdapter que herda de android.widget.ArrayAdapter, como mostrado a seguir: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.leanpub.googleandroid.meusprodutos; import import import import import import import import import import import android.content.Context; android.text.Editable; android.text.TextWatcher; android.util.Log; android.view.LayoutInflater; android.view.View; android.view.ViewGroup; android.widget.ArrayAdapter; android.widget.EditText; android.widget.ImageView; android.widget.TextView; import java.util.List; public class ProdutoArrayAdapter extends ArrayAdapter<Produto> { private Context context; private List<Produto> produtos; public ProdutoArrayAdapter(Context context, int resource, List<Produto> objects) { super(context, resource, objects); this.context = context; ListView personalizada 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 this.produtos = objects; } @Override public View getView(final int position, View convertView, final ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View linha = inflater.inflate(R.layout.produto, parent, false); TextView nome = (TextView) linha.findViewById(R.id.nome); TextView preco = (TextView) linha.findViewById(R.id.preco); ImageView imagem = (ImageView) linha.findViewById(R.id.imagem); EditText qtd = (EditText) linha.findViewById(R.id.quantidade); qtd.addTextChangedListener(new TextWatcher() { Produto p = produtos.get(position); @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { try { p.setQuantidade(Integer.valueOf(s.toString())); } catch (NumberFormatException nfe) { p.setQuantidade(0); } atualizar(((TextView) parent.getRootView() .findViewById(R.id.valorTotal)), produtos); } }); 109 ListView personalizada 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 110 Produto p = produtos.get(position); nome.setText(p.getNome()); preco.setText("R$: " + p.getPreco()); imagem.setImageResource(p.getImagem()); return linha; } public void atualizar(TextView valorTotal, List<Produto> produtos) { double total = 0; for(Produto p : produtos) { total += p.getPrecoTotal(); } valorTotal.setText("R$" + total); } } Na classe ProdutoArrayAdapter foi criado: • Um construtor public ProdutoArrayAdapter(Context context, int resource, List<Produto> objects) para receber principalmente o context que servirá para definir o layout de cada linha da ListView e também a List<Produto> que será carregado; • Sobrescrevemos o método public View getView(final int position, View convertView, final ViewGroup parent) que serve para pegar cada linha da ListView; Para criar uma linha do ListView usamos o código: 1 2 3 LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View linha = inflater.inflate(R.layout.produto, parent, false); Nele dizemos para criar um LayoutInflater que serve para carregar um layout em tempo de execução e através dele conseguimos criar uma View com o layout do produto.xml que representa uma linha da ListView. No campo quantidade que é um EditText foi adicionado um TextChangedListener que serve para identificar quando seu valor for alterado. ListView personalizada 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 111 EditText qtd = (EditText) linha.findViewById(R.id.quantidade); qtd.addTextChangedListener(new TextWatcher() { Produto p = produtos.get(position); @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { try { p.setQuantidade(Integer.valueOf(s.toString())); } catch (NumberFormatException nfe) { p.setQuantidade(0); } atualizar(((TextView) parent.getRootView().findViewById(R.id.valorTotal)), produtos); } }); Precisamos implementar três métodos da TextWatcher, mas neste exemplo só precisamos usar o método public void afterTextChanged(Editable s) que serve para executar alguma lógica após o usuário informar a quantidade. Então, nesse exemplo vamos chamar o método public void atualizar(TextView valorTotal, List<Produto> produtos) que foi implementado para calcular o valor total com base nas quantidades de cada produto escolhido e depois atualiza o TextView que mostra o valor total na tela. Agora só falta implementar a Activity como mostrado a seguir: ListView personalizada 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 112 package com.leanpub.googleandroid.meusprodutos; import import import import import import import import import import android.content.Intent; android.support.v7.app.AppCompatActivity; android.os.Bundle; android.view.Menu; android.view.MenuItem; android.view.View; android.widget.ListView; android.widget.TextView; java.util.ArrayList; java.util.List; public class MainActivity extends AppCompatActivity { private List<Produto> produtos = new ArrayList(); private ProdutoArrayAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); produtos.add(new Produto("Brigadeiro", R.drawable.brigadeiros, 0.90)); produtos.add(new Produto("Cupcake", R.drawable.cupcake, 2.50)); produtos.add(new Produto("Mousse", R.drawable.mousse_maracuja, 1.50)); adapter = new ProdutoArrayAdapter(this, 0, produtos); ((ListView) findViewById(R.id.produtos)).setAdapter(adapter); ((TextView) findViewById(R.id.valorTotal)).setText("R$0.0"); } } Na MainActivity foi criado uma lista dos produtos que serão apresentados e também um ProdutoArrayAdapter que foi adicionado na ListView da tela. Pronto, execute o aplicativo e preencha alguns valores nas quantidades dos produtos e veja o calculo automático do valor total. 7.4 Resumo Nesse capítulo abordamos como criar uma ListView personalizada, a ideia principal é que cada linha da lista é representado por um arquivo xml de layout, portanto podemos criar qualquer tipo ListView personalizada 113 de layout personalizado. 7.5 Exercícios Exercício 1 - Altere o exemplo apresentado para salvar usando o SQLite as vendas. Exercício 2 - Continuando o exercício 1, adicione mais uma tela para consultar todas as vendas realizadas. 8. Chamando Web Service REST Vamos continuar o aplicativo Meus Produtos criado no capítulo anterior. Até o momento criamos uma tela que mostra os produtos, mas todos os dados são fixos no programa. Na classe MainActivity temos a criação de três produtos que são apresentados na lista: 1 2 3 produtos.add(new Produto("Brigadeiro", R.drawable.brigadeiros, 0.90)); produtos.add(new Produto("Cupcake", R.drawable.cupcake, 2.50)); produtos.add(new Produto("Mousse", R.drawable.mousse_maracuja, 1.50)); Nesse capítulo vamos armazenar os dados dos produtos em um Web Service REST. Figura 8.1 - Meus Produtos. 8.1 Introdução sobre Web Service REST Web Service é uma forma de integração entre aplicações que permite a troca de dados independente da linguagem de programação de cada aplicação. Nesse exemplo será utilizado o OpenWS (https://openws.org/¹) que é uma plataforma quer permite armazenar objetos JSON de forma bem simples e gratuita utilizando Web Services REST. ¹https://openws.org/ 115 Chamando Web Service REST Figura 8.1.1 - OpenWS - https://openws.org/. Esse site é útil para criar um protótipos rápido podendo armazenar dinamicamente os dados do app desenvolvido em um Web Service REST. Só um detalhe, todos os objetos armazenados nesse site são públicos, então qualquer pessoa pode consultar, adicionar ou alterar as informações. O OpenWS será utilizado para armazenar os produtos cadastrados no aplicativo Meus Produtos. Nele será salvo objetos no formato JSON contendo os dados dos produtos que o app deve apresentar. 8.2 Alterando o aplicativo Meus Produtos No aplicativo Meus Produtos que criado no capítulo anterior há uma tela para mostrar e calcular o preço total dos produtos. 116 Chamando Web Service REST Figura 8.2.1 - Meus Produtos. Nessa tela vamos adicionar um menu no canto superior direito. Clique com o botão direito do mouse em res e escolha no menu New a opção Directory, como mostrado na Figura 8.2.2. Figura 8.2.2 - Criando um novo diretório. Na tela New Directory defina o nome como menu, como mostrado na Figura 8.2.3. 117 Chamando Web Service REST Figura 8.2.3 - Criando um novo diretório. Clique com o botão direito na pasta menu que acabamos de criar e escolha no menu New a opção Menu resource file, como mostrado na Figura 8.2.4. Figura 8.2.4 - Criando um novo menu. Na tela New Resource File defina o File name como menu_main, como mostrado na Figura 8.2.5. 118 Chamando Web Service REST Figura 8.2.5 - Criando um novo menu. No menu_main, altere seu código para incluir um novo item de menu, como mostrado a seguir: 1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/menuNovoProduto" android:title="@string/novo_produto" android:orderInCategory="1" app:showAsAction="never" android:onClick="novoProduto"/> </menu> Este menu é apresentado quando o usuário clica no símbolo menu que tem no canto superior da aplicação. Nele definimos o id, título, ordem que deve aparecer no menu, o valor never na opção app:showAsAction indica para abrir nesse menu chamado Modo de ação contextual, e o onClick indica qual método deve ser executado quando acionar este item do menu. Figura 8.6 - Menu de novo produto. No arquivo strings.xml adicione a string: Chamando Web Service REST 1 119 <string name="novo_produto">Novo Produto</string> Na MainActivity.java adicione os imports para utilizar o menu: 1 2 import android.view.Menu; import android.view.MenuItem; Após o método onCreate, sobrescreva o método onCreateOptionsMenu, esse método será responsável por abrir o menu no canto superior direito da tela do app. 1 2 3 4 5 @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } Ainda na MainActivity.java adicione o método novoProduto, esse método será responsável por abrir a nova tela: 1 2 3 public void novoProduto(MenuItem item) { \\TODO - Ainda vamos chamar a tela de produto aqui. } O método novoProduto ainda não está funcional, mas logo implementaremos o código para chamar a outra tela. 8.3 Criando a tela de cadastro de produto Vamos criar a activity que controla os produtos, para isso crie uma classe chamada ProdutoActivity com o seguinte código: 120 Chamando Web Service REST 1 2 3 4 5 6 7 8 9 10 11 12 package com.leanpub.googleandroid.meusprodutos; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; public class ProdutoActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_produto); } } Na ProdutoActivity está apenas definindo que é uma Activity e que controla um layout chamado activity_produto.xml. Então, crie o layout activity_produto.xml para a tela de cadastro de um novo produto como mostrado na Figura 8.3.1: Figura 8.3.1 - Tela de novo produto. O código do layout activity_produto.xml fica da seguinte forma: 1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <ProgressBar android:id="@+id/progresso" android:layout_width="match_parent" Chamando Web Service REST 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 android:layout_height="wrap_content" android:visibility="gone" app:layout_constraintTop_toTopOf="parent"/> <TextView android:id="@+id/textoNome" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/nome" android:textSize="30sp" android:textAppearance="@style/Base.TextAppearance.AppCompat.Large" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/nome" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" app:layout_constraintTop_toBottomOf="@+id/textoNome" /> <TextView android:id="@+id/textoPreco" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/preco" android:textSize="30sp" android:textAppearance="@style/Base.TextAppearance.AppCompat.Large" app:layout_constraintTop_toBottomOf="@+id/nome"/> <EditText android:id="@+id/preco" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="numberDecimal" app:layout_constraintTop_toBottomOf="@+id/textoPreco"/> <TextView android:id="@+id/textoImagem" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/imagem" 121 Chamando Web Service REST 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 122 android:textSize="30sp" android:textAppearance="@style/Base.TextAppearance.AppCompat.Large" app:layout_constraintTop_toBottomOf="@+id/preco"/> <ImageView android:id="@+id/imagem" android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="center_horizontal" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toTopOf="@+id/button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textoImagem" /> <Button android:id="@+id/button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:onClick="obterImagem" android:text="@string/procurar_imagem" android:textSize="30sp" app:layout_constraintBottom_toTopOf="@id/salvar" /> <Button android:id="@+id/salvar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:onClick="salvar" android:text="@string/salvar" android:textSize="30sp" app:layout_constraintBottom_toBottomOf="parent" /> </android.support.constraint.ConstraintLayout> No começo da activity_produto.xml, adicionamos um componente chamado ProgressBar, ele serve para mostrar o símbolo de carregando ou uma barra de progresso. Nessa definição, a propriedade visibility definida como gone vai deixar esse componente invisível, mas quando Chamando Web Service REST 123 consultarmos os dados do Web Service REST, utilizaremos esse ProgressBar para sinalizar que estamos carregando os dados da tela. 1 2 3 4 5 6 <ProgressBar android:id="@+id/progresso" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" app:layout_constraintTop_toTopOf="parent"/> No arquivo strings.xml adicione as strings: 1 2 3 4 5 <string <string <string <string <string name="nome">Nome</string> name="preco">Preço</string> name="imagem">Imagem</string> name="procurar_imagem">Procurar Imagem</string> name="salvar">Salvar</string> E para registrar a nova Activity altere o arquivo AndroidManifest.xml incluindo a seguinte tag <activity android:name=".ProdutoActivity"/>, como mostrado a seguir: 1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="utf-8"?> <manifest ... > <application ... > <activity ...> ... </activity> <activity android:name=".ProdutoActivity"/> </application> </manifest> Nesse momento se executar o aplicativo já tem a navegação entre as telas. O comportamento será: na tela inicial é apresentado todos os produtos cadastrados e na tela de novo produto enviaremos os dados do produto para o OpenWS. 8.4 Adicionando arquivos no emulador do Android Vamos deixar o aplicativo um pouco mais realista, ao cadastrar um novo produto será escolhido uma imagem do celular, nesse exemplo a imagem será obtida do emulador. 124 Chamando Web Service REST Para adicionar arquivos no emulador, primeiro inicie o emulador e depois no canto inferior direito da barra lateral, acesse a aba Device File Explorer, como mostrado na Figura 8.4.1. Figura 8.4.1 - Abrir o Device File Explorer. No Device File Explorer acesse o diretório storage � self � primary � DCIM, clique com o botão direito do mouse e escolha Upload, como mostrado na Figura 8.4.2: 125 Chamando Web Service REST Figura 8.4.2 - Adicionando arquivos no emulador. Escolha a imagem dos três produtos que serão usados no cadastro do produto, nesse exemplo foram adicionando os arquivos brigadeiro.png, cupcake.png e mousse_maracuja.png, como mostrado na Figura 8.4.2. Figura 8.4.3 - Figuras adicionadas no emulador. Pronto as imagens já estarão disponíveis para serem acessadas no emulador. 8.5 Integração com Web Service REST Agora vamos fazer o aplicativo usar o Web Service REST disponibilizado pelo OpenWS. Para que a aplicação possa realizar uma conexão HTTP, ela precisa ter acesso a Internet, e para obter as imagens da galeria é necessário ter uma permissão de escrita no storage. Para permitir isso edite o arquivo AndroidManifest.xml, adicionando as permissões antes da declaração da tag <application>: 1 2 <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> O Web Service REST será usado para enviar e obter dados no formato JavaScript Object Notation (JSON), para facilitar a criação de objetos JSON e converter para objetos Java, vamos utilizar uma biblioteca do Google chamado GSon. Chamando Web Service REST 126 Os projetos Android utilizam a ferramenta Gradle para gerenciar suas dependências, isso significa que podemos informar para o Gradle que agora o projeto precisa usar a biblioteca GSon. Para isso edite o arquivo Gradle Scripts → build.gradle (Module: app), esse arquivo contém as configurações para compilar o projeto Android, na propriedade dependencies inclua também: compile 'com.google.code.gson:gson:2.8.0' Essa linha indica que o projeto tem uma dependência com o GSon na versão 2.8.0. Altere a classe Produto como mostrado a seguir: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.leanpub.googleandroid.meusprodutos; public class Produto { private String _id; private String nome; private String imagem; private Double preco; private int quantidade; public Produto(String id, String nome, String imagem, Double preco) { this._id = id; this.nome = nome; this.imagem = imagem; this.preco = preco; } public Produto(String nome, String imagem, Double preco) { this.nome = nome; this.imagem = imagem; this.preco = preco; } public String get_id() { return _id; } public void set_id(String _id) { this._id = _id; } public String getNome() { return nome; } Chamando Web Service REST 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 127 public void setNome(String nome) { this.nome = nome; } public String getImagem() { return imagem; } public void setImagem(String imagem) { this.imagem = imagem; } public Double getPreco() { return preco; } public void setPreco(Double preco) { this.preco = preco; } public int getQuantidade() { return quantidade; } public void setQuantidade(int quantidade) { this.quantidade = quantidade; } public double getPrecoTotal() { return preco * quantidade; } } Agora estamos definindo uma imagem como uma String, isso porque a imagem será enviada para o Web Service e será salva no formato de texto. Para utilizar o Web Service do OpenWS crie uma nova classe chamada ProdutoService, a seguir será apresentado todo o código e logo a seguir será explicado passo a passo: Chamando Web Service REST 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.leanpub.googleandroid.meusprodutos; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import import import import import import import import import import import import java.io.BufferedInputStream; java.io.BufferedOutputStream; java.io.BufferedWriter; java.io.InputStream; java.io.OutputStream; java.io.OutputStreamWriter; java.io.Writer; java.net.HttpURLConnection; java.net.URL; java.util.ArrayList; java.util.List; java.util.Scanner; public class ProdutoService { private static final String URL_OPEN_WS = "https://openws.org/api/collections/"; private static final String MINHA_COLECAO = "meusprodutos"; public List<Produto> getAll() { List<Produto> produtos = new ArrayList(); HttpURLConnection urlConnection = null; try { URL url = new URL(URL_OPEN_WS + MINHA_COLECAO); urlConnection = (HttpURLConnection) url.openConnection(); InputStream in = new BufferedInputStream(urlConnection.getInputStream()); Scanner s = new Scanner(in); String conteudo = s.useDelimiter("\\A").next(); Gson gson = new Gson(); produtos = gson.fromJson(conteudo, new TypeToken<List<Produto>>(){} .getType()); } catch (Exception e) { throw new RuntimeException(e); } finally { urlConnection.disconnect(); 128 Chamando Web Service REST 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 } return produtos; } public void post(Produto produto) { HttpURLConnection urlConnection = null; try { URL url = new URL(URL_OPEN_WS + MINHA_COLECAO); urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setDoOutput(true); urlConnection.setRequestMethod("POST"); urlConnection.setRequestProperty("Content-Type", "application/json"); urlConnection.setRequestProperty("Accept", "application/json"); OutputStream out = new BufferedOutputStream( urlConnection.getOutputStream()); Writer w = new BufferedWriter(new OutputStreamWriter(out)); Gson gson = new Gson(); String json = gson.toJson(produto); w.write(json); w.close(); InputStream in = new BufferedInputStream(urlConnection.getInputStream()); Scanner s = new Scanner(in); String conteudo = s.nextLine(); in.close(); } catch (Exception e) { throw new RuntimeException(e); } finally { urlConnection.disconnect(); } } public void delete(String id) { HttpURLConnection urlConnection = null; try { URL url = new URL(URL_OPEN_WS + MINHA_COLECAO + "/" + id); urlConnection = (HttpURLConnection) url.openConnection(); 129 Chamando Web Service REST 85 86 87 88 89 90 91 92 93 94 130 urlConnection.setRequestMethod("DELETE"); InputStream in = new BufferedInputStream(urlConnection.getInputStream()); } catch (Exception e) { throw new RuntimeException(e); } finally { urlConnection.disconnect(); } } } Foram declarados dois atributos, neles temos: a URL_OPEN_WS de acesso ao Web Service do OpenWS, a MINHA_COLECAO que é o nome da coleção que o OpenWS vai criar quando enviar os dados de um produto pelo App. Note que no exemplo foi definido o valor da MINHA_COLECAO como meusprodutos, quando você for definir o nome para o seu projeto, sugiro colocar algo como meusprodutos_seunome porque senão você acessará uma coleção igual ao desse exemplo e já trará os objetos que cadastrei. 1 2 private static final String URL_OPEN_WS = "https://openws.org/api/collections/"; private static final String MINHA_COLECAO = "meusprodutos"; O método getAll() foi criado para consultar todos os produtos que estão salvos no OpenWS, nele primeiro criamos uma conexão com a url e coleção do OpenWS: 1 2 URL url = new URL(URL + MINHA_COLECAO); urlConnection = (HttpURLConnection) url.openConnection(); Depois executamos a chamada da URL e guardamos o retorno na variável resultado: 1 2 3 InputStream in = new BufferedInputStream(urlConnection.getInputStream()); Scanner s = new Scanner(in); String resultado = s.useDelimiter("\\A").next(); Esse tipo de requisição está usando o método GET do HTTP para chamar o Web Service disponibilizado pelo OpenWS, o mesmo comportamento pode ser simulado por meio de um navegador, basta abrir o navegador e digitar na URL https://openws.org/api/collections/meusprodutos lembrando de trocar o valor meusprodutos pelo nome que você deu para a coleção. O resultado é um vetor de objetos JSON, então vamos utilizar a biblioteca GSon para converter essa String para uma lista de produtos: Chamando Web Service REST 1 2 131 Gson gson = new Gson(); produtos = gson.fromJson(resultado, new TypeToken<List<Produto>>(){}.getType()); Então, na primeira vez que chamarmos esse método não trará nada, mas após cadastrarmos algum produto no OpenWS será retorno os produtos cadastrados. A seguir foi criado o método post() que recebe um Produto como parâmetro e envia os dados para o OpenWS. Agora criamos uma URL com algumas configurações diferentes: 1 2 3 4 5 6 URL url = new URL(URL + MINHA_COLECAO); urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setDoOutput(true); urlConnection.setRequestMethod("POST"); urlConnection.setRequestProperty("Content-Type", "application/json"); urlConnection.setRequestProperty("Accept", "application/json"); Nessa URL é informado que a requisição deve usar o método POST do HTTP isso informa para o Web Service salvar os dados do JSON e também é informado que o conteúdo enviado é um JSON. A seguir é criado um Writer que é usado para enviar as informações para o Web Service. Aqui o GSon foi utilizado para converter um objeto do tipo Produto para JSON. 1 2 3 4 5 6 7 8 OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream()); Writer w = new BufferedWriter(new OutputStreamWriter(out)); Gson gson = new Gson(); String json = gson.toJson(produto); w.write(json); w.close(); E para finalizar é feita uma chamada para a URL, nessa chamada será retornado um JSON com os dados do produto que foi salvo no OpenWS. Também foi criado um método chamado delete() que recebe um id de produto que deve ser removido, mas esse método não foi usado no exemplo, apenas foi implementado para que você possa chamar esse método e limpar os dados do Web Service. Na classe ProdutoArrayAdapter foi alterado apenas a forma como será renderizado a imagem, no final do método getView altere o seguinte código: 1 imagem.setImageResource(p.getImagem()); Para esse: Chamando Web Service REST 1 2 3 132 byte[] byteArray = Base64.decode(p.getImagem(), Base64.DEFAULT) ; imagem.setImageBitmap(BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length)); Esse novo código está dizendo para pegar a String da imagem e decodificar no formado de um vetor de bytes e depois esse vetor é decodificado no formado de imagem bitmap e guardado no ImageView. A seguir temos o novo código da classe MainActivity e logo a seguir tem a explicação de cada método: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.leanpub.googleandroid.meusprodutos; import import import import import import import import import android.content.Intent; android.os.AsyncTask; android.support.v7.app.AppCompatActivity; android.os.Bundle; android.view.Menu; android.view.MenuItem; android.widget.ListView; android.widget.ProgressBar; android.widget.TextView; import java.util.List; public class MainActivity extends AppCompatActivity { private ProdutoService service = new ProdutoService(); private ProdutoArrayAdapter adapter; private ProgressBar progressBar; private void carregarDados() { new CarregarMeusProdutosTask().execute(); ((TextView) findViewById(R.id.valorTotal)).setText("R$ 0,00"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); progressBar = (ProgressBar) findViewById(R.id.progresso); carregarDados(); } @Override Chamando Web Service REST 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 133 protected void onResume() { super.onResume(); carregarDados(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } private class CarregarMeusProdutosTask extends AsyncTask<String, Void, List<Produto>> { @Override protected void onPreExecute() { progressBar.setVisibility(ProgressBar.VISIBLE); } @Override protected void onPostExecute(List<Produto> produtos) { if (produtos != null) { adapter = new ProdutoArrayAdapter(getBaseContext(), 0, produtos); ((ListView) findViewById(R.id.produtos)).setAdapter(adapter); } progressBar.setVisibility(ProgressBar.INVISIBLE); } @Override protected List<Produto> doInBackground(String... params) { return service.getAll(); } } public void novoProduto(MenuItem item) { startActivity(new Intent(this, ProdutoActivity.class)); } } O método carregarDados() executa uma tarefa para consultar os dados do Web Service e com o resultado recarregar a tela: Chamando Web Service REST 1 2 3 4 134 private void carregarDados() { new CarregarMeusProdutosTask().execute(); ((TextView) findViewById(R.id.valorTotal)).setText("R$ 0,00"); } Quando executamos uma requisição HTTP precisamos fazer com que isso execute em uma thread paralela, porque se a thread principal demorar muito o próprio sistema operacional do Android interrompe o aplicativo. A classe CarregarMeusProdutosTask foi declarada internamente dentro da classe MainActivity: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private class CarregarMeusProdutosTask extends AsyncTask<String, Void, List<Produto>> { @Override protected void onPreExecute() { progressBar.setVisibility(ProgressBar.VISIBLE); } @Override protected void onPostExecute(List<Produto> produtos) { if (produtos != null) { adapter = new ProdutoArrayAdapter(getBaseContext(), 0, produtos); ((ListView) findViewById(R.id.produtos)).setAdapter(adapter); } progressBar.setVisibility(ProgressBar.INVISIBLE); } @Override protected List<Produto> doInBackground(String... params) { return service.getAll(); } } Para executar a requisição em paralelo criamos a classe interna chamada CarregarMeusProdutosTask que estende a classe android.os.AsyncTask. E precisamos implementar os seguintes métodos: • onPreExecute() - Método executado antes do processamento da thread; • onPostExecute() - Método executado após o processamento da thread; • String[] doInBackground(String … params) - Método que é executado pela thread. Essa classe permite executar tarefas de forma assíncrona, então chamar o método execute(), apresentará o componente ProgressBar no método onPreExecute(), depois será consultado todos Chamando Web Service REST 135 os produtos e atualizado a ListView no método doInBackground() e para finalizar no método onPostExecute() o ProgressBar volta a ficar invisível. O método onCreate foi alterado para chamar o método carregarDados(): 1 2 3 4 5 6 7 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); progressBar = (ProgressBar) findViewById(R.id.progresso); carregarDados(); } O mesmo foi feito com o método onResume() que serve para atualizar essa tela após a criação de um produto. Porque será chamado a tela de novo produto, e após cadastrar o produto quando voltar para a tela principal o método onResume() será executado 1 2 3 4 5 6 @Override protected void onResume() { super.onResume(); carregarDados(); } E o método novoProduto(MenuItem item) é usado para chamar a tela de cadastro de novo produto quando clicar no menu. 1 2 3 public void novoProduto(MenuItem item) { startActivity(new Intent(this, ProdutoActivity.class)); } Por último altere a classe ProdutoActivity criada no inicio desse exemplo com o seguinte código: Chamando Web Service REST 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.leanpub.googleandroid.meusprodutos; import import import import import import import import import import import import import import import import import import import import android.Manifest; android.app.Activity; android.content.Intent; android.content.pm.PackageManager; android.database.Cursor; android.graphics.Bitmap; android.graphics.BitmapFactory; android.net.Uri; android.os.AsyncTask; android.os.Bundle; android.provider.MediaStore; android.support.v4.app.ActivityCompat; android.support.v4.content.ContextCompat; android.support.v7.app.AppCompatActivity; android.util.Base64; android.util.Log; android.view.View; android.widget.EditText; android.widget.ImageView; android.widget.ProgressBar; import java.io.ByteArrayOutputStream; public class ProdutoActivity extends AppCompatActivity { private ProdutoService service = new ProdutoService(); private Bitmap bmp; private String nome; private double preco; private String imagemURI; private ProgressBar progresso; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_produto); progresso = (ProgressBar) findViewById(R.id.progresso); } public void obterImagem(View view) { if (ContextCompat.checkSelfPermission(this, 136 Chamando Web Service REST 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { } else { ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE }, 1); } } Intent i = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(i, 1); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_OK) { imagemURI = data.getDataString(); Uri uri = data.getData(); String[] filePathColumn = { MediaStore.Images.Media.DATA }; Cursor cursor = getContentResolver().query(uri,filePathColumn, null, null, null); cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); String picturePath = cursor.getString(columnIndex); Log.d("picturePath", picturePath); bmp = BitmapFactory.decodeFile(picturePath); cursor.close(); ImageView imageView = (ImageView) findViewById(R.id.imagem); imageView.setImageURI(uri); } } 137 Chamando Web Service REST 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 public void salvar(View view) { nome = ((EditText) findViewById(R.id.nome)).getText().toString(); preco = Double.valueOf(((EditText) findViewById(R.id.preco)) .getText().toString()); new EnviarMeusProdutosTask().execute(); } private class EnviarMeusProdutosTask extends AsyncTask<String, Void, Void> { @Override protected void onPreExecute() { progresso.setVisibility(ProgressBar.VISIBLE); } @Override protected void onPostExecute(Void aVoid) { ((EditText) findViewById(R.id.nome)).setText(""); ((ImageView) findViewById(R.id.imagem)).setImageURI(null); ((EditText) findViewById(R.id.preco)).setText(""); imagemURI = ""; progresso.setVisibility(ProgressBar.INVISIBLE); } @Override protected Void doInBackground(String... params) { service.post(new Produto(nome, converterImagem(imagemURI), preco)); return null; } } private String converterImagem(String path) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); bmp.compress(Bitmap.CompressFormat.PNG, 100, stream); return Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP); } } O método obterImagem(View view) é chamado pelo botão PROCURAR IMAGEM. 138 139 Chamando Web Service REST 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public void obterImagem(View view) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { } else { ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE }, 1); } Intent i = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(i, 1); } Esse método cria uma Intent para selecionar uma figura da galeria, como mostrado na Figura 8.5.1: Figura 8.5.1 - Selecionar uma imagem. Após selecionar a figura será solicitado ao usuário se ele permite o acesso para escrita nos seus arquivos, isso é usado para gerar um bitmap da figura que ele escolheu, como mostrado na Figura 8.5.2. 140 Chamando Web Service REST Figura 8.5.2 - Solicitação de permissão de escrita. Depois o método onActivityResult(int requestCode, int resultCode, Intent data) será chamado passando como retorno um Intent contendo a imagem selecionada: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_OK) { imagemURI = data.getDataString(); Uri uri = data.getData(); String[] filePathColumn = { MediaStore.Images.Media.DATA }; Cursor cursor = getContentResolver().query(uri,filePathColumn, null, null, null); cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); String picturePath = cursor.getString(columnIndex); bmp = BitmapFactory.decodeFile(picturePath); cursor.close(); ImageView imageView = (ImageView) findViewById(R.id.imagem); imageView.setImageURI(uri); } } 141 Chamando Web Service REST Então, com a figura selecionada será atualizado o ImageView da tela, como mostrado na Figura 8.5.3: Figura 8.5.3 - Tela de novo produto. O método salvar(View view) será chamado pelo botão SALVAR para pegar os dados preenchidos pelo usuário e chamar uma tarefa para salvar o produto. 1 2 3 4 5 6 7 public void salvar(View view) { nome = ((EditText) findViewById(R.id.nome)).getText().toString(); preco = Double.valueOf(((EditText) findViewById(R.id.preco)) .getText().toString()); new EnviarMeusProdutosTask().execute(); } A classe EnviarMeusProdutosTask foi criada para chamar o método do web service que serve para salvar um novo produto. Chamando Web Service REST 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 142 private class EnviarMeusProdutosTask extends AsyncTask<String, Void, Void> { @Override protected void onPreExecute() { progresso.setVisibility(ProgressBar.VISIBLE); } @Override protected void onPostExecute(Void aVoid) { ((EditText) findViewById(R.id.nome)).setText(""); ((ImageView) findViewById(R.id.imagem)).setImageURI(null); ((EditText) findViewById(R.id.preco)).setText(""); imagemURI = ""; progresso.setVisibility(ProgressBar.INVISIBLE); } @Override protected Void doInBackground(String... params) { service.post(new Produto(nome, converterImagem(imagemURI), preco)); return null; } } Quando executar o EnviarMeusProdutosTask, no método onPreExecute() o ProgressBar fica visível, depois monta um Produto e passa para salvar no Web Service no método doInBackground() e para finalizar no método onPostExecute() o ProgressBar fica invisível. O método converterImagem(String path) foi criado para converter a imagem selecionada para uma String. 1 2 3 4 5 private String converterImagem() { ByteArrayOutputStream stream = new ByteArrayOutputStream(); bmp.compress(Bitmap.CompressFormat.PNG, 100, stream); return Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP); } Pronto, agora o aplicativo está integrado com o Web Service do OpenWS, teste as telas e cadastre alguns produtos. 143 Chamando Web Service REST Figura 8.5.4 - Meus Produtos. 8.6 Resumo Nesse capítulo foi apresentado o uso de Web Services em aplicativos Android, o objetivo não é focar apenas no uso do OpenWS, mas sim no consumo de um serviço que pode ser disponibilizado por outra plataforma ou até mesmo criado por você. Outro ponto importante nesse capítulo é o uso de tarefas que são executadas de forma assíncrona utilizando a AsyncTask, porque atrás dela podemos executar algumas tarefas que consomem um pouco mais de tempo, sem bloquear a thread principal da tela do aplicativo. Também mostramos como: adicionar ou obter arquivos no emulador usando o Device File Explorer, criar um Intent para obter uma figura da galeria, converter uma imagem para String usando Base64, adicionar uma biblioteca de dependência, converter objetos Java para GSon e vice versa, fazer requisições HTTP e apresentar uma carregando usando o componente ProgressBar. 8.7 Exercícios Exercício 1 - Altere o projeto Meus Produtos e salve as vendas realizadas usando o OpenWS, crie também uma tela para consultar todas as vendas realizadas. Exercício 2 - O que você achou de guardar a imagem como String junto com os dados do Produto? Será que isso consome muito trafego de dados? O que pode ser alterado para melhorar esse exemplo? 9. Action Bar Nesse capítulo será abordado o uso do Action Bar como um acesso rápido de algumas tela do aplicativo, também utilizaremos os componentes DatePicker, TimePicker e Spinner. O Action Bar é uma barra de ações que fica localizada na parte superior na tela. Figura 9.1 - Action Bar. Essa área é utilizada normalmente para: 1. Apresentar o nome ou em qual local do aplicativo o usuário está; 2. Ações muito usuais e de fácil acesso; 3. Navegação por meio de menu ou abas. Todos os projetos que criamos até o momento possuem uma Action Bar, mas basicamente estavamos utilizando apenas a opção de menu. O interessante é que todos os aplicativos de destaque (exceto jogos) no Google Play¹ utilizam Action Bar, é algo que facilita muito a usabilidade do aplicativo. Crie um novo projeto Android chamado Tá na hora. Este aplicativo servirá para anotar a quantidade de horas gastas em tarefas que o usuário executa, pode ser usado para registrar horas de trabalho, horas de exercícios, horas que passa jogando, enfim qualquer coisa, e vamos fazer um relatório de resumo das atividades. ¹https://play.google.com/store 145 Action Bar Figura 9.2 - Aplicativo Tá na hora. 9.1 Configurando o Action Bar Por padrão quando criamos um novo projeto, e escolhemos uma Empty Activity uma Activity é criada, mas o Action Bar apresenta apenas o nome do projeto, como mostrado na Figura 9.1.1. Figura 9.1.1 - Action Bar padrão. Então para criar um menu, faremos igual ao do capítulo anterior: 1) Crie o diretório menu dentro do diretório res; 2) Dentro do diretório menu crie um Menu Resource File chamado menu_main, adicione nele o seguinte conteúdo: 146 Action Bar 1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/adicionar" android:title="@string/adicionar" android:orderInCategory="1" app:showAsAction="ifRoom" /> </menu> O valor ifRoom da propriedade app:showAsAction, indica que se houver espaço este item deve aparecer no Action Bar. No arquivo strings.xml adicione a String: 1 <string name="adicionar">Adicionar</string> Para esse menu aparecer no Action Bar é necessário incluir o método onCreateOptionsMenu na MainActivity.java: 1 2 3 4 5 6 @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } O método public boolean onCreateOptionsMenu(Menu menu) é usado para carregar um menu que é o Action Bar. O menu é um arquivo XML de layout normalmente chamado menu_main.xml, sendo carregado durante a execução do aplicativo por meio do método getMenuInflater().inflate(R.menu.menu_main, menu);. O uso da anotação @Override indica que o método está sobrescrevendo o comportamento da classe pai nesse caso a classe android.app.Activity.java, também é usado para indicar que está implementando um método de uma interface. A Action Bar agora terá a aparência da Figura 9.1.2: Figura 9.1.2 - Action Bar com item de menu. 147 Action Bar É importante notar que podemos criar menus diferentes para as telas do aplicativo. Mas nem sempre um menu com o texto é a melhor visualmente, uma sugestão é o uso de ícones na Action Bar, porque também permitirá criar mais menus se for necessário. Para criar aplicativos melhores, a Google disponibilizou diversos ícones que seguem as boas práticas de Material Design² gratuitamente no GitHub³ e podem ser utilizados em projetos Web, Android, iOS, etc. No site https://github.com/google/material-design-icons/⁴ tem um botão para fazer Download ZIP com todos os ícones e em diversas fontes diferentes. Faça o download desse ZIP que utilizaremos alguns ícones no aplicativo criado nesse capítulo. Nesse exemplo, adicione na pasta drawable as imagens ic_alarm_add_white_48dp.png e ic_alarm_on_white_48dp.png que estão dentro da pasta material-design-icons-master → action → drawable-xxxhdpi. No item adicionar do menu informe o ícone android:icon="@drawable/ic_alarm_add_white_48dp", agora o Action Bar terá a aparência igual da Figura 9.1.3. Figura 9.1.3 - Action Bar com ícone no item de menu. Vamos continuar a criação do projeto e depois voltamos para configurar a ação deste item adicionar. 9.2 Criando o layout das telas Na tela inicial do activity_main.xml vamos adicionar uma lista que apresentará todas as tarefas registradas: ²https://www.google.com/design/spec/material-design/introduction.html ³https://github.com/google/material-design-icons/ ⁴https://github.com/google/material-design-icons/ Action Bar 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 148 <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.leanpub.googleandroid.tanahora.MainActivity"> <ListView android:id="@+id/lista" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> </android.support.constraint.ConstraintLayout> Para representar os itens da ListView crie um novo arquivo de layout chamado tarefa.xml com o seguinte código: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:id="@+id/linearLayout" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" 149 Action Bar 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/nome" app:layout_constraintHorizontal_weight="1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <TextView android:id="@+id/tempo" android:layout_width="match_parent" android:layout_height="match_parent" android:text="02:40" android:textSize="30sp" android:textStyle="bold" /> <TextView android:id="@+id/categoria" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="Trabalho" /> </LinearLayout> <TextView android:id="@+id/nome" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:gravity="top" android:text="Escrever o capítulo 09 do livro Google Android: Uma aborda\ gem prática." app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_weight="3" app:layout_constraintStart_toEndOf="@+id/linearLayout" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout> Nesse layout tem uma linha mostrando o tempo usado e a categoria da tarefa e outra linha mostrando o nome da tarefa, como mostrado na figura a seguir: 150 Action Bar Figura 9.2.1 - Representação da linha da ListView. E para cadastrar as tarefas crie um novo layout chamado activity_tarefa.xml com o seguinte código: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="8dp"> <TextView android:id="@+id/textoTarefa" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/tarefa" android:textSize="25sp" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/nome" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:hint="@string/hintTarefa" app:layout_constraintTop_toBottomOf="@id/textoTarefa" /> <TextView android:id="@+id/textoInicio" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/inicio" android:textSize="25sp" app:layout_constraintTop_toBottomOf="@id/nome"/> <LinearLayout android:id="@+id/inicio" Action Bar 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintTop_toBottomOf="@id/textoInicio"> <EditText android:id="@+id/dataInicio" android:layout_width="0dp" android:layout_height="wrap_content" android:inputType="date" android:hint="@string/hintDataInicio" android:layout_weight="2"/> <EditText android:id="@+id/horaInicio" android:layout_width="0dp" android:layout_height="wrap_content" android:inputType="time" android:hint="@string/hintHoraInicio" android:layout_weight="1"/> </LinearLayout> <TextView android:id="@+id/textoFim" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/fim" android:textSize="25sp" app:layout_constraintTop_toBottomOf="@id/inicio"/> <LinearLayout android:id="@+id/fim" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintTop_toBottomOf="@id/textoFim"> <EditText android:id="@+id/dataFim" android:layout_width="0dp" android:layout_height="wrap_content" android:inputType="date" android:hint="@string/hintDataFim" android:layout_weight="2"/> <EditText 151 Action Bar 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 152 android:id="@+id/horaFim" android:layout_width="0dp" android:layout_height="wrap_content" android:inputType="time" android:hint="@string/hintHoraFim" android:layout_weight="1"/> </LinearLayout> <TextView android:id="@+id/textoCategoria" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/categoria" android:textSize="25sp" app:layout_constraintTop_toBottomOf="@id/fim"/> <Spinner android:id="@+id/categoria" android:layout_width="match_parent" android:layout_height="wrap_content" android:entries="@array/categorias" app:layout_constraintTop_toBottomOf="@id/textoCategoria"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:onClick="salvar" android:text="@string/salvar" android:textSize="25sp" app:layout_constraintBottom_toBottomOf="parent" /> </android.support.constraint.ConstraintLayout> Nesse layout estamos definindo os campos para o usuário informar o nome da tarefa, data / hora de inicio, data / hora fim e uma categoria. Para auxiliar o preenchimento, nos EditText foi utilizado o android:hint como uma dica do que pode ser preenchido no campo. Para representar a categoria será utilizado o componente Spinner, nele usamos a propriedade android:entries="@array/categorias" para usar um conjunto fixo de itens representado por uma array de strings. No arquivo strings.xml adicione essas strings: 153 Action Bar 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <string name="tarefa">Tarefa</string> <string name="inicio">Inicio</string> <string name="fim">Fim</string> <string name="categoria">Categoria</string> <string name="salvar">Salvar</string> <array name="categorias"> <item>Diversão</item> <item>Estudos</item> <item>Trabalho</item> </array> <string name="hintTarefa">Descrição breve da tarefa.</string> <string name="hintDataInicio">28/02/2020</string> <string name="hintHoraInicio">09:00</string> <string name="hintDataFim">28/02/2020</string> <string name="hintHoraFim">10:30</string> O layout da tela de tarefa ficará como a Figura 9.2.2 a seguir: Figura 9.2.2 - Layout da tela de tarefas. Action Bar 9.3 Implementando o comportamento do aplicativo Para começar vamos criar uma classe que representa a tarefa: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.leanpub.googleandroid.tanahora; import java.util.Date; public class Tarefa { private Long id; private String nome; private Date inicio; private Date fim; private String categoria; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public Date getInicio() { return inicio; } public void setInicio(Date inicio) { this.inicio = inicio; } public Date getFim() { return fim; } 154 Action Bar 39 40 41 42 43 44 45 46 47 48 49 50 51 155 public void setFim(Date fim) { this.fim = fim; } public String getCategoria() { return categoria; } public void setCategoria(String categoria) { this.categoria = categoria; } } Um detalhe importante é que na classe Tarefa.java temos os campos inicio e fim que representam a data/hora da atividade, mas na tela essas informações serão separados em campos especificos para data e hora. Os dados serão salvos no SQLite, então vamos criar a classe TarefaDB: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.leanpub.googleandroid.tanahora; import import import import import android.content.ContentValues; android.content.Context; android.database.Cursor; android.database.sqlite.SQLiteDatabase; android.database.sqlite.SQLiteOpenHelper; import import import import java.text.DateFormat; java.text.SimpleDateFormat; java.util.ArrayList; java.util.List; public class TarefaDB extends SQLiteOpenHelper { private DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public TarefaDB(Context context) { super(context, "TarefaDB", null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE Tarefa (_id INTEGER PRIMARY KEY AUTOINCREMENT, nome\ Action Bar 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 TEXT, inicio TEXT, fim TEXT, categoria TEXT);"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } public Tarefa salvar(Tarefa tarefa) { SQLiteDatabase db = getWritableDatabase(); try { ContentValues values = new ContentValues(); values.put("nome", tarefa.getNome()); values.put("inicio", df.format(tarefa.getInicio())); values.put("fim", df.format(tarefa.getFim())); values.put("categoria", tarefa.getCategoria()); if (tarefa.getId() == null) { long id = db.insert("Tarefa", null, values); tarefa.setId(id); } else { String[] where = new String[]{String.valueOf(tarefa.getId())}; db.update("Tarefa", values, "_id = ?", where); } } finally { db.close(); } return tarefa; } public Tarefa consultarTarefaPorId(Long id) { Tarefa tarefa = new Tarefa(); SQLiteDatabase db = getReadableDatabase(); try { Cursor cursor = db.rawQuery("SELECT _id, nome, inicio, fim, categoria FROM Tarefa where _id = ?", new String[]{id.toString()}); cursor.moveToFirst(); tarefa.setId(cursor.getLong(0)); tarefa.setNome(cursor.getString(1)); 156 Action Bar 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 tarefa.setInicio(df.parse(cursor.getString(2))); tarefa.setFim(df.parse(cursor.getString(3))); tarefa.setCategoria(cursor.getString(4)); cursor.close(); } catch(Exception e) { e.printStackTrace(); } finally { db.close(); } return tarefa; } public List<Tarefa> consultarTarefas() { List<Tarefa> lista = new ArrayList<>(); SQLiteDatabase db = getReadableDatabase(); try { Cursor cursor = db.rawQuery("SELECT _id, nome, inicio, fim, categoria FROM Tarefa", null); cursor.moveToFirst(); for (int i = 0; i < cursor.getCount(); i++) { Tarefa tarefa = new Tarefa(); tarefa.setId(cursor.getLong(0)); tarefa.setNome(cursor.getString(1)); tarefa.setInicio(df.parse(cursor.getString(2))); tarefa.setFim(df.parse(cursor.getString(3))); tarefa.setCategoria(cursor.getString(4)); lista.add(tarefa); cursor.moveToNext(); } cursor.close(); } catch(Exception e) { e.printStackTrace(); } finally { db.close(); } return lista; } 157 Action Bar 108 158 } A classe TarefaDB tem métodos para salvar, alterar, consultarTarefaPorId e consultarTarefas. Note no método consultarTarefaPorId que a consulta recebe um parâmetro: 1 2 Cursor cursor = db.rawQuery("SELECT _id, nome, inicio, fim, categoria FROM Tarefa where _id = ?", new String[]{id.toString()}); Podemos passar diversos parâmetros para uma consulta por meio do uso das ? (interrogações), cada ? representa um parâmetro da consulta e seus valores são passados para a consulta através de um array de Strings. Agora vamos criar um adaptador para a ListView chamado TarefaAdapter: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.leanpub.googleandroid.tanahora; import import import import import import android.content.Context; android.view.LayoutInflater; android.view.View; android.view.ViewGroup; android.widget.ArrayAdapter; android.widget.TextView; import java.util.List; public class TarefaAdapter extends ArrayAdapter<Tarefa> { private Context context; private List<Tarefa> tarefas; public TarefaAdapter(Context context, int resource, List<Tarefa> objects) { super(context, resource, objects); this.context = context; this.tarefas = objects; } @Override public View getView(final int position, View convertView, final ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View linha = inflater.inflate(R.layout.tarefa, parent, false); Action Bar 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 159 TextView tempo = (TextView) linha.findViewById(R.id.tempo); TextView nome = (TextView) linha.findViewById(R.id.nome); TextView categoria = (TextView) linha.findViewById(R.id.categoria); Tarefa p = tarefas.get(position); long time = (p.getFim().getTime() - p.getInicio().getTime()) / 1000 / 60; long minutos = time % 60; long horas = time / 60; tempo.setText((horas < 10 ? "0" + horas : horas) + ":" + (minutos < 10 ? "0" + minutos : minutos)); nome.setText(p.getNome()); categoria.setText(p.getCategoria()); return linha; } } Na TarefaAdapter estamos simplesmente recebendo uma lista de objetos tarefas e populando a ListView com o layout criado na tarefa.xml. Agora vamos criar uma activity para controlar a tela de cadastro de tarefa, para isso crie a classe TarefaActivity: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.leanpub.googleandroid.tanahora; import import import import import import import import import android.app.DatePickerDialog; android.app.TimePickerDialog; android.os.Bundle; android.support.v7.app.AppCompatActivity; android.view.View; android.widget.DatePicker; android.widget.EditText; android.widget.Spinner; android.widget.TimePicker; import import import import java.text.DateFormat; java.text.SimpleDateFormat; java.util.Calendar; java.util.Date; public class TarefaActivity extends AppCompatActivity { 160 Action Bar 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 private private private private private DateFormat dhf = new SimpleDateFormat("dd/MM/yyyy HH:mm"); DateFormat df = new SimpleDateFormat("dd/MM/yyyy"); DateFormat hf = new SimpleDateFormat("HH:mm"); Long tarefaId; TarefaDB db; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_tarefa); db = new TarefaDB(this); if (getIntent().getExtras() != null) { tarefaId = getIntent().getExtras().getLong("id"); } Calendar inicio = Calendar.getInstance(); Calendar fim = Calendar.getInstance(); if (tarefaId == null) { fim.set(Calendar.HOUR_OF_DAY, fim.get(Calendar.HOUR_OF_DAY) + 1); } else { TarefaDB db = new TarefaDB(this); Tarefa tarefa = db.consultarTarefaPorId(tarefaId); ((EditText) findViewById(R.id.nome)).setText(tarefa.getNome()); inicio.setTime(tarefa.getInicio()); fim.setTime(tarefa.getFim()); Spinner categoria = ((Spinner) findViewById(R.id.categoria)); int pos = 0; String[] categorias = getResources().getStringArray(R.array.categorias); for (int i = 0; i < categorias.length; i++) { String s = categorias[i]; if (s.equals(tarefa.getCategoria())) { pos = i; break; } } categoria.setSelection(pos); } adicionarDatePicker((EditText) findViewById(R.id.dataInicio), inicio); Action Bar 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 adicionarDatePicker((EditText) findViewById(R.id.dataFim), fim); adicionarTimePicker((EditText) findViewById(R.id.horaInicio), inicio); adicionarTimePicker((EditText) findViewById(R.id.horaFim), fim); } private void adicionarDatePicker(final EditText edit, final Calendar date) { edit.setText(df.format(date.getTime())); final DatePickerDialog.OnDateSetListener dpd = new DatePickerDialog.OnDateSetListener() { @Override public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { date.set(Calendar.YEAR, year); date.set(Calendar.MONTH, monthOfYear); date.set(Calendar.DAY_OF_MONTH, dayOfMonth); edit.setText(df.format(date.getTime())); } }; edit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new DatePickerDialog(TarefaActivity.this, dpd, date.get(Calendar.YEAR), date.get(Calendar.MONTH), date.get(Calendar.DAY_OF_MONTH)).show(); } }); } private void adicionarTimePicker(final EditText edit, final Calendar date) { edit.setText(hf.format(date.getTime())); final TimePickerDialog.OnTimeSetListener tpd = new TimePickerDialog.OnTimeSetListener(){ @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { date.set(Calendar.HOUR_OF_DAY, hourOfDay); date.set(Calendar.MINUTE, minute); edit.setText(hf.format(date.getTime())); } }; 161 Action Bar 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 edit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new TimePickerDialog(TarefaActivity.this, tpd, date.get(Calendar.HOUR_OF_DAY), date.get(Calendar.MINUTE) , true).show(); } }); } public void salvar(View view) { Date inicio; Date fim; try{ inicio = dhf.parse( ((EditText) findViewById(R.id.dataInicio)).getText().toString() + " " + ((EditText) findViewById(R.id.horaInicio)).getText().toString()); fim = dhf.parse( ((EditText) findViewById(R.id.dataFim)).getText().toString() + " " + ((EditText) findViewById(R.id.horaFim)).getText().toString()); } catch(Exception e) { inicio = new Date(); fim = new Date(); } Tarefa tarefa = new Tarefa(); tarefa.setId(tarefaId); tarefa.setNome(((EditText) findViewById(R.id.nome)).getText().toString()); tarefa.setInicio(inicio); tarefa.setFim(fim); tarefa.setCategoria((String) ((Spinner) findViewById(R.id.categoria)) .getSelectedItem()); db.salvar(tarefa); finish(); } } Também adicione a TarefaActivity no AndroidManifest: 162 Action Bar 1 163 <activity android:name=".TarefaActivity"/> No método onCreate verificamos se ao chamar essa activity está sendo enviado um id da tarefa, isso é usado para quando receber um id, então consulta os dados da tarefa porque está querendo alterar os dados de uma tarefa já previamente criada. Para obter e selecionar um item do Spinner usamos o método setSelection passando a posição do elemento selecionado: 1 2 Spinner categoria = ((Spinner) findViewById(R.id.categoria)); categoria.setSelection(pos); Lembrando que a posição do primeiro elemento é zero (0). Para obter o array de strings declarado no arquivo strings.xml usamos o seguinte código: 1 String[] categorias = getResources().getStringArray(R.array.categorias); O método adicionarDatePicker implementa uma caixa de dialógo que permite selecionar uma data quando clicar no EditText da data de inicio e fim da tarefa. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private void adicionarDatePicker(final EditText edit, final Calendar date) { edit.setText(df.format(date.getTime())); final DatePickerDialog.OnDateSetListener dpd = new DatePickerDialog.OnDateSetListener() { @Override public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { date.set(Calendar.YEAR, year); date.set(Calendar.MONTH, monthOfYear); date.set(Calendar.DAY_OF_MONTH, dayOfMonth); edit.setText(df.format(date.getTime())); } }; edit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new DatePickerDialog(TarefaActivity.this, dpd, date.get(Calendar.YEAR), date.get(Calendar.MONTH), date.get(Calendar.DAY_OF_MONTH)).show(); } }); } Action Bar 164 O método adicionarDatePicker recebe um EditText que guarda o valor da data e também um Calendar que possui e guarda a data que é apresentada no DatePicker, o uso do Calendar é apenas para facilitar a separação dos campos dia, mês e ano. O método adicionarTimePicker implementa uma caixa de dialogo que permite selecionar uma hora quando clicar no EditText da hora de inicio e fim da tarefa. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void adicionarTimePicker(final EditText edit, final Calendar date) { edit.setText(hf.format(date.getTime())); final TimePickerDialog.OnTimeSetListener tpd = new TimePickerDialog.OnTimeSetListener(){ @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { date.set(Calendar.HOUR_OF_DAY, hourOfDay); date.set(Calendar.MINUTE, minute); edit.setText(hf.format(date.getTime())); } }; edit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new TimePickerDialog(TarefaActivity.this, tpd, date.get(Calendar.HOUR_OF_DAY), date.get(Calendar.MINUTE), true).show(); } }); } O método adicionarTimePicker recebe um EditText que guarda o valor da hora e também um Calendar que possui e guarda a hora que é apresentada no TimePicker, o uso do Calendar é apenas para facilitar a separação dos campos hora e minuto. A seguir temos as imagens das caixas de dialógo de data e hora na versão antiga (figura 9.6) e na versão nova (figura 9.7): 165 Action Bar Figura 9.6 - Antiga versão do DataPicker e TimePicker. Figura 9.7 - Nova versão do DataPicker e TimePicker. Por fim temos o método salvar que salva os dados no SQLite: Action Bar 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 166 public void salvar(View view) { Date inicio; Date fim; try{ inicio = dhf.parse( ((EditText) findViewById(R.id.dataInicio)).getText().toString() + " " + ((EditText) findViewById(R.id.horaInicio)).getText().toString()); fim = dhf.parse( ((EditText) findViewById(R.id.dataFim)).getText().toString() + " " + ((EditText) findViewById(R.id.horaFim)).getText().toString()); } catch(Exception e) { inicio = new Date(); fim = new Date(); } Tarefa tarefa = new Tarefa(); tarefa.setId(tarefaId); tarefa.setNome(((EditText) findViewById(R.id.nome)).getText().toString()); tarefa.setInicio(inicio); tarefa.setFim(fim); tarefa.setCategoria((String) ((Spinner) findViewById(R.id.categoria)) .getSelectedItem()); db.salvar(tarefa); finish(); } Note que ao final do salvar é chamado o método finish();, essa chamada indica que a Activity deve ser encerrada e portanto retornará para a MainActivity. Na MainActivity vamos carregar a lista de acordo com as tarefas que foram salvas no SQLite: 1 2 3 4 5 6 7 8 9 10 11 package com.leanpub.googleandroid.tanahora; import import import import import import import import android.content.Intent; android.support.v7.app.AppCompatActivity; android.os.Bundle; android.view.Menu; android.view.MenuItem; android.view.View; android.widget.AdapterView; android.widget.ListView; Action Bar 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import java.util.List; public class MainActivity extends AppCompatActivity { private TarefaDB db; private List<Tarefa> lista; private TarefaAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); db = new TarefaDB(this); carregarLista(); ListView list = (ListView) findViewById(R.id.lista); list.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Bundle bundle = new Bundle(); bundle.putLong("id", lista.get(position).getId()); Intent i = new Intent(getBaseContext(), TarefaActivity.class); i.putExtras(bundle); startActivity(i); } }); } @Override protected void onResume() { super.onResume(); carregarLista(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } 167 168 Action Bar 54 55 56 57 58 59 60 61 62 63 64 65 public void carregarLista() { lista = db.consultarTarefas(); ListView list = (ListView) findViewById(R.id.lista); adapter = new TarefaAdapter(this, 0, lista); list.setAdapter(adapter); } public void novo(MenuItem item) { startActivity(new Intent(this, TarefaActivity.class)); } } No menu_main.xml adicione no item adicionar a ação do onClick, para quando clicar nele abrir a tela de cadastro de tarefa: 1 android:onClick="novo" Execute o aplicativo e teste o cadastro e alteração das tarefas: Figura 9.3.1 - Executando o aplicativo Tá na hora. 169 Action Bar 9.4 Alterando o ícone do aplicativo No AndroidManifest.xml podemos informar qual o ícone utilizado por meio da propriedade android:icon="@mipmap/ic_launcher" se quiser mudar o ícone basta alterar as imagens que estão na pasta res: mipmap-mdpi - Imagem com tamanho 48x48. mipmap-hdpi - Imagem com tamanho 72x72. mipmap-xhdpi - Imagem com tamanho 96x96. mipmap-xxhdpi - Imagem com tamanho 144x144. mipmap-xxxhdpi - Imagem com tamanho 192x192. Figura 9.4.1 - Ícone do aplicativo. 9.5 Resumo Nesse capítulo abordamos o uso básico do Action Bar para criar acesso rápido as telas do aplicativo, também usamos os componentes Spinner, DatePicker e TimePicker, por fim foi apresentado como alterar a imagem usada como ícone de lançamento do aplicativo. 9.6 Exercícios Exercício 1 - Altere o aplicativo Tá na hora para permitir cadastrar as categorias da tarefa, para isso utilize o menu flutuante no Action Bar e salve as categorias no SQLite. Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento O objetivo deste apêndice é auxiliar na instalação e configuração de todo ambiente necessário para o desenvolvimento dos exemplos e exercícios deste livro. Precisaremos de basicamente dois itens: o Java Developer Kit (JDK) e o Android Studio. A seguir, será apresentado passo a passo como fazer o download, instalar e configurar cada um deles. Java Developer Kit O Java Developer Kit (Kit de Desenvolvimento Java) contém as principais ferramentas para que possamos programar e compilar os códigos escritos utilizando a linguagem Java, que é uma das linguagens disponível para desenvolvimento de aplicativos Android. Para fazer o download do JDK, acesse o site da Oracle⁵ e siga os passos: 1- Escolha a versão mais atual do Java, neste exemplo está no Java SE 9.0.4, e clique no botão DOWNLOAD do JDK; ⁵http://www.oracle.com/technetwork/java/javase/downloads/index.html Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 171 Figura 1.1 - Fazer download do JDK. 2- Leia os termo de acordo de licença do Java SE e se concordar marque a opção Accept License Agreement; 3- Clique na versão do download de acordo com o seu sistema operacional, neste exemplo vou mostrar a instalação para Windows. Neste momento será realizado o download do Java SE Development Kit. Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 172 Figura 1.2 - Aceitar a licença de uso e fazer download do JDK de acordo com o sistema operacional. Instalando o JDK no Windows Para instalar o JDK no Windows, execute o arquivo .exe que foi feito o download e siga os passos como mostrado a seguir: 1- Na tela Java SE Development Kit … - Setup clique em Next (Próximo); Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 173 Figura 1.3 - Instalar o JDK. 2- A seguir, na tela de Custom Setup, temos a opção de configurar o local do computador que será instalado o JDK, se quiser alterar o local de instalação clique no botão Change…, caso contrário clique apenas na opção Next (Próximo) para continuar com a instalação no diretório padrão; Figura 1.4 - Definir local de instalação do JDK. 3- Agora será solicitado a instalação do Java Runtime Enviroment (JRE) que é o ambiente que executa as aplicações Java, se você quiser alterar o local de instalação clique no botão Alterar…, caso contrário clique apenas na opção Próximo >; Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 174 Figura 1.5 - Instalar o JRE. 4- Por fim, clique na opção Close (Fechar) para finalizar a instalação do JDK e JRE. Figura 1.6 - Finalizar a instalação do JDK e JRE. Agora precisamos configurar as variáveis de ambiente do Java no Windows, para isso siga os passos como mostrados a seguir: 1- No Windows Explorer, clique com o botão direito do mouse sobre Meu Computador; 2- No menu escolha a opção Propriedades; Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento Figura 1.7 - Acessando as propriedades do computador. 3- Na tela de Sistema clique no link Configurações avançadas do sistema; Figura 1.8 - Acessando as configurações avançadas do sistema. 4- Na tela de Propriedades do Sistema clique no botão Variáveis de Ambiente…; 175 Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 176 Figura 1.9 - Acessando as variáveis de ambiente. 5- Na tela de Variáveis de Ambiente precisamos configurar duas variáveis, para fazer isso, na área de Variáveis do sistema clique no botão *Novo…; Figura 1.10 - Criando uma nova variável de ambiente. 6- Defina o nome da variável como JAVA_HOME e o valor da variável o caminho da instalação do Java no seu computador, clique em OK para criar a variável de ambiente; Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 177 Figura 1.11 - Configurando a variável de ambiente JAVA_HOME. 7- Na lista de variáveis do sistema escolha a variável chamada PATH e clique em Editar…, no final da variável acrescente ;%JAVA_HOME%\bin. Caso não tenha uma variável PATH crie uma nova variável com o valor %JAVA_HOME%\bin. Figura 1.12 - Configurando a variável de ambiente PATH. Essa configuração da variável de ambiente serve para que as demais aplicações encontrem a instalação do Java e também para que você possa executar uma aplicação Java de qualquer local do seu computador. Após isso pode fechar a tela de Variáveis de Ambiente, Propriedades do Sistema e Sistema. Para testar se a configuração está correta, siga os passos apresentados a seguir: 1- No iniciar do Windows pesquise e abra o programa Prompt de Comando; 2- No Prompt de Comando digite o comando java -version e pressione em ENTER, deverá aparecer as informações da versão do Java instalado; 3- Digite o comando javac e pressione ENTER aparece uma mensagem informando as possíveis combinações que podem ser utilizadas no comando javac. Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 178 Figura 1.13 - Testando se o Java foi instalado corretamente. Pronto, você instalou e configurou o Java no seu computador, agora você já pode começar a desenvolver suas aplicações Java. Android Studio O Android Studio é a IDE que utilizamos para desenvolver os aplicativos para Smartphones, Tablets, TV e Wear (dispositivos vestíveis como: relógio, pulseira e óculos) Android. Para fazer download do Android Studio, acesse o site do Android Developer⁶: 1- No site do Android Developers clique no link Obter o Android Studio; ⁶http://developer.android.com Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento Figura 1.14 - Site Android Developers. 2- Na página do Android Studio clique na opção DOWNLOAD ANDROID STUDIO; 179 Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 180 Figura 1.15 - Site do Android Studio. 3- Leia os termos e condições de licença do Android Studio, se você estiver de acordo assinale a opção Li e concordo com todos os termos e condições acima e clique no botão DOWNLOAD ANDROID STUDIO FOR …. Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 181 Figura 1.16 - Fazer download do Android Studio. Nesse momento será realizado o download do Android Studio. Instalando o Android Studio no Windows Após fazer o download do Android Studio, vamos instalar e configurar o ambiente de desenvolvimento como mostrado a seguir: 1- Na tela Welcome to Android Studio Setup (Bem vindo a configuração do Android Studio) clique na opção Next (Próximo) para iniciar a instalação; Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 182 Figura 1.17 - Instalar o Android Studio. 2- Na tela Choose Componentes (Escolher componentes) é possível escolher quais componentes do Android serão instalados, assinale as opções: Android SDK - é o kit de desenvolvimento para Android; e Android Virtual Device - que é o emulador virtual de dispositivos Android; depois clique no botão Next (Próximo) para continuar a instalação; Figura 1.18 - Selecionar os componentes que serão instalados. 3- Na tela Configuration Settings (Definições de configuração) é possível escolher o local que o Android Studio e o SDK serão instalados no seu computador, após escolher o melhor local para você, clique no botão Next (Próximo) para continuar a instalação; Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 183 Figura 1.19 - Definir local de instalação do Android Studio. 4- Na tela Choose Start Menu Folder (Escolher a pasta do menu iniciar) você pode escolher se deseja criar os atalhos para acessar o Android Studio no Menu Iniciar do Windows, clique no botão Next (Próximo) para continuar a instalação; Figura 1.20 - Definir se será criado atalho para acessar o programa Android Studio. 5- Na tela Installation Complete (Instalação completa) após completar a instalação clique no botão Next (Próximo) para continuar a instalação; Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 184 Figura 1.21 - Instalação completa do Android Studio. 6- Na tela Completing Android Studio Setup (Configuração completa do Android Studio) assinale a opção Start Android Studio (Iniciar Android Studio) e clique em Finish (Terminar) para finalizar a instalação e inicializar o Android Studio. Figura 1.22 - Finalizar a instalação e iniciar o Android Studio. Quando finalizar a instalação o Android Studio será inicializado. Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 185 Figura 1.23 - Finalizar a instalação e iniciar o Android Studio. Agora temos mais algumas configurações iniciais do Android Studio, como mostrado a seguir: 1- Quando o Android Studio termina de inicializar pela primeira vez aparece a tela de Welcome (Bem vindo), clique em Next (Próximo) para continuar; Figura 1.24 - Bem vindo ao Android Studio. 2- Na janela Install Type (Tipo de instalação) deixe selecionado a opção Standard (Padrão) e clique em Next (Próximo) para continuar; Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 186 Figura 1.25 - Definindo o tipo de instalação. 3- Na janela Select UI Theme (Selecionar o tema de interface de usuário) escolha o tema que mais te agrada: IntelliJ ou Darcula e clique em Next (Próximo) para continuar; Figura 1.26 - Selecionando o tema visual. 4- Na janela Verify Settings (Verificar configurações) você pode visualizar tudo que será feito Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento 187 download e instalado por padrão, clique em Finish (Finalizar) para continuar; Figura 1.27 - Verificando as configurações que serão instaladas. 5- Na janela Downloading Components (Fazendo download dos componentes) você pode acompanhar todos os downloads realizados, clique em Finish (Finalizar) para finalizar esta configuração inicial. Apêndice 1 - Instalando e configurando o ambiente de desenvolvimento Figura 1.28 - Finalizar a configuração inicial do Android Studio. Pronto, a partir daqui o Android Studio está instalado e tem sua configuração inicial. 188