2011
Anderson Duarte de Amorim
ANDROID, uma vis達o geral
Conteúdo Introdução ....................................................................................................................... 10 Atividades ....................................................................................................................... 11 Criar uma atividade .................................................................................................... 11 Implementando uma interface de usuário ................................................................... 12 Declarando a atividade no manifesto.......................................................................... 13 O uso de filtros intenção ............................................................................................. 13 Iniciar uma atividade .................................................................................................. 14 Iniciar uma atividade para um resultado..................................................................... 15 Encerrar uma atividade ............................................................................................... 16 Gerenciando o ciclo de atividade................................................................................ 16 Aplicar o ciclo de vida callbacks ................................................................................ 17 Salvando estado de atividade ...................................................................................... 23 Manipulação de alterações na configuração ............................................................... 25 Coordenar as atividades .............................................................................................. 26 Fragmentos ..................................................................................................................... 27 Filosofia de design ...................................................................................................... 27 Criando um fragmento ................................................................................................ 29 DialogFragment ...................................................................................................... 30 ListFragment ........................................................................................................... 31 PreferenceFragment ................................................................................................ 31 Adicionando uma interface de usuário ................................................................... 31 Criando um layout .................................................................................................. 32 Adicionando um fragmento de uma atividade........................................................ 32 Adicionando um fragmento sem uma interface de usuário (UI) ............................ 34 Gerenciando fragmentos ......................................................................................... 35 Executando transações com fragmento .................................................................. 35
ANDROID, uma visão geral – Anderson Duarte de Amorim
2
Comunicando-se com a atividade ........................................................................... 37 Criar callbacks evento para a atividade .................................................................. 38 Adicionando itens à barra de ação .......................................................................... 39 Manuseio do ciclo de vida do fragmento................................................................ 40 Coordenação com o ciclo de vida de atividade ...................................................... 41 Loaders ........................................................................................................................... 43 Resumo API Loader ................................................................................................... 43 Usando carregadores em um aplicativo ...................................................................... 44 Iniciando um Loader ............................................................................................... 45 Reiniciando o Loader.............................................................................................. 46 Usando callbacks do LoaderManager..................................................................... 47 Exemplo ...................................................................................................................... 50 Tarefas e pilha de execução ............................................................................................ 53 Salvando estado de atividade ...................................................................................... 56 Gerenciando tarefas .................................................................................................... 57 Definição de modos de lançamento ............................................................................ 58 Usando o arquivo de manifesto .................................................................................. 59 Usando as opções de intenções ................................................................................... 61 Manipulação de afinidades ......................................................................................... 62 Limpando a pilha de volta .......................................................................................... 64 Iniciando uma tarefa ................................................................................................... 65 Serviços .......................................................................................................................... 66 O básico ...................................................................................................................... 67 Você deve utilizar um serviço ou um thread? ........................................................ 67 Declarando um serviço no manifesto ..................................................................... 69 Criando um serviço iniciado ....................................................................................... 70 Segmentação Android 1.6 ou inferior..................................................................... 70
ANDROID, uma visão geral – Anderson Duarte de Amorim
3
Estendendo a classe IntentService .......................................................................... 71 Estendendo a classe de serviço ............................................................................... 73 Iniciando um serviço .............................................................................................. 77 Parando um serviço ................................................................................................ 77 Criando um serviço vinculado .................................................................................... 78 Enviando notificações para o usuário ......................................................................... 79 Executando um serviço em primeiro plano ................................................................ 79 Gerenciando ciclo de vida de um serviço ................................................................... 80 Aplicando o ciclo de vida dos callbacks ................................................................. 81 Serviços vinculados ........................................................................................................ 84 O básico ...................................................................................................................... 84 Vinculação a um serviço iniciado ........................................................................... 84 Criando um serviço ligado.......................................................................................... 85 Estendendo a classe Binder .................................................................................... 87 Usando um Messenger ........................................................................................... 90 Vinculação a um serviço............................................................................................. 93 Notas adicionais ...................................................................................................... 95 Gerenciando o ciclo de vida de um serviço de associação ......................................... 96 Processos e threads ......................................................................................................... 98 Processos .................................................................................................................... 98 Ciclo de vida do processo ....................................................................................... 99 Thread ....................................................................................................................... 102 Threads funcionais ................................................................................................ 103 Usando AsyncTask ............................................................................................... 104 Métodos de Thread-safe ....................................................................................... 106 Comunicação entre processos ................................................................................... 106 Interface de usuário ...................................................................................................... 108
ANDROID, uma visão geral – Anderson Duarte de Amorim
4
Hierarquia de view.................................................................................................... 108 Como o Android desenha views ........................................................................... 109 Layout ....................................................................................................................... 111 Widgets ..................................................................................................................... 112 Eventos UI ................................................................................................................ 114 Menus ....................................................................................................................... 114 Tópicos Avançados .................................................................................................. 115 Adaptadores .......................................................................................................... 115 Estilos e Temas ..................................................................................................... 116 Declarando Layout ....................................................................................................... 117 Escreve o XML......................................................................................................... 118 Carregar os recursos XML ....................................................................................... 119 Atributos ................................................................................................................... 119 ID .......................................................................................................................... 119 Parâmetros de layout ............................................................................................ 120 Posição de Layout ..................................................................................................... 122 Tamanho, padding e margin ..................................................................................... 122 Criando Menus ............................................................................................................. 124 Menu de Opções ................................................................................................... 124 Menu de Contexto ................................................................................................ 124 Submenu ............................................................................................................... 124 Criando um recurso de menu .................................................................................... 124 Inflar um recurso de menu ........................................................................................ 126 Criando um Menu de Opções ................................................................................... 126 Respondendo à ação do usuário............................................................................ 127 Alterando os itens de menu em tempo de execução ............................................. 129 Criando um Menu de Contexto ................................................................................ 129
ANDROID, uma visão geral – Anderson Duarte de Amorim
5
Registre uma ListView ......................................................................................... 130 Criando Submenus.................................................................................................... 132 Outras funções do menu ........................................................................................... 132 Grupos de Menu ................................................................................................... 132 Itens de menu verificados ..................................................................................... 133 As teclas de atalho ................................................................................................ 135 Adicionar intenções em menu dinamicamente ..................................................... 136 Permitindo a sua atividade a ser adicionada para outros menus........................... 137 Usando a barra de ação ................................................................................................. 139 Adicionando a barra de ação .................................................................................... 139 Removendo a barra de ação .................................................................................. 140 Adicionando itens de ação ........................................................................................ 141 Usando o ícone do aplicativo como um item de ação .......................................... 142 Usando o ícone do aplicativo para navegar "para cima" ...................................... 143 Adicionando uma exibição de ação .......................................................................... 144 Adicionando abas ..................................................................................................... 146 Adicionando de navegação drop-down .................................................................... 149 Exemplo de SpinnerAdapter e OnNavigationListener ......................................... 150 Estilizando a barra de ação ....................................................................................... 152 Criando caixas de diálogo............................................................................................. 156 Mostrando uma caixa de diálogo .............................................................................. 156 Dispensar um diálogo ............................................................................................... 158 Usando demissão de receptores ............................................................................ 158 Criando um AlertDialog ........................................................................................... 159 Adicionando botões .............................................................................................. 160 Adicionando uma lista .......................................................................................... 161 Adicionando caixas de seleção e botões de rádio ................................................. 161
ANDROID, uma visão geral – Anderson Duarte de Amorim
6
Criar um ProgressDialog .......................................................................................... 162 Mostrando uma barra de progresso ...................................................................... 163 Criando uma caixa de diálogo personalizada ........................................................... 166 Manipulando eventos de UI.......................................................................................... 169 Os ouvintes de eventos ............................................................................................. 169 Manipuladores de eventos ........................................................................................ 172 Modo de toque .......................................................................................................... 173 Manipulação do foco ................................................................................................ 174 Notificar o usuário ........................................................................................................ 176 Notificação brinde .................................................................................................... 176 Criando notificações brinde .................................................................................. 177 Notificação na barra de status................................................................................... 179 Criação de notificações da barra de status ............................................................ 180 Notificação de diálogo .............................................................................................. 189 Aplicando estilos e temas ............................................................................................. 190 Definição de estilos .................................................................................................. 190 Herança ................................................................................................................. 191 Propriedades do estilo........................................................................................... 192 Aplicando estilos e temas para a interface do usuário .............................................. 194 Aplicar um estilo a uma view ............................................................................... 194 Aplicar um tema a uma atividade ou aplicação .................................................... 195 Selecione um tema baseado na versão de plataforma........................................... 196 Usando estilos e temas da plataforma ...................................................................... 196 Recursos de aplicação ................................................................................................... 198 Armazenamento de dados............................................................................................. 201 Utilizando preferências compartilhadas ................................................................... 201 Preferências do usuário ......................................................................................... 202
ANDROID, uma visão geral – Anderson Duarte de Amorim
7
Usando o armazenamento interno ............................................................................ 203 Salvando os arquivos de cache ............................................................................. 204 Usando o armazenamento externo............................................................................ 205 Verificar a disponibilidade dos meios .................................................................. 205 Acessando arquivos em armazenamento externo ................................................. 206 Escondendo seus arquivos a partir da Media Scanner.......................................... 206 Como salvar arquivos que devem ser compartilhados ......................................... 206 Salvando os arquivos de cache ............................................................................. 207 Utilizando bancos de dados ...................................................................................... 208 Banco de dados de depuração ............................................................................... 209 Usando uma conexão de rede ................................................................................... 209 Artigos .......................................................................................................................... 210 Acessibilidade........................................................................................................... 210 Linguagens e recursos .......................................................................................... 210 Linguagens e localidade ....................................................................................... 211 Fazendo o dispositivo „falar‟ ................................................................................ 211 Interface .................................................................................................................... 213 Toque .................................................................................................................... 213 Gestos ....................................................................................................................... 216 Método de entrada de dados ................................................................................. 216 Criando um método de entrada de dados ............................................................. 223 Ações de desenhos .................................................................................................... 226 Truques de layout: criando layouts eficientes .......................................................... 229 Truques de layout: usando ViewStub ....................................................................... 234 Truques de layout: mesclando layouts...................................................................... 237 ListView, uma otimização ........................................................................................ 244 Live folders ............................................................................................................... 247
ANDROID, uma visão geral – Anderson Duarte de Amorim
8
Live Wallpapers ........................................................................................................ 252 Usando webViews .................................................................................................... 254 Funcionalidades ........................................................................................................ 255 Caixa de pesquisa ................................................................................................. 255 Sistema ..................................................................................................................... 258 Alocação de memória ........................................................................................... 258 Zipaling oferece uma otimização fácil ................................................................. 260 Nota .............................................................................................................................. 263 Fontes ........................................................................................................................... 263 Fontes das imagens ................................................................................................... 266 Fontes dos artigos ..................................................................................................... 266
ANDROID, uma visão geral – Anderson Duarte de Amorim
9
Introdução O Android é um software open-source criado para os celulares e outros dispositivos. O Android Open Source Project (AOSP), liderado pelo Google, está encarregado da manutenção e desenvolvimento do Android. Muitos fabricantes de dispositivos trouxeram ao mercado de dispositivos rodando o Android, e eles são disponíveis ao redor
do
mundo.
O objetivo principal é construir uma plataforma de software excelente para usuários de todos os dias. Uma série de empresas empenhou muitos engenheiros para atingir esse objetivo, e o resultado é uma produção total de produtos de consumo de qualidade, cuja fonte
é
aberta
para
customização
e
portabilidade.
Você pode encontrar mais informações sobre o Android a partir destas páginas abaixo: Filosofia de projeto e objetivos Interagindo com o projeto Compatibilidade com Android Informações sobre licenciamento
ANDROID, uma visão geral – Anderson Duarte de Amorim
10
Atividades Uma Activity é um componente do aplicativo que fornece uma tela com a qual os usuários podem interagir, a fim de fazer algo, como discar o telefone, tirar uma foto, enviar um e-mail, ou ver um mapa. Para cada atividade é dada uma janela na qual se desenha sua interface de usuário. A janela normalmente preenche a tela, mas pode ser menor do que a tela e flutuar em cima de outras janelas. Um aplicativo normalmente consiste de múltiplas atividades que são frouxamente ligadas uns aos outros. Normalmente uma atividade em um aplicativo é especificada como a atividade “principal", que é apresentada ao usuário ao iniciar o aplicativo pela primeira vez. Cada atividade pode começar outra atividade, a fim de executar ações diferentes. Cada vez que começa uma nova atividade, a atividade anterior é interrompida, mas o sistema preserva a atividade em uma pilha (a "pilha de volta"). Quando uma nova atividade começa, é empurrada para a pilha de volta e leva o foco do usuário. A pilha de volta usa "last in, first out" como mecanismo de fila, então, quando o usuário está em uma atividade e pressione a tecla BACK, a atividade é removida da pilha (e destruída) e retoma a atividade anterior. Quando uma atividade é parada por causa de uma nova atividade, há uma notificação da alteração no estado através de métodos de retorno da atividade do ciclo de vida. Existem vários métodos de retorno que uma atividade possa receber, devido a uma mudança em seu estado. Por exemplo, quando parado, sua atividade deve liberar todos os objetos grandes, como conexões de rede ou banco de dados. Quando a atividade recomeça, você pode readquirir os recursos necessários e retomar as ações que foram interrompidas. Estas transições de estado são todos parte do ciclo de atividade.
Criar uma atividade Para criar uma atividade, você deve criar uma subclasse da Activity (ou uma subclasse existente do mesmo). Em sua subclasse, você precisa implementar métodos de callback que chama o sistema quando ocorrem as transições de atividade entre diversos estados do seu ciclo de vida, como quando a atividade está sendo criada, parou, recomeçou, ou foi destruída. Os dois métodos de retorno mais importantes são: onCreate(): Você deve implementar este método. O sistema chama isso ao criar a sua atividade. Dentro de sua aplicação, você deve inicializar os componentes essenciais de ANDROID, uma visão geral – Anderson Duarte de Amorim
11
sua atividade. Mais importante, este é o lugar onde você deve chamar setContentView() para definir o layout para a atividade do usuário a interface. onPause(): O sistema chama este método como o primeiro indício de que o usuário está saindo de sua atividade (embora nem sempre signifique que a atividade está sendo destruída). Isso geralmente é onde você deve cometer quaisquer alterações que devem ser mantidas para além da sessão atual do usuário (porque o usuário pode não voltar). Existem vários métodos de retorno do ciclo de vida de outros que você deve usar a fim de proporcionar uma experiência de usuário mais fluida entre as atividades e manipular interrupções inesperadas que causem a sua atividade a ser interrompida e até mesmo destruída.
Implementando uma interface de usuário A interface de usuário para uma determinada atividade é assegurada por uma hierarquia de pontos de vista - objetos derivam da classe View. Cada exibição controla um determinado espaço retangular dentro da janela da atividade e pode responder a interação do usuário. Por exemplo, uma visão pode ser um botão que inicia uma ação quando o usuário tocá-la. Android fornece um número de pontos de vista prontos que você pode usar para criar e organizar seu layout. "Widgets" são vistas que proporcionam um visual (interativo) de elementos para a tela, como um botão, um campo texto, checkbox, ou apenas uma imagem. "Esquemas" são pontos de vista derivados de ViewGroup que fornecem um modelo de layout exclusivo para a estrutura derivada, como um layout linear, um layout de grade, ou a disposição relativa. Você pode criar uma subclasse da View e ViewGroup (ou subclasses existentes) para criar seus próprios widgets e layouts e aplicá-las ao seu layout atividade. A maneira mais comum para definir um layout usando pontos de vista é com um arquivo XML salvo disposição em recursos do seu aplicativo. Dessa forma, você pode manter o design da sua interface de usuário separadamente do código fonte que define o comportamento da atividade. Você pode definir o layout da interface do usuário para a sua atividade com setContentView(), passando a identificação do recurso para o layout. No entanto, você também pode criar novas Views no seu código de atividade e construir
ANDROID, uma visão geral – Anderson Duarte de Amorim
12
uma hierarquia de vista através da inserção de novos Views em um ViewGroup, em seguida, usar esse esquema, passando a raiz ViewGroup para setContentView().
Declarando a atividade no manifesto Você deve declarar a sua atividade no arquivo de manifesto para que ele seja acessível para o sistema. Para declarar sua atividade, abra o arquivo e adicione um <activity> como um filho do <application>. Por exemplo: <manifest ... > <application ... > <activity android:name=".ExampleActivity" /> ... </application ... > ... </manifest >
Existem vários outros atributos que podem ser incluídos nesse elemento, para definir propriedades, como o rótulo para a atividade, um ícone para a atividade, ou um tema ao estilo de interface do usuário da atividade.
O uso de filtros intenção Um <activity> também pode especificar filtros diferentes, usando o <intent-filter>, a fim de declarar como outros componentes de aplicação podem ativá-lo. Quando você cria um novo aplicativo usando as ferramentas do Android SDK, a atividade de topo que é criada para você automaticamente inclui a intenção de filtro que declara a atividade e responde à ação "principal" e deve ser colocado no "lançador" da categoria. A intenção parece filtro como este: <activity android:name=".ExampleActivity" android:icon="@drawable/app_icon"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
O elemento <action> especifica que este é o "principal ponto de entrada" para o aplicativo. O elemento <category> especifica que esta atividade deve ser listada no sistema lançador de aplicação.
ANDROID, uma visão geral – Anderson Duarte de Amorim
13
Se você pretende que o seu aplicativo seja auto-suficiente e não permita que outras aplicações ativem as suas atividades, então você não precisa de nenhum outro filtro de intenção. Apenas uma atividade deve ter a ação "principal" e "lançador" da categoria, como no exemplo anterior. Atividades que você não deseja disponibilizar para outros aplicativos não devem ter a intenção de filtros e você pode iniciá-los usando as intenções explícitas. No entanto, se você quiser a sua atividade para responder às intenções implícitas que são entregues a partir de outras aplicações (e suas próprias), então você deve definir filtros de intenção adicional para a sua atividade. Para cada tipo de intenção para o qual pretende responder, você deve incluir um <intent-filter> que inclui um elemento <action> e, opcionalmente, um elemento <category> e/ou um <data>. Estes elementos especificam o tipo de intenções para que sua atividade possa responder.
Iniciar uma atividade Você pode iniciar outra atividade, chamando startActivity(), passando-lhe uma Intent que descreve a atividade que deseja iniciar. A intenção especifica qualquer atividade exatamente o que deseja iniciar ou descreve o tipo de ação que deseja executar (e o sistema seleciona a atividade adequada para você, que pode mesmo ser de um aplicativo diferente). A intenção também pode transportar pequenas quantidades de dados a serem utilizados pela atividade que é iniciada. Quando se trabalha dentro de sua própria aplicação, muitas vezes você precisa simplesmente lançar uma atividade conhecida. Você pode fazer isso criando uma intenção que define explicitamente a atividade que deseja iniciar, usando o nome da classe. Por exemplo, aqui está como uma atividade inicia outra atividade denominada SignInActivity: Intent intent = new Intent(this, SignInActivity.class); startActivity(intent);
No entanto, sua aplicação pode também querer executar alguma ação, como enviar um e-mail, mensagem de texto, ou atualização de status, usando dados de sua atividade. Neste caso, sua aplicação não pode ter as suas próprias atividades para realizar tais ações, para que você possa aproveitar ao invés das atividades previstas por outras aplicações no dispositivo, que pode executar as ações para você. Este é o lugar onde as
ANDROID, uma visão geral – Anderson Duarte de Amorim
14
intenções são realmente valiosas, você pode criar uma intenção que descreve uma ação que deseja executar e o sistema inicia a atividade adequada a partir de outro aplicativo. Se houver múltiplas atividades que podem manipular a intenção, então o usuário pode selecionar qual usar. Por exemplo, se você quer permitir que o usuário envie uma mensagem de e-mail, você pode criar a seguinte intenção: Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_EMAIL, recipientArray); startActivity(intent);
O EXTRA_EMAIL extra adicionado é uma matriz de seqüência de endereços de e-mail para onde o e-mail deve ser enviado. Quando um aplicativo de e-mail responde a esta intenção, ele lê a matriz de cadeia prevista na „extra‟ e as coloca no campo "Para" do formulário de composição de e-mail.
Iniciar uma atividade para um resultado Às vezes, você pode querer receber um resultado da atividade que você começar. Nesse caso, iniciar a atividade, chamando startActivityForResult() (em vez de startActivity() ). Para então receber o resultado da atividade subseqüente, aplicar o onActivityResult(), método de retorno. Quando a atividade subseqüente é feita, ele retorna um resultado de uma Intent para o seu método onActivityResult(). Por exemplo, talvez você queira que o usuário escolha um de seus contatos, para que sua atividade pode fazer algo com as informações desse contato. Veja como você pode criar uma intenção e manipular o resultado: private void pickContact() { // Create an intent to "pick" a contact, as defined by the content provider URI Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI); startActivityForResult(intent, PICK_CONTACT_REQUEST); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // If the request went well (OK) and the request was PICK_CONTACT_REQUEST if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) { // Perform a query to the contact's content provider for the contact's name Cursor cursor = getContentResolver().query(data.getData(), new String[] {Contacts.DISPLAY_NAME}, null, null, null); if (cursor.moveToFirst()) { // True if the cursor is not empty int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME); String name = cursor.getString(columnIndex);
ANDROID, uma visão geral – Anderson Duarte de Amorim
15
// Do something with the selected contact's name... } } }
Este exemplo mostra a lógica básica que você deve usar seu método onActivityResult() para lidar com um resultado de atividade. A primeira condição verifica se a solicitação foi bem-sucedida, se for, então o resultCode será RESULT_OK e se a solicitação para que este resultado está respondendo é conhecido, neste caso, o requestCode coincide com o segundo parâmetro enviada com startActivityForResult(). De lá, o código manipula o resultado de atividade, consultando os dados retornados de uma Intent. O que acontece é, um ContentResolver executa uma consulta contra um provedor de conteúdo, que retorna um Cursor que permite que os dados consultados possam serem lidos.
Encerrar uma atividade Você pode encerrar uma atividade chamando seu método finish(). Você também pode encerrar uma atividade separada que já começou chamando finishActivity() . Nota: Na maioria dos casos, você não deve terminar explicitamente uma atividade com estes métodos. Como discutido na seção seguinte sobre o ciclo de vida de atividade, o sistema Android gerencia a vida de uma atividade para você, então você não precisa terminar a sua própria atividade. Chamar esses métodos pode afetar negativamente a experiência do usuário e só deve ser usado quando você realmente não quer que o usuário retorne a esta instância da atividade.
Gerenciando o ciclo de atividade Gerenciar o ciclo de vida de suas atividades através da implementação de métodos de retorno é essencial para desenvolver uma aplicação forte e flexível. O ciclo de vida de uma atividade está diretamente afetada pela sua associação com outras atividades, a sua missão e voltar à pilha. Uma atividade pode existir em três estados, essencialmente: Retomado: A atividade está em primeiro plano da tela e tem o foco do usuário. (Esse estado é também por vezes referido como "run".)
ANDROID, uma visão geral – Anderson Duarte de Amorim
16
Em pausa: Outra atividade está em primeiro plano e tem foco, mas este é ainda visível. Ou seja, outra atividade é visível na parte superior de um presente e que a atividade é parcialmente transparente ou não cobre a tela inteira. Uma atividade em pausa está completamente viva (o objeto Activity é mantido na memória, ele mantém todas as informações do estado e membro, e permanece preso ao gerenciador de janelas), mas pode ser morta pelo sistema em situações de pouca memória. Parado: A atividade é totalmente obscurecida por outra atividade (a atividade está agora em "background"). A atividade parada também está ainda viva (o objeto Activity é mantido na memória, ele mantém todas as informações do estado e membro, mas não está ligado ao gerenciador de janelas). No entanto, já não é visível para o usuário e pode ser morto pelo sistema quando a memória é necessária em outro lugar. Se uma atividade está em pausa ou parada, o sistema pode retirá-la da memória, quer por pedir para terminar (chamando seu finish()), ou simplesmente matar o processo. Quando a atividade é aberta novamente (depois de ter sido concluído ou morto), ela deve ser criada por toda parte.
Aplicar o ciclo de vida callbacks Quando uma atividade transita entrando e saindo dos diferentes estados descritos acima, ele é notificado através de vários métodos de retorno. Todos os métodos de callback são ganchos que você pode substituir para fazer um trabalho adequado quando o estado da sua atividade muda. A atividade seguinte inclui cada um dos métodos de ciclo de vida fundamentais: public class ExampleActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // The activity is being created. } @Override protected void onStart() { super.onStart(); // The activity is about to become visible. } @Override protected void onResume() { super.onResume(); // The activity has become visible (it is now "resumed"). }
ANDROID, uma visão geral – Anderson Duarte de Amorim
17
@Override protected void onPause() { super.onPause(); // Another activity is taking focus (this activity is about to be "paused"). } @Override protected void onStop() { super.onStop(); // The activity is no longer visible (it is now "stopped") } @Override protected void onDestroy() { super.onDestroy(); // The activity is about to be destroyed. } }
Nota: A implementação destes métodos do ciclo de vida deve sempre chamar a implementação da superclasse antes de fazer qualquer trabalho, conforme mostrado nos exemplos acima. Juntos, esses métodos definem o ciclo de vida de uma atividade. Ao implementar esses métodos, você pode monitorar três loops aninhados no ciclo de vida de atividade: A vida inteira de uma atividade acontece entre a chamada para onCreate() e a chamada para onDestroy(). Sua atividade deve executar a instalação do "Estado" global (tal como a definição de layout) em onCreate(), e liberar todos os recursos remanescentes em onDestroy().
Por exemplo, se a sua atividade tem um
segmento em execução em segundo plano para transferir os dados da rede, ele pode criar esse tópico em onCreate() e depois parar o segmento em onDestroy(). O tempo de vida visível de uma atividade acontece entre a chamada para onStart() e a chamada para onStop(). Durante este tempo, o usuário pode ver a atividade na tela e interagir com ele. Por exemplo, onStop() é chamado quando inicia uma nova atividade e esta não é mais visível. Entre estes dois métodos, você pode manter os recursos que são necessários para mostrar a atividade para o usuário. Por exemplo, você pode registrar um BroadcastReceiver em onStart() para monitorar as mudanças que impactam sua interface do usuário, e cancelar o registro em onStop() quando o usuário não pode mais ver o que você está sendo exibido. O sistema pode chamar onStart() e onStop() várias vezes durante toda a vida útil da atividade, como a atividade se alterna entre visível e oculta para o usuário. ANDROID, uma visão geral – Anderson Duarte de Amorim
18
O tempo de vida do primeiro plano de uma atividade acontece entre a chamada para onResume() e a chamada para onPause(). Durante este tempo, a atividade está na frente de todas as outras atividades na tela e tem foco de entrada do usuário. Uma atividade pode freqüentemente transitar para dentro e fora do plano, por exemplo, onPause() é chamado quando o dispositivo vai dormir ou quando uma caixa de diálogo aparece. O código desses dois métodos deve ser bastante leve, para evitar transições lentas que fazem o usuário esperar. A figura 1 ilustra esses laços e os caminhos de uma atividade que podem levar a esses estados.
Figura 1. O ciclo de vida de atividade.
ANDROID, uma visão geral – Anderson Duarte de Amorim
19
Tabela 1. Um resumo do ciclo de vida do callback métodos atividade.
Método
Killable depois?
Descrição
Seguinte
Chamado quando a atividade é criada pela primeira vez. Isto é onde você deve fazer tudo do seu conjunto estático onCreate()
normal - criar pontos de vista,
Não
onStart()
Não
onStart()
Não
onResume()
Não
onPause()
vincular dados em listas, e assim por diante. Sempre seguido por onStart() . Chamado atividade onRestart()
depois foi
que
a
interrompida,
pouco antes de ele ser iniciado novamente. Sempre seguido por onStart() Chamado imediatamente antes da atividade tornar-se visível para o usuário. Seguido por
onStart()
onResume() se a atividade vem para o primeiro plano, ou onStop() se torna oculto. Chamado imediatamente antes da atividade passar a interagir com o usuário. Neste ponto, a
onResume()
atividade está no topo da pilha de atividade. Sempre seguido por onPause() .
ANDROID, uma visão geral – Anderson Duarte de Amorim
20
Chamado quando o sistema está prestes a começar a retomar a outra atividade. Este método é geralmente usado para confirmar as alterações não salvas, dados persistentes, animações stop e outras coisas que podem estar consumindo onPause()
CPU, e assim por diante. Ele
Sim
onResume() ou onStop()
Sim
onRestart() ou onDestroy()
deve fazer tudo o que ele faz muito rapidamente, porque a próxima atividade não será retomada até que ele retorne. Seguidas por onResume() se a atividade retorna para a frente, ou por onStop() se torna invisível para o usuário. Chamado quando a atividade já não é visível para o usuário. Isso pode acontecer porque ele está sendo destruído, ou porque outra atividade (seja um existente ou uma nova) foi onStop()
retomado e está cobrindo-o. Seguidas por onRestart() se a atividade está voltando para interagir com o usuário, ou por
onDestroy()
se
essa
atividade está indo embora.
ANDROID, uma visão geral – Anderson Duarte de Amorim
21
Chamado
antes
que
a
atividade é destruída. Esta é a chamada final que a atividade irá
onDestroy()
receber.Poderia
chamada,
quer
atividade
está
(alguém
chamado
ser
porque
a
acabando finish()
Sim
nada
nela), ou porque o sistema está destruindo essa instância da atividade para economizar espaço. Você pode distinguir entre estes dois cenários com o isFinishing().
A coluna chamada "killable depois?" indica se o sistema pode matar o processo que acolhe a atividade a qualquer momento após o método retornar, sem executar outra linha. Três métodos são marcados como "sim": (onPause() , onStop() , e onDestroy() ). onPause() é o primeiro dos três, uma vez que a atividade é criada, onPause() é o último método que é garantido para ser chamado antes que o processo pode ser morto, se o sistema deve recuperar a memória em caso de emergência, então onStop() e onDestroy() não podem ser chamados. Portanto, você deve usar onPause() para escrever dados persistentes para armazenamento. No entanto, você deve ser seletivo sobre quais informações devem ser mantidas durante onPause(), porque os procedimentos de bloqueio neste método bloqueiam a passagem para a próxima atividade e retardam a experiência do usuário. Métodos que são marcados como "Não" na coluna killable protegem o processo da atividade de ser morto desde o momento em que são chamados. Assim, uma atividade é killable a partir do momento onPause() e retorna quando onResume() é chamado. Não será novamente killable até onPause() seja novamente chamado e retornado. Nota: uma atividade que não é tecnicamente "killable" por esta definição na tabela 1 ainda pode ser morta pelo sistema, mas isso vai acontecer apenas em circunstâncias extremas, quando não há outro recurso.
ANDROID, uma visão geral – Anderson Duarte de Amorim
22
Salvando estado de atividade A introdução à Gestão do Ciclo de Atividade menciona brevemente que, quando uma atividade está em pausa ou parada, o estado da atividade é mantido. Isto é verdade porque a Activity ainda está retida na memória quando está em pausa ou parada, todas as informações sobre seus membros e estado atuais ainda estão vivos. Assim, qualquer alteração que o usuário fez no âmbito da atividade é retida na memória, de modo que quando a atividade retorna para o primeiro plano (quando ele "retoma"), essas mudanças ainda estão lá.
Figura 2. As duas formas em que para a atividade um usuário retorna ao foco com seu estado intacto, quer a atividade é interrompida, e retomada em seguida, o estado de atividade permanece intacta (à esquerda), ou a atividade é destruído, então recriada e a atividade deve restaurar o estado da atividade anterior (direita).
No entanto, quando o sistema destrói uma atividade, a fim de recuperar a memória, a Activity é destruída, então o sistema não pode simplesmente continuar com o seu estado intacto. Em vez disso, o sistema deve recriar a Activity se o usuário navega de volta para ele. No entanto, o usuário não sabe que o sistema destrói e recria a atividade e, assim, provavelmente espera que a atividade seja exatamente como era. Nessa situação,
ANDROID, uma visão geral – Anderson Duarte de Amorim
23
você pode garantir que informações importantes sobre o estado de atividade são preservadas através da implementação de um método de retorno adicional que permite que você salve as informações sobre o estado de sua atividade e, em seguida, restaura quando o sistema recria a atividade. O método de callback em que você pode salvar informações sobre o estado atual da sua atividade é onSaveInstanceState(). O sistema chama este método antes de fazer a atividade vulnerável a ser destruída e passa-lhe um objeto Bundle. O Bundle é o lugar onde você pode armazenar informações de estado sobre a atividade como pares valornome, utilizando métodos como putString(). Então, se o sistema mata a atividade e o usuário navega de volta para sua atividade, o sistema passa o Bundle para onCreate() para que você possa restaurar o estado de atividade que tenha sido guardado durante onSaveInstanceState(). Se não há informações do estado para restaurar, em seguida, o Bundle que passou a onCreate() se torna nulo. Nota: Não há nenhuma garantia de que onSaveInstanceState() será chamado antes de sua atividade ser destruída, porque há casos em que não será necessário salvar o estado (como quando o usuário deixa a sua atividade com a chave de volta, porque a usuário explicitamente encerra as atividades). Se o método for chamado, ele sempre é chamado antes de onStop() e, possivelmente, antes de onPause(). No entanto, mesmo se você não faz nada e não implementar onSaveInstanceState(), alguns estados de atividade são restaurados pela Activity de implementação padrão da classe de onSaveInstanceState(). Especificamente, a implementação padrão chama onSaveInstanceState() para cada View no layout, que permite fornecer informações sobre si que devem ser salvos. Quase todos os widgets no âmbito Android implementam este método, de modo que qualquer mudança visível para o interface do usuário são automaticamente salvas e restauradas quando sua atividade é recriada. Por exemplo, o EditText salva qualquer texto digitado pelo usuário e o CheckBox widget salva se é marcado ou não. O único trabalho exigido por você é fornecer uma identificação única (com o android:id) para cada elemento gráfico que deseja salvar seu estado. Se um elemento não tem um ID, então ele não pode salvar seu estado. Você também pode parar explicitamente de salvar em seu layout seu estado, definindo o android:saveEnabled para "false" ou chamando o setSaveEnabled(). Normalmente, você
ANDROID, uma visão geral – Anderson Duarte de Amorim
24
não deve desativar isso, mas você pode caso queira restaurar o estado da atividade de interface diferente. Embora a implementação padrão de onSaveInstanceState() salva as informações úteis sobre a atividade da sua interface, você ainda pode precisar substituí-lo para guardar informações adicionais. Por exemplo, você talvez precise salvar valores de um membro que mudou na vida da atividade (que poderiam se correlacionar com os valores restaurados na interface do usuário, mas os membros que detêm esses valores UI não são restaurados, por padrão). Como a implementação padrão de onSaveInstanceState() ajuda a salvar o estado da interface do usuário, se você substituir o método para salvar informações de estado adicionais, você deve sempre chamar a implementação da superclasse de onSaveInstanceState() antes de fazer qualquer trabalho. Nota: Devido ao onSaveInstanceState() não ser garantido de ser chamado, você deve usá-lo apenas para registrar o estado transiente da atividade (o estado da interface do usuário), você nunca deve usá-lo para armazenar dados persistentes. Em vez disso, você deve usar onPause() para armazenar dados persistentes (como os dados que devem ser salvos em um banco de dados) quando o usuário deixa a atividade. Uma boa maneira de testar a capacidade do seu aplicativo para restaurar seu estado é simplesmente girar o dispositivo para fazer alterações na orientação da tela. Quando da mudança de orientação da tela, o sistema destrói e recria a atividade a fim de aplicar recursos alternativos que possam estar disponíveis para a nova orientação. Por esta razão, é muito importante para sua atividade restaurar completamente o seu estado quando ele é recriado, pois os usuários regularmente giram a tela ao usar aplicações.
Manipulação de alterações na configuração Algumas configurações de dispositivo podem mudar durante a execução (tais como a orientação da tela, a disponibilidade de teclado e idioma). Quando essa mudança ocorre, o Android reinicia a atividade em execução (onDestroy() é chamado, seguido imediatamente por onCreate()). O comportamento reiniciar é projetado para ajudar a sua candidatura a se adaptar às novas configurações automaticamente recarregando a sua aplicação com recursos alternativos que você forneceu. Se você projeta sua atividade
ANDROID, uma visão geral – Anderson Duarte de Amorim
25
para lidar adequadamente com este evento, vai ser mais resistentes a eventos inesperados no ciclo de atividade. A melhor maneira de lidar com uma mudança de configuração, tais como uma mudança na orientação da tela, é simplesmente preservar o estado do seu aplicativo usando onSaveInstanceState() e onRestoreInstanceState() (ou onCreate() ), como discutido na seção anterior.
Coordenar as atividades Quando uma atividade começa outra, ambas experimentam as transições do ciclo de vida. A primeira atividade faz uma pausa e para (embora, não vai parar se ele ainda está visível ao fundo), enquanto a outra atividade é criada. Caso esses dados compartilham atividades salvas em disco ou em outro lugar, é importante entender que a primeira atividade não está completamente parada antes de a segunda ser criada. Pelo contrário, o processo de iniciar o segundo se sobrepõe ao processo de parar o primeiro. A ordem dos retornos do ciclo de vida é bem definida, especialmente quando as duas atividades estão no mesmo processo e está começando um do outro. Aqui está a ordem das operações que ocorrem quando a atividade A começa atividade B: 1. O método onPause() da atividade A é executado. 2. Os métodos onCreate(), onStart(), e onResume() de B são executados em seqüência. (Atividade B agora tem o foco do usuário.) 3. Então, se uma atividade não é mais visível na tela, a sua onStop() é executada. Esta seqüência previsível de callbacks do ciclo de vida permite-lhe gerir a transição de informações de uma atividade para outra. Por exemplo, se você deve escrever em um banco de dados quando a primeira atividade pára para que esta atividade pode lê-lo, então você deve escrever para o banco de dados durante onPause() em vez de durante onStop().
ANDROID, uma visão geral – Anderson Duarte de Amorim
26
Fragmentos Um Fragment representa um comportamento ou uma parte da interface de usuário em uma Activity. Você pode combinar vários fragmentos em uma única atividade para construir uma interface multi-painel e reutilização de um fragmento de atividades múltiplas. Você pode pensar em um fragmento como uma seção modular de uma atividade, que tem seu próprio ciclo de vida, recebe os seus próprios eventos de entrada, e que você pode adicionar ou remover, enquanto a atividade está em execução. Um fragmento deve sempre ser incorporado em uma atividade e o ciclo de vida do fragmento é diretamente afetado pelo ciclo de vida da atividade de acolhimento. Por exemplo, quando a atividade é interrompida, assim são todos os fragmentos nele, e quando a atividade é destruída, assim são todos os fragmentos. No entanto, enquanto uma atividade está em execução (que é na retomada do ciclo de vida do estado), você pode manipular cada fragmento de forma independente, como adicionar ou remover. Quando você executa uma operação deste tipo de fragmento, você também pode adicioná-la a uma pilha de volta que é gerenciado pela atividade de cada pilha de volta na entrada da atividade que é um registro da transação de fragmento que ocorreu. A volta da pilha permite que o usuário possa reverter uma transação (navegar para trás), pressionando a tecla BACK. Quando você adiciona um fragmento como uma parte do seu layout, ele vive em um ViewGroup dentro da view de hierarquia e define o seu próprio layout de pontos de vista. Você pode inserir um fragmento em seu layout declarando o fragmento na atividade de distribuição de arquivos, como <fragment>, ou a partir de seu código de aplicativo, adicionando-o a um já existente ViewGroup. No entanto, um fragmento não é obrigado a fazer parte do esquema de atividade, você também pode utilizar um fragmento como um trabalhador invisível para a atividade.
Filosofia de design Android apresenta fragmentos no Android 3.0 (API Level "Honeycomb"), principalmente para apoiar projetos mais dinâmicos e flexíveis de interface do usuário em telas grandes, como os Tablets. Como uma tela de tablet é muito maior do que a de um telefone, há mais espaço para combinar e trocar os componentes de interface do usuário. Fragmentos permitem tais projetos sem a necessidade de gerenciar mudanças
ANDROID, uma visão geral – Anderson Duarte de Amorim
27
complexas à hierarquia vista. Ao dividir o layout de uma atividade em fragmentos, você se torna capaz de modificar a aparência da atividade em tempo de execução e preservar essas mudanças em uma pilha de volta que é gerenciada pela atividade. Por exemplo, um aplicativo de notícias pode usar um fragmento para mostrar uma lista de artigos à esquerda e outro fragmento para mostrar um artigo à direita, então os fragmentos aparecem em uma atividade, lado a lado, e cada fragmento tem seu próprio conjunto do ciclo de vida, métodos callback e lidam com seus próprios eventos de entrada do usuário. Assim, em vez de usar uma atividade para selecionar um artigo e outra atividade para ler o artigo, o usuário pode selecionar um artigo e ler tudo dentro da mesma atividade, conforme ilustrado na figura 1.
Figura 1. Um exemplo de como dois módulos de interface do usuário que normalmente são separados em duas atividades podem ser combinados em uma atividade, utilizando fragmentos.
Um fragmento deve ser um componente modular e reutilizável em sua aplicação. Ou seja, porque o fragmento define o seu próprio layout e seu próprio comportamento, usando seu próprio ciclo de vida callbacks, você pode incluir um fragmento em múltiplas atividades. Isto é especialmente importante porque permite adaptar a sua experiência de usuário para diferentes tamanhos de tela. Por exemplo, você pode incluir vários fragmentos de uma atividade apenas quando o tamanho da tela é suficientemente grande, e, quando não é, lançar atividades distintas que utilizam diferentes fragmentos. Por exemplo, para continuar com o aplicativo de notícia, a aplicação pode inserir dois fragmentos da atividade, quando rodando em uma grande tela extra (um tablet, por exemplo). No entanto, em um tamanho de tela normal (um telefone, por exemplo), não há lugar suficiente para os dois fragmentos, de modo a Atividade A inclui somente o fragmento para a lista de artigos, e quando o usuário seleciona um artigo, ele começa a
ANDROID, uma visão geral – Anderson Duarte de Amorim
28
Atividade B, que inclui o fragmento para ler o artigo. Assim, a aplicação suporta os padrões de projeto sugerido na figura 1.
Criando um fragmento
Figura 2. O ciclo de vida de um fragmento (enquanto a sua atividade está em execução).
ANDROID, uma visão geral – Anderson Duarte de Amorim
29
Para criar um fragmento, você deve criar uma subclasse de Fragment (ou uma subclasse existente do mesmo). O código da classe Fragment se parece muito com uma Activity. Ele contém métodos de retorno semelhante a uma atividade, como onCreate(), onStart(), onPause(), e onStop(). Na verdade, se você está convertendo uma aplicação Android existentes para usar fragmentos, você pode simplesmente mover o código de métodos de retorno de sua atividade sobre os métodos de retorno de seus respectivos fragmentos. Normalmente, você deve implementar pelo menos os métodos do ciclo de vida a seguir: onCreate(): O sistema chama isso ao criar o fragmento. Dentro de sua aplicação, você deve inicializar os componentes essenciais do fragmento que pretende manter quando o fragmento é pausado ou parado, então retomado. onCreateView(): O sistema chama isso quando está na hora de extrair o fragmento de sua interface de usuário pela primeira vez. Para desenhar uma interface para o seu fragmento, você deve retornar um View a partir deste método que é a raiz do fragmento do seu layout. Você pode retornar nulo se o fragmento não fornece uma interface do usuário. onPause(): O sistema chama este método como o primeiro indício de que o usuário está saindo do fragmento (embora nem sempre significa que o fragmento está sendo destruído). Isso geralmente é onde você deve cometer quaisquer alterações que devem ser mantidas para além da sessão atual do usuário (porque o usuário pode não voltar). A maioria dos aplicativos devem implementar pelo menos estes três métodos para cada fragmento, mas existem vários métodos de retorno que você também deve usar para lidar com diferentes fases do ciclo de vida do fragmento. Todos os métodos de retorno do ciclo de vida são discutidos mais adiante, na seção sobre o manuseio do Ciclo de Vida do fragmento. Existem também algumas subclasses que você pode querer estender:
DialogFragment Mostra uma janela flutuante. Usar essa classe para criar uma caixa de diálogo é uma boa alternativa para usar os métodos auxiliares de diálogo na Activity, porque você pode incorporar um fragmento de diálogo para a volta da pilha de fragmentos gerido pela atividade, permitindo que o usuário retorne a um fragmento rejeitado. ANDROID, uma visão geral – Anderson Duarte de Amorim
30
ListFragment Exibe uma lista de itens que são gerenciados por um adaptador (como um SimpleCursorAdapter), semelhante ao ListActivity. Ele fornece diversos métodos para gerenciar uma lista, como o onListItemClick() de callback para manipular eventos de clique.
PreferenceFragment Exibe uma hierarquia de objetos Preference como uma lista, semelhante à PreferenceActivity. Isso é útil quando se cria um "settings" para sua aplicação.
Adicionando uma interface de usuário Um fragmento é normalmente usado como parte de uma atividade de interface de usuário e contribui com a sua própria disposição para a atividade. Para fornecer um layout de um fragmento, você deve implementar o onCreateView(), que o sistema Android chama quando é hora do fragmento ser desenhado no layout. A implementação deste método deve retornar um View que é a raiz do fragmento do seu layout. Nota: Se o fragmento é uma subclasse de ListFragment, a implementação padrão retorna um ListView de onCreateView(), então você não precisa implementá-lo. Para devolver um layout de onCreateView(), você pode retirá-lo a partir de um layout de recursos definidos em XML e o desenvolve. Para ajudá-lo a fazê-lo, onCreateView() fornece um LayoutInflater objeto. Por exemplo, aqui está uma subclasse de Fragment que carrega um layout a partir da example_fragment.xml: public static class ExampleFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.example_fragment, container, false); } }
ANDROID, uma visão geral – Anderson Duarte de Amorim
31
Criando um layout No exemplo acima, R.layout.example_fragment é uma referência a um recurso chamado layout example_fragment.xml salvo na aplicação dos recursos. O parâmetro passado para onCreateView() é o pai ViewGroup (da atividade do layout), em que o layout do fragmento será inserido. O parâmetro savedInstanceState é um Bundle que fornece dados sobre a instância anterior do fragmento, se o fragmento está sendo retomado. O método inflate() utiliza três argumentos: A identificação de recurso do layout que você deseja inserir. O ViewGroup ser o pai do layout já em utilização. Passando o container é importante para que o sistema possa aplicar os parâmetros de layout para o modo de exibição raiz do layout inflado, especificado pela posição do pai em que ele está indo. Um booleano que indica se o layout desenvolvido deverá ser anexado ao ViewGroup (segundo parâmetro) durante a chamada do procedimento inflate(). (Neste caso, isso é falso, porque o sistema já está inserindo o layout inflado no container de passagem verdade seria criar um grupo de vista redundantes no layout final.)
Adicionando um fragmento de uma atividade Normalmente, um fragmento contribui com uma parcela de UI para a atividade de acolhimento, que é incorporado como parte da hierarquia da visão da atividade de conjunto. Há duas maneiras com as quais você pode adicionar um fragmento para o layout de atividade: Declare o fragmento dentro atividade de layout do arquivo. Neste caso, você pode especificar propriedades de layout para o fragmento como se fosse uma exibição. Por exemplo, aqui está o arquivo de layout para uma atividade com dois fragmentos:
ANDROID, uma visão geral – Anderson Duarte de Amorim
32
<?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="match_parent"> <fragment android:name="com.example.news.ArticleListFragment" android:id="@+id/list" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" /> <fragment android:name="com.example.news.ArticleReaderFragment" android:id="@+id/viewer" android:layout_weight="2" android:layout_width="0dp" android:layout_height="match_parent" /> </LinearLayout>
O atributo android:name na <fragment> especifica o Fragment para instanciar no layout. Quando o sistema cria esse layout, ele instancia cada fragmento especificado no layout e chama o onCreateView() para cada um, para recuperar o layout de cada fragmento. O sistema insere a View retornada pelo fragmento diretamente no local do elemento <fragment>. Nota: Cada fragmento requer um identificador único que o sistema pode usar para restaurar o fragmento se a atividade for reiniciada (e que você pode usar para capturar o fragmento para realizar transações, como removê-lo). Existem três formas para fornecer uma identificação de um fragmento: o
Forneça o android:id com um ID único.
o
Forneça o android:tag com uma string única.
o
Se você não fornecer nenhum dos dois anteriores, o sistema utiliza a identificação de exibição de recipiente.
Ou então, programaticamente adicionar o fragmento de um já existente ViewGroup . A qualquer momento, enquanto sua atividade está sendo executada, você pode adicionar fragmentos ao seu layout. Você só precisa especificar um ViewGroup para colocar o fragmento.
ANDROID, uma visão geral – Anderson Duarte de Amorim
33
Para fazer transações em sua atividade (como adicionar, remover ou substituir um fragmento), você deve usar as APIs do FragmentTransaction. Você pode obter uma instância de FragmentTransaction de sua Activity como esta: FragmentManager fragmentManager = getFragmentManager() FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Você pode então adicionar um fragmento ao usar o método add(), especificando o fragmento a adicionar e a visão para inseri-lo. Por exemplo: ExampleFragment fragment = new ExampleFragment(); fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit();
O primeiro argumento passado para add() é o ViewGroup em que o fragmento deve ser colocado, especificado por identificação do recurso, e o segundo parâmetro é o fragmento a acrescentar. Depois que você fizer as alterações com FragmentTransaction , você deve chamar commit() para que as alterações tenham efeito.
Adicionando um fragmento sem uma interface de usuário (UI) Os exemplos acima mostram como adicionar um fragmento de sua atividade, a fim de fornecer uma interface do usuário. No entanto, você também pode usar um fragmento para fornecer um comportamento de fundo para a atividade sem a apresentação da interface do usuário. Para adicionar um fragmento sem uma interface de usuário, adicione o fragmento da atividade usando add(Fragment, String) (fornecimento de uma única seqüência de "tag" para o fragmento, ao invés de um ID). Isso adiciona o fragmento, mas, porque não está associada a um ponto de vista do layout atividade, ele não recebe uma chamada para onCreateView(). Assim você não precisa implementar esse método. Fornecendo uma tag string para o fragmento não é estritamente para os fragmentos nãoUI. Você também pode fornecer etiquetas de seqüência de fragmentos que possuem uma interface de usuário, mas se o fragmento não possui uma interface de usuário, a tag string é o único caminho para identificá-lo. Se você deseja obter o fragmento da atividade posterior, você precisa usar findFragmentByTag().
ANDROID, uma visão geral – Anderson Duarte de Amorim
34
Gerenciando fragmentos Para gerenciar os fragmentos em sua atividade, você precisará usar FragmentManager. Para obtê-lo, chame getFragmentManager() em sua atividade. Algumas coisas que você pode fazer com FragmentManager incluem: Obter fragmentos que existem na atividade, com findFragmentById() (para os fragmentos que fornecem uma interface de usuário no layout de atividade) ou findFragmentByTag() (para os fragmentos que fazem ou não uma interface do usuário). Retirar fragmentos da pilha, com popBackStack() (simulando um comando BACK pelo usuário). Registre-se um ouvinte de alteração de parte de trás da pilha, com addOnBackStackChangedListener(). Conforme demonstrado na seção anterior, você também pode usar FragmentManager para abrir uma FragmentTransaction, que lhe permite realizar transações, tais como adicionar e remover fragmentos.
Executando transações com fragmento Uma das grandes novidades sobre o uso de fragmentos em sua atividade é a capacidade de adicionar, remover, substituir e realizar outras ações com eles, em resposta à interação do usuário. Cada conjunto de alterações que comprometem a atividade é chamado de transação e você pode executar um usando APIs em FragmentTransaction. Você também pode salvar cada transação na pilha gerenciada pela atividade, permitindo ao usuário navegar para trás através das mudanças no fragmento (semelhante ao navegar para trás por meio de atividades). Você pode adquirir uma instância de FragmentTransaction do FragmentManager como este: FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Cada transação é um conjunto de mudanças que se deseja realizar, ao mesmo tempo. Você pode configurar todas as alterações que pretendem efetuar uma operação
ANDROID, uma visão geral – Anderson Duarte de Amorim
35
determinada utilizando métodos como add(), remove(), e replace(). Em seguida, para aplicar a operação para a atividade, você deve chamar commit(). Antes de chamar commit(), no entanto, você pode querer chamar addToBackStack(), a fim de acrescentar a operação a uma volta da pilha de transações. Esta volta na pilha é gerida pela atividade e permite que ao usuário retornar ao estado de fragmento anterior, pressionando a tecla BACK. Por exemplo, aqui está como você pode substituir um fragmento a outro e preservar o estado anterior da pilha de volta: // Create new fragment and transaction Fragment newFragment = new ExampleFragment(); FragmentTransaction transaction = getFragmentManager().beginTransaction(); // Replace whatever is in the fragment_container view with this fragment, // and add the transaction to the back stack transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // Commit the transaction transaction.commit();
Neste exemplo, newFragment substitui qualquer fragmento (se houver) atualmente no contêiner de layout identificado pelo R.id.fragment_container ID. Ao chamar addToBackStack(), é salvado na pilha de volta a operação para que o usuário possa anular a operação e trazer de volta o fragmento anterior pressionando a tecla BACK. Se você adicionar várias alterações à operação (como um outro add() ou remove()) e chamar addToBackStack(), então todas as mudanças aplicadas antes de chamar commit() são adicionados à volta da pilha como uma única operação e a tecla BACK irá inverter-los todos juntos. A ordem na qual você adiciona as alterações em um FragmentTransaction não importa, exceto: Você deve chamar commit() por último. Se você está adicionando vários fragmentos para o mesmo recipiente, então a ordem em que você adicioná-los determina a ordem em que aparecem na hierarquia.
ANDROID, uma visão geral – Anderson Duarte de Amorim
36
Se você não chamar addToBackStack() quando você executar uma operação que remove um fragmento, em seguida, esse fragmento é destruído quando a transação for confirmada e o usuário não pode navegar de volta para ela. Considerando que, se você chamar addToBackStack() quando da remoção de um fragmento, o fragmento é interrompido e será retomado se o usuário navega de volta. Dica: Para cada transação, você pode aplicar uma animação de transição, chamando setTransition() antes do commit(). Chamar commit() não executa a operação imediatamente. Em vez disso, ele agenda a execução no segmento da atividade de interface do usuário (a thread "main"), logo que o segmento for capaz de fazê-lo. Se necessário, no entanto, você pode chamar executePendingTransactions() no seu segmento de interface do usuário para executar imediatamente as operações apresentadas por commit(). Fazer isso geralmente não é necessário a menos que a transação é uma dependência para o emprego em outros segmentos. Cuidado: você pode cometer uma transação usando commit() apenas antes da atividade salvar seu estado (quando o usuário deixa a atividade). Se a tentativa for cometer depois desse ponto, uma exceção será lançada. Isso ocorre porque o estado, após a confirmação, pode ser perdido se a atividade precisa ser restaurada. Para as situações
em
que
não
tem
problema
você
perder
o
commit,
use
commitAllowingStateLoss().
Comunicando-se com a atividade Ainda que um Fragment seja implementado como um objeto que é independente de uma Activity e pode ser usado dentro de múltiplas atividades, uma determinada instância de um fragmento está diretamente ligada à atividade que o contém. Especificamente, o fragmento pode acessar a instância Activity com getActivity() e facilmente realizar tarefas como encontrar um ponto de vista do esquema de atuação: View listView = getActivity().findViewById(R.id.list);
Da mesma forma, sua atividade pode chamar métodos no fragmento através da aquisição de uma referência para o Fragment de FragmentManager, usando findFragmentById() ou findFragmentByTag(). Por exemplo:
ANDROID, uma visão geral – Anderson Duarte de Amorim
37
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
Criar callbacks evento para a atividade Em alguns casos, você pode precisar de um fragmento de compartilhar eventos com a atividade. Uma boa maneira de fazer isso é definir uma interface de retorno no interior do fragmento e exigem que a atividade de acolhimento implemente-a. Quando a atividade recebe uma chamada através da interface, ela pode compartilhar a informação com outros fragmentos no layout conforme necessário. Por exemplo, se um aplicativo de notícias tem dois fragmentos de uma atividade e um mostra uma lista de artigos (fragmento A) e outro mostra um artigo (fragmento B), então um fragmento deve informar a atividade quando um item da lista é escolhido de modo que pode dizer ao fragmento B para exibir o artigo. Neste caso, a interface OnArticleSelectedListener é declarada dentro de um fragmento: public static class FragmentA extends ListFragment { ... // Container Activity must implement this interface public interface OnArticleSelectedListener { public void onArticleSelected(Uri articleUri); } ... }
Em
seguida,
a
atividade
que
hospeda
o
fragmento
implementa
a
OnArticleSelectedListener e substitui onArticleSelected() para notificar o fragmento B do evento a partir do fragmento A. Para garantir que a atividade de acolhimento implemente essa interface, um fragmento do método onAttach() de retorno (que chama o sistema quando adicionando o fragmento para a atividade) instancia uma instância de OnArticleSelectedListener pelo casting da Activity que é passado para onAttach(): public static class FragmentA extends ListFragment { OnArticleSelectedListener mListener; ... @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mListener = (OnArticleSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener"); }
ANDROID, uma visão geral – Anderson Duarte de Amorim
38
} ... }
Se a atividade não tenha aplicado a interface, então o fragmento lança um ClassCastException. Em caso de sucesso, o membro mListener mantém uma referência para a implementação da atividade de OnArticleSelectedListener, de modo que um fragmento pode compartilhar eventos com a atividade, chamando os métodos definidos pela interface OnArticleSelectedListener. Por exemplo, se um fragmento é uma extensão do ListFragment, cada vez que o usuário clica em um item da lista, o sistema chama onListItemClick() no fragmento, o que chama onArticleSelected() para compartilhar o evento com a atividade: public static class FragmentA extends ListFragment { OnArticleSelectedListener mListener; ... @Override public void onListItemClick(ListView l, View v, int position, long id) { // Append the clicked item's row ID with the content provider Uri Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id); // Send the event and Uri to the host activity mListener.onArticleSelected(noteUri); } ... }
O parâmetro id passado para onListItemClick() é o ID da linha do item clicado, que a atividade (ou outro fragmento) utiliza para buscar o artigo a partir do aplicativo ContentProvider.
Adicionando itens à barra de ação Seus fragmentos podem contribuir itens de menu para a atividade do menu de opções (e, conseqüentemente, a Barra de ação) pela execução onCreateOptionsMenu(). Para que esse método receba chamadas, no entanto, você deve chamar setHasOptionsMenu() durante onCreate(), para indicar que o fragmento gostaria de adicionar itens ao menu de opções (caso contrário, o fragmento não irá receber uma chamada para onCreateOptionsMenu()). Os itens que você adicionar ao menu de opções do fragmento são acrescentados aos itens
de
menu
existente.
O
fragmento
também
recebe
callbacks
para
onOptionsItemSelected() quando um item de menu é selecionado.
ANDROID, uma visão geral – Anderson Duarte de Amorim
39
Você também pode registrar uma exibição em seu layout para fornecer um menu de contexto, chamando registerForContextMenu(). Quando o usuário abre o menu de contexto, o fragmento recebe uma chamada para onCreateContextMenu(). Quando o usuário
seleciona
um
item,
o
fragmento
recebe
uma
chamada
para
onContextItemSelected(). Nota: Embora o fragmento receba um on-item-selected na chamada de retorno para cada item de menu que acrescenta, a atividade é a primeira a receber o respectivo retorno quando o usuário seleciona um item de menu. Se a execução da atividade da chamada de retorno no item selecionado não lidar com o item selecionado, o evento é transmitido para retorno do fragmento. Isso é verdadeiro para o menu de opções e menus de contexto.
Manuseio do ciclo de vida do fragmento
Figura 3. O ciclo de vida afeta a atividade do ciclo de vida do fragmento.
ANDROID, uma visão geral – Anderson Duarte de Amorim
40
Gerenciar o ciclo de vida de um fragmento é um pouco como gerir o ciclo de vida de uma atividade. Como uma atividade, um fragmento pode existir em três estados: Retomado: O fragmento é visível na atividade de execução. Em pausa: Outra atividade está em primeiro plano e tem o foco, mas a atividade em que vive esse fragmento é ainda visível (a atividade do primeiro plano é parcialmente transparente ou não cobre a tela inteira). Parado: O fragmento não é visível. A atividade host foi parada ou o fragmento foi retirado da atividade, mas adicionado à volta da pilha. Um fragmento que parou ainda está vivo (todas as informações do estado e membro são mantidas pelo sistema). No entanto, já não é visível para o usuário e serão mortos se a atividade é morta. Também como uma atividade, você pode manter o estado de um fragmento com um Bundle, no caso de a atividade do processo ser morta e você precisar restaurar o estado do fragmento, quando a atividade é recriada. Você pode salvar o estado durante o fragmento onSaveInstanceState() de callback e restaurá-lo durante onCreate(), onCreateView() ou onActivityCreated(). A diferença mais significativa no ciclo de vida entre uma atividade e um fragmento é como são armazenados em suas respectivas pilhas. Uma atividade é colocada em uma pilha de volta das atividades que é gerido pelo sistema quando ela está parada, por padrão (para que o usuário possa navegar de volta a ele com a chave de volta, como discutido em Tarefas e pilha de volta). No entanto, um fragmento é colocado em uma pilha de volta gerido pela atividade de acolhimento somente quando você solicitar explicitamente que a instância deve ser salva chamando addToBackStack() durante uma operação que remove o fragmento. Gerenciar o ciclo de vida do fragmento é muito semelhante à gestão do ciclo de vida da atividade. Assim, as mesmas práticas de gestão do ciclo de vida de atividade também se aplicam aos fragmentos. O que você também precisa entender, porém, é como a vida da atividade afeta a vida do fragmento.
Coordenação com o ciclo de vida de atividade O ciclo de vida da atividade em que o fragmento de vida afeta diretamente o ciclo de vida do fragmento, da mesma forma que cada ciclo de retorno para a atividade resulta ANDROID, uma visão geral – Anderson Duarte de Amorim
41
em um retorno semelhante para cada fragmento. Por exemplo, quando a atividade recebe onPause(), cada fragmento na atividade recebe onPause(). Fragmentos têm alguns retornos do ciclo de vida extra, no entanto, para lidar com uma interação única com a atividade, a fim de realizar ações como construir e destruir UI do fragmento. Estes métodos de callback adicionais são: onAttach(): Chamado quando o fragmento foi associado com a atividade. onCreateView(): Chamado para criar a hierarquia de visão associada com o fragmento. onActivityCreated(): Chamado quando onCreate() da atividade foi retornado. onDestroyView(): Chamado quando a hierarquia de visão associada com o fragmento está sendo removida. onDetach(): Chamado quando o fragmento está sendo dissociado da atividade. O fluxo do ciclo de vida de um fragmento, como ele é afetado por sua atividade de acolhimento, é ilustrado pela figura 3. Nesta figura, você pode ver o que cada estado sucessivo da atividade que determina os métodos de retorno de um fragmento pode receber. Por exemplo, quando a atividade tenha recebido uma onCreate() de callback, um fragmento da atividade não recebe mais do que o onActivityCreated() de callback. Uma vez que a atividade atinge o estado retomado, você pode adicionar livremente e remover fragmentos na a atividade. Assim, somente quando a atividade está em estado de retomada, o ciclo de vida de um fragmento pode mudar de forma independente. No entanto, quando a atividade deixa o estado de retomada, o fragmento é novamente inserido através do ciclo de vida da atividade.
ANDROID, uma visão geral – Anderson Duarte de Amorim
42
Loaders Loaders tornam fácil carregar os dados de forma assíncrona, em uma atividade ou um fragmento. Loaders têm estas características: Eles estão disponíveis para cada Activity e Fragment. Eles fornecem carga assíncrona de dados. Eles monitoram a fonte de seus dados e entregam novos resultados quando muda o conteúdo. Eles reconectam automaticamente o cursor do gestor passado, quando está sendo recriado após uma mudança de configuração. Assim, eles não precisam de voltar a consultar os seus dados.
Resumo API Loader Existem várias classes e interfaces que podem ser envolvidos na utilização de carregadores em um aplicativo. Os resultados estão resumidos nesta tabela: Classe/Interface LoaderManager
Descrição Uma classe abstrata associada a uma Activity ou Fragment para gerenciar uma ou mais instâncias Loader. Isso ajuda a gerenciar um pedido de execução de operações já em conjunto com o ciclo de vida de Activity ou Fragment, o uso mais comum deste é com um CursorLoader, no entanto as aplicações são livres para escrever seus próprios loaders para carregar outros tipos de dados. Há apenas um LoaderManager por atividade ou fragmento. Mas um LoaderManager pode ter vários carregadores.
LoaderManager.LoaderCallbacks
Uma interface de retorno de um cliente para interagir com o LoaderManager. Por exemplo, você usar o onCreateLoader() para criar um carregador novo.
ANDROID, uma visão geral – Anderson Duarte de Amorim
43
Loader
Uma classe abstrata que executa o carregamento assíncrono de dados. Esta é a classe base para um gestor. Você usaria normalmente CursorLoader, mas você pode implementar sua própria subclasse. Enquanto os carregadores estão ativos, eles devem acompanhar a fonte de seus dados e apresentar resultados novos quando alterar o conteúdo.
AsyncTaskLoader
Carregador abstrato que provê AsyncTansk para o trabalho.
CursorLoader
Uma subclasse de AsyncTaskLoader que consulta o ContentResolver e retorna um Cursor. Essa classe implementa o protocolo Loader de uma forma padrão para consultar cursores, com base em AsyncTaskLoader para realizar a consulta de cursor em uma discussão de fundo para que ele não bloqueie os aplicativo de interface do usuário. Utilizar este carregador é a melhor maneira de carregar os dados de forma assíncrona a partir de um ContentProvider, ao invés de realizar uma consulta gerida através do fragmento ou de APIs.
As classes e interfaces na tabela acima são os componentes essenciais que você vai usar para implementar um carregador em sua aplicação. Você não vai precisar de todos eles para cada gestor, mas você sempre precisa de uma referência ao LoaderManager para inicializar um carregador e uma implementação de um Loader, como CursorLoader. As seções a seguir mostram como usar essas classes e interfaces em uma aplicação.
Usando carregadores em um aplicativo Um aplicativo que usa carregadores normalmente inclui o seguinte: Uma Activity ou Fragment. Uma instância da LoaderManager.
ANDROID, uma visão geral – Anderson Duarte de Amorim
44
Um CursorLoader para carregar dados apoiado por uma ContentProvider. Alternativamente, você pode implementar sua própria subclasse de Loader ou AsyncTaskLoader para carregar dados de alguma outra fonte. Uma implementação para LoaderManager.LoaderCallbacks. Isto é onde você cria novos loaders e gerencia suas referências aos carregadores existentes. Uma
maneira
de
mostrar
o
carregador
de
dados,
como
um
SimpleCursorAdapter. Uma fonte de dados, como um ContentProvider, ao usar um CursorLoader .
Iniciando um Loader O LoaderManager gerencia um ou mais instâncias Loader dentro de uma Activity ou Fragment. Há apenas um LoaderManager por atividade ou fragmento. Normalmente, você inicializa um Loader com o método onCreate() dentro da atividade ou método onActivityCreated() dentro do fragmento. Você pode fazer isso da seguinte forma: // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this);
O método initLoader() utiliza os seguintes parâmetros: Um ID exclusivo que identifica o carregador. Neste exemplo, a identificação é 0. Os argumentos opcionais para fornecer ao loader a construção (null neste exemplo). A execução de LoaderManager.LoaderCallbacks, em que a LoaderManager é chamada para relatar eventos carregador. Neste exemplo, a classe local implementa a interface LoaderManager.LoaderCallbacks, assim que passa uma referência para si, this.
ANDROID, uma visão geral – Anderson Duarte de Amorim
45
A chamada ao initLoader() assegura que um carregador é inicializado e ativo. Ele tem dois resultados possíveis: Se o carregador especificado pelo ID já existe, o último carregador criado é reutilizado. Se o carregador especificado pela ID não existir, initLoader() aciona o método LoaderManager.LoaderCallbacks em onCreateLoader(). Isto é onde você implementa o código para instanciar e retornar um carregador novo. Em ambos os casos, a aplicação determinada LoaderManager.LoaderCallbacks está associada com o carregador, e será chamada quando o estado muda carregador. Se no momento da chamada, o chamador está em seu estado inicial e o carregador solicitado já existe e tem gerado os seus dados, o sistema solicita onLoadFinished() (durante initLoader()), então você deve estar preparado para isso acontecer. Observe que o método initLoader() retorna o Loader que é criado, mas você não precisa capturar uma referência a ele. O LoaderManager gerencia a vida do carregador automaticamente. O LoaderManager inicia e pára de carregar quando necessário, e mantém o estado do carregador e do seu conteúdo associado. Isso implica que você raramente interage com carregadores diretamente. É mais comumente usar o LoaderManager.LoaderCallbacks para intervir no processo de carregamento quando ocorrem eventos específicos.
Reiniciando o Loader Quando você usa initLoader(), como mostrado acima, ele usa um carregador existente com a identificação especificada, se houver. Se não houver, ele cria um. Mas às vezes você deseja descartar os dados antigos e começar de novo. Para descartar os dados antigos, use restartLoader(). Por exemplo, essa implementação de SearchView.OnQueryTextListener reinicia o carregador quando o usuário muda de consulta. O loader precisa ser reiniciado para que ele possa usar a pesquisa de revisão de filtro para fazer uma nova consulta:
ANDROID, uma visão geral – Anderson Duarte de Amorim
46
public boolean onQueryTextChanged(String newText) { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true; }
Usando callbacks do LoaderManager LoaderManager.LoaderCallbacks é uma interface de callback que permite que um cliente interaja com o LoaderManager . Loaders, em especial o CursorLoader, são esperados para reter seus dados depois de ser interrompido. Isso permite aos aplicativos que mantenham seus dados através dos métodos onStop() e onStart() da atividade ou fragmento, de modo que quando os usuários retornam a um pedido, eles não tem que aguardar os dados para recarregarem. Você usa o método LoaderManager.LoaderCallbacks quando quer saber quando criar um carregador novo, e para dizer a aplicação quando é hora de parar de usar um gerenciador de dados. LoaderManager.LoaderCallbacks inclui os seguintes métodos: onCreateLoader() - instancia e retorna um novo Loader para o ID dado. onLoadFinished() - Chamado quando um loader criado anteriormente terminou sua carga. onLoaderReset() - Chamado quando um loader criado anteriormente está sendo redefinido, tornando os dados disponíveis. Esses métodos são descritos em detalhes nas seções seguintes. onCreateLoader Quando você tenta acessar um loader (por exemplo, através initLoader()), ele verifica se o carregador especificado pelo ID existe. Se isso não ocorrer, ele aciona o método onCreateLoader() do LoaderManager.LoaderCallbacks. Isto é onde você irá criar um carregador novo. Normalmente, esse será um CursorLoader, mas você pode implementar sua própria subclasse Loader.
ANDROID, uma visão geral – Anderson Duarte de Amorim
47
Neste exemplo, o onCreateLoader() cria um método de retorno CursorLoader. Você deve construir o CursorLoader usando o método construtor, que exige um conjunto completo
de
informações
necessárias
para
realizar
uma
consulta
para
o
ContentProvider. Especificamente, é necessário: URI - A URI para o conteúdo para recuperar. projeção - uma lista de quais colunas retornar. Passando null irá retornar todas as colunas, que é ineficiente. seleção - Um filtro que declara que as linhas de retorno, formatado como uma cláusula WHERE SQL (excluindo o próprio WHERE). Passando null retornará todas as linhas para o URI especificado. selectionArgs - Você pode incluir ?s na seleção, que serão substituídas pelos valores da selectionArgs, na ordem em que aparecem na seleção. Os valores serão vinculados como Strings. SortOrder - Como adquirir as linhas, formatado como uma cláusula ORDER BY de SQL (excluindo-se o ORDER BY). Passando null usará a ordem de classificação padrão, que pode ser desordenada. // If non-null, this is the current filter the user has provided. String mCurFilter; ... public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); }
ANDROID, uma visão geral – Anderson Duarte de Amorim
48
onLoadFinished Este método é chamado quando um loader criado anteriormente terminou sua carga. Este método é garantido para ser chamado antes do lançamento do último dado que foi fornecido para este carregador. Neste ponto, você deve remover todo uso dos dados antigos (desde que será lançado em breve), mas não deve fazer a seu próprio lançamento dos dados desde o seu carregador é o dono e vai cuidar disso. O carregador vai lançar os dados, uma vez que conhece que o aplicativo não está mais usando. Por exemplo, se os dados são um cursor de um CursorLoader, você não deve chamar close() sobre ele mesmo. Se o cursor está sendo colocado em um CursorAdapter, você deve usar o método swapCursor() para que o antigo Cursor não seja fechado. Por exemplo: // This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; ... public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data); }
onLoaderReset Este método é chamado quando um loader criado anteriormente está sendo redefinido, tornando os seus dados indisponíveis. Este retorno permite saber quando o dado está prestes a ser liberado assim você pode remover a referência a ele. Esta aplicação chama swapCursor() com um valor null: // This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; ... public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null); }
ANDROID, uma visão geral – Anderson Duarte de Amorim
49
Exemplo Como exemplo, aqui é a implementação completa de um Fragment que apresenta um ListView com os resultados de uma consulta contra o provedor de conteúdo contatos. Ele usa um CursorLoader para gerenciar a consulta do fornecedor. Para uma aplicação para acessar os contatos de um usuário, como mostrado neste exemplo, o manifesto deve incluir a permissão READ_CONTACTS . public static class CursorLoaderListFragment extends ListFragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { // This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; // If non-null, this is the current filter the user has provided. String mCurFilter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Give some text to display if there is no data. In a real // application this would come from a resource. setEmptyText("No phone numbers"); // We have a menu item to show in action bar. setHasOptionsMenu(true); // Create an empty adapter we will use to display the loaded data. mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_2, null, new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, new int[] { android.R.id.text1, android.R.id.text2 }, 0); setListAdapter(mAdapter); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Place an action bar item for searching. MenuItem item = menu.add("Search"); item.setIcon(android.R.drawable.ic_menu_search); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); SearchView sv = new SearchView(getActivity()); sv.setOnQueryTextListener(this); item.setActionView(sv); } public boolean onQueryTextChange(String newText) {
ANDROID, uma visão geral – Anderson Duarte de Amorim
50
// Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true; } @Override public boolean onQueryTextSubmit(String query) { // Don't care about this. return true; } @Override public void onListItemClick(ListView l, View v, int position, long id) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: " + id); } // These are the Contacts rows that we will retrieve. static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS, Contacts.CONTACT_PRESENCE, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY, }; public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); } public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data); }
ANDROID, uma visão geral – Anderson Duarte de Amorim
51
public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null); } }
ANDROID, uma visĂŁo geral â&#x20AC;&#x201C; Anderson Duarte de Amorim
52
Tarefas e pilha de execução Um aplicativo normalmente contém várias atividades. Cada atividade deve ser concebida em torno de um tipo específico de ação que o usuário pode realizar e pode iniciar outras atividades. Por exemplo, um aplicativo de e-mail pode ter uma atividade para mostrar uma lista de e-mail. Quando o usuário seleciona um e-mail, uma nova atividade é aberta para ver o e-mail. Uma atividade pode até iniciar atividades que existem em outras aplicações no dispositivo. Por exemplo, se sua aplicação quer enviar um e-mail, você pode definir a intenção de realizar um "send" e incluir alguns dados, tais como um endereço de e-mail e uma mensagem. Uma atividade de outra aplicação que se declara para lidar com este tipo de intenção, em seguida, é aberta. Neste caso, a intenção é enviar um e-mail, assim a atividade de "compor" e-mail começa (se múltiplas atividades apóiam a mesma intenção, então o sistema permite ao usuário selecionar qual usar). Quando o email é enviado, sua atividade é retomada e parece como se a atividade de e-mail é parte do seu aplicativo. Mesmo que as atividades podem ser de diferentes aplicações, o Android mantém essa experiência do usuário uniforme, mantendo as duas atividades na mesma tarefa. Uma tarefa é um conjunto de atividades que os usuários interagem ao realizar um determinado trabalho. As atividades são organizadas em uma pilha (a "pilha de volta"), na ordem em que cada atividade é aberta. A tela inicial é o ponto de partida para a maioria das tarefas. Quando o usuário toca num ícone na tela do aplicativo (ou um atalho na tela inicial), essa tarefa vem para o primeiro plano. Se existe uma tarefa para a aplicação (o pedido não tenha sido usado recentemente), então uma nova tarefa é criada e a atividade "principal" abre como a atividade da raiz na pilha. Quando a atividade atual começa outra, a nova atividade é empurrada na parte superior da pilha e ganha foco. A atividade anterior permanece na pilha, mas está parada. Quando uma atividade termina, o sistema mantém o estado atual de sua interface de usuário. Quando o usuário pressiona a tecla BACK, a atividade atual é retirada da parte superior da pilha (a atividade é destruída) e a atividade anterior recomeça (o estado anterior de sua interface é restaurado). Atividades na pilha nunca são reorganizadas, só ANDROID, uma visão geral – Anderson Duarte de Amorim
53
incluídas e excluídas da pilha - inseridas na pilha quando iniciado pela atividade atual e retiradas quando o usuário deixa-as usando a tecla BACK. Como tal, a parte de trás da pilha funciona como uma estrutura de objetos "last in, first out". A Figura 1 mostra esse comportamento com uma linha do tempo mostrando o progresso entre as atividades junto com a atual pilha de volta em cada momento.
Figura 1. Uma representação de como cada nova atividade em uma tarefa adiciona um item na parte de trás da pilha. Quando o usuário pressiona a tecla BACK, a atividade atual é destruída e volta à atividade anterior.
Se o usuário continuar a pressionar BACK, então cada atividade da pilha é retirada para revelar a anterior, até que o usuário retorna à tela inicial (ou de qualquer atividade que estava sendo executada quando a tarefa começou). Quando todas as atividades são removidas da pilha, a tarefa não existe mais.
Figura 2. Duas tarefas tarefa estão no fundo, esperando para ser retomado, enquanto a Tarefa B recebe interação do usuário em primeiro plano.
ANDROID, uma visão geral – Anderson Duarte de Amorim
54
Figura 3. A única atividade é instanciada várias vezes.
Uma tarefa é uma unidade coesa, que pode passar para o "background" quando os usuários começam uma nova tarefa ou vão para a tela inicial, através da tecla HOME. Enquanto no fundo, todas as atividades na tarefa estão paradas, mas a pilha de volta para a tarefa permanece intacta, a tarefa simplesmente perdeu o foco enquanto outra tarefa se realiza como mostrado na figura 2. Uma tarefa pode, em seguida, voltar ao "primeiro plano" para que os usuários possam continuar de onde pararam. Suponha, por exemplo, que a tarefa atual (Tarefa A) tenha três atividades em sua pilha e dois no âmbito da atividade corrente. O usuário pressiona a tecla HOME, e em seguida, inicia uma nova aplicação a partir do lançador de aplicação. Quando a tela inicial aparece, uma tarefa vai para o fundo. Quando inicia o novo aplicativo, o sistema inicia uma tarefa para a aplicação (Tarefa B) com sua própria pilha de atividades. Após a interação com esse aplicativo, o usuário volta para HOME novamente e seleciona o aplicativo que originalmente começou Tarefa A. Agora, a tarefa A vem para o primeiro plano - todas as três atividades em sua pilha estão intactas e as atividades no topo da pilha são retomadas. Neste ponto, o usuário também pode voltar à Tarefa B indo para a HOME e selecionando o ícone do aplicativo que iniciou essa tarefa (ou tocando e segurando a tecla HOME para revelar as tarefas recentes e selecionando uma). Este é um exemplo de multitarefa no Android. Nota: Várias tarefas podem ser realizadas no fundo de uma vez. No entanto, se o usuário estiver executando muitas tarefas em segundo plano ao mesmo tempo, o sistema pode começar a destruir as atividades do fundo, a fim de recuperar a memória, fazendo com que os estados de atividade possam ser perdidos. Como as atividades na parte de trás da pilha nunca são reorganizadas, se seu aplicativo permite que usuários iniciem uma atividade específica de mais de uma atividade, uma
ANDROID, uma visão geral – Anderson Duarte de Amorim
55
nova instância daquela atividade é criada e colocada na pilha (ao invés de trazer qualquer instância anterior da atividade para o topo). Como tal, uma atividade em seu aplicativo pode ser instanciada várias vezes (mesmo de diferentes tarefas), como mostrado na figura 3. Como tal, se o usuário navega para trás usando a tecla BACK, cada instância da atividade é revelada na ordem em que foi aberta (cada um com seu estado próprio de UI). No entanto, você pode modificar esse comportamento se você não quer uma atividade a ser instanciada mais de uma vez. Para resumir o comportamento padrão de atividades e tarefas: Quando a atividade A começa atividade B, uma atividade é interrompida, mas o sistema mantém o seu estado (como a posição de rolagem e texto inseridos em formulários). Se o usuário pressiona a tecla de volta, enquanto na Atividade B, a atividade A continua com o seu estado restaurado. Quando o usuário deixa uma tarefa, pressionando a tecla HOME, a atividade em curso é interrompida e sua tarefa vai para o fundo. O sistema mantém o estado de cada atividade na tarefa. Se o usuário depois recomeça a tarefa de selecionar o ícone do lançador, que começou a tarefa, ela vem para o primeiro plano e retoma a atividade no topo da pilha. Se o usuário pressionar a tecla BACK, a atividade atual é removida da pilha e destruída. A atividade anterior na pilha é retomada. Quando uma atividade é destruída, o sistema não mantém atividade do Estado. As atividades podem ser instanciadas várias vezes, até mesmo de outras tarefas.
Salvando estado de atividade Como discutido acima, o comportamento padrão do sistema preserva o estado de uma atividade quando está parada. Desta forma, quando os usuários navegam de volta para uma atividade anterior, sua interface parece do jeito que deixou. No entanto, você pode, e deve, de forma proativa manter o estado de suas atividades através de métodos de retorno, caso a atividade seja destruída e deve ser recriada. Quando o sistema pára uma de suas atividades (como quando inicia uma nova atividade ou movimenta as tarefas para o fundo), o sistema poderia destruir completamente essa atividade se ele precisa recuperar a memória do sistema. Quando isso acontece, as
ANDROID, uma visão geral – Anderson Duarte de Amorim
56
informações sobre o estado da atividade são perdidas. Se isso acontecer, o sistema ainda sabe que a atividade tem um lugar na parte de trás da pilha, mas quando a atividade é trazida para o topo da pilha, o sistema deve recriá-la (em vez de retomá-la). A fim de evitar a perda do trabalho do usuário, você deve mantê-la de forma proativa através da aplicação do método onSaveInstanceState() de retorno de sua atividade.
Gerenciando tarefas A forma como o Android gerencia as tarefas e a pilha de volta, como descrito acima colocando todas as atividades que começaram sucessivamente na mesma tarefa e em uma pilha "last in, first out" - funciona muito bem para a maioria das aplicações e você não deve se preocupar em como suas atividades estão associadas a tarefas ou como eles existem na parte de trás da pilha. No entanto, você pode decidir que você deseja interromper o comportamento normal. Talvez você queira uma atividade em seu aplicativo para iniciar uma nova tarefa quando é iniciada (em vez de ser colocada dentro da tarefa atual), ou, quando você começa uma atividade, que pretende apresentar uma instância existente da mesma (em vez de criar uma nova instância no topo da pilha de volta), ou, você quer a sua pilha para ser limpas de todas as activitiesstart, com exceção para a atividade de raiz quando o usuário deixa a tarefa. Você pode fazer essas coisas e mais, com atributos no manifesto da <activity> e com bandeiras de intenção que você passa para startActivity(). Neste sentido, os principais atributos de <activity> que você pode usar são: taskAffinity launchMode allowTaskReparenting clearTaskOnLaunch alwaysRetainTaskState finishOnTaskLaunch E as principais bandeiras de intenção você pode usar são: FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_CLEAR_TOP FLAG_ACTIVITY_SINGLE_TOP ANDROID, uma visão geral – Anderson Duarte de Amorim
57
Atenção: A maioria dos aplicativos não deve interromper o comportamento padrão de atividades e tarefas. Se você determinar que seja necessário para a sua atividade modificar o comportamento padrão, tenha cuidado e não se esqueça de testar a usabilidade da atividade durante o lançamento e quando se navega de volta a ele de outras atividades e tarefas com a tecla BACK. Certifique-se de teste para os comportamentos de navegação que possam ser incompatíveis com o comportamento esperado do usuário.
Definição de modos de lançamento Modos de Lançamento permitem que a definir como uma nova instância de uma atividade está associada à tarefa atual. Você pode definir diferentes modos de lançamento de duas maneiras: Usando o arquivo de manifesto o Quando você declarar uma atividade em seu arquivo de manifesto, você pode especificar como a atividade deve se associar com as tarefas quando é iniciada. Usando as opções de Intenções o Quando você chamar startActivity(), você pode incluir uma bandeira na Intent que declara como (ou se) a nova atividade deve associar com a tarefa atual. Como tal, se a atividade A inicia a atividade B, a atividade B pode definir no seu manifesto de como ele deve se associar com a tarefa atual (caso exista) e uma atividade também pode pedir o quanto a atividade B deve associar com a tarefa atual. Se ambas as atividades definem como a atividade B deve associar com uma tarefa, então a solicitação da atividade A (como definido na intenção) é honrada sobre o pedido da atividade B (como definido no seu manifesto). Nota: Alguns dos modos disponíveis no lançamento do manifesto não estão disponíveis como sinalizadores para uma intenção e, também, alguns modos de lançamento disponível como sinalizadores para a intenção pode não ser definida no manifesto.
ANDROID, uma visão geral – Anderson Duarte de Amorim
58
Usando o arquivo de manifesto Quando declarar uma atividade em seu arquivo de manifesto, você pode especificar como a atividade deve associar com uma tarefa usando o atributo launchMode do elemento <activity>. O atributo launchMode especifica uma instrução sobre como a atividade deve ser lançada em uma tarefa. Há quatro modos diferentes de lançamento você pode atribuir ao atributo launchMode: "standard" (o modo padrão) Padrão. O sistema cria uma nova instância da atividade na tarefa de que foi iniciado e mapeia a intenção a ele. A atividade pode ser instanciada várias vezes, cada instância pode pertencer a diferentes tarefas e uma tarefa pode ter múltiplas instâncias. "singleTop" Se uma instância da atividade já existe no topo da tarefa atual, o sistema mapeia da estância através de uma chamada para o seu método onNewIntent(), ao invés de criar uma nova instância da atividade. A atividade pode ser instanciada várias vezes, cada instância pode pertencer a diferentes tarefas e uma tarefa pode ter múltiplas instâncias (mas só se a atividade na parte superior da pilha de retorno não é uma instância existente da atividade). Por exemplo, suponha que uma tarefa que está na pilha consiste de uma atividade de raiz A com as atividades B, C e D no topo (a pilha é ABCD, D está no topo). A intenção chega para uma atividade do tipo D. Se D tem o modo de lançamento padrão "standard", uma nova instância da classe é lançada e se torna a pilha ABCDD. No entanto, se D está no modo de lançamento "singleTop", a instância existente do D é entregue à intenção por meio onNewIntent(), porque está no topo da pilha, a pilha continua ABCD. No entanto, se a intenção chega para uma atividade do tipo B, então, uma nova instância de B é adicionada à pilha, mesmo se o seu modo de lançamento é "singleTop". Nota: Quando uma nova instância de uma atividade é criada, o usuário pode pressionar a tecla Back para retornar à atividade anterior. Mas quando uma ANDROID, uma visão geral – Anderson Duarte de Amorim
59
instância existente de uma atividade lida com uma nova intenção, o usuário não poderá pressionar a tecla Voltar para retornar ao estado da atividade antes da nova intenção chegaram a onNewIntent(). "singleTask" O sistema cria uma nova tarefa e instancia a atividade na raiz da nova tarefa. No entanto, se uma instância da atividade já existe em uma tarefa separada, o sistema mapeia a intenção da instância existente através de um convite à sua onNewIntent(), ao invés de criar uma nova instância. Apenas uma instância da atividade pode existir ao mesmo tempo. Nota: Embora a atividade começe em uma nova tarefa, a tecla BACK ainda retorna o usuário para a atividade anterior. "singleInstance" . O mesmo que "singleTask", exceto que o sistema não inicia qualquer outra atividade para a tarefa, segurando a instância. A atividade é sempre e único membro dessa tarefa; quaisquer atividades iniciadas por este abrem um em uma tarefa separada. Como outro exemplo, o navegador Android declara que a atividade do navegador web deve sempre aberta em sua própria tarefa, especificando o modo de lançamento singleTask no elemento <activity>. Isto significa que, se o aplicativo emite a intenção de abrir o navegador do Android, a sua atividade não é colocada na mesma tarefa que a sua aplicação. Em vez disso, ou uma nova tarefa para o navegador é iniciada ou, se o navegador já tem uma tarefa em execução em segundo plano, essa tarefa é antecipada para lidar com a nova intenção. Independentemente de uma atividade ser iniciada em uma nova tarefa ou na mesma tarefa como a atividade que começou, a tecla BACK sempre leva o usuário para a atividade anterior. Entretanto, se você iniciar uma atividade de sua tarefa (Tarefa A), que especifica o modo de lançamento sendo singleTask, então a atividade pode ter uma instancia em background que pertence a uma tarefa com a sua própria pilha de volta (Tarefa B). Neste caso, quando a tarefa B é antecipada para lidar com uma nova
ANDROID, uma visão geral – Anderson Duarte de Amorim
60
intenção, a tecla BACK navega de volta através das atividades na tarefa B antes de retornar à atividade do topo da tarefa A. Figura 4 visualiza este tipo de cenário.
Figura 4. A representação de como uma atividade com o modo de lançar "singleTask" é adicionada à pilha de volta. Se a atividade já faz parte de uma tarefa em segundo plano com a sua própria pilha de volta (Tarefa B), então toda a volta da pilha também vem para a frente, em cima da tarefa atual (Tarefa A).
Nota: O comportamento que você especificar para a sua atividade com a launchMode pode ser anulado por bandeiras incluídas com a intenção de iniciar a sua atividade, como discutido na próxima seção.
Usando as opções de intenções Ao iniciar uma atividade, você pode modificar o padrão de associação de uma atividade, incluindo na intenção que você entrega a startActivity(). As bandeiras que podem ser usadas para modificar o comportamento padrão são: FLAG_ACTIVITY_NEW_TASK Iniciar a atividade em uma nova tarefa. Se a tarefa já está em execução para a atividade que você está começando agora, essa tarefa é levada para o primeiro plano com o seu último estado restaurado e a atividade recebe a nova intenção em onNewIntent().
ANDROID, uma visão geral – Anderson Duarte de Amorim
61
Isso produz o mesmo comportamento que a "singleTask", discutido na seção anterior. FLAG_ACTIVITY_SINGLE_TOP Se a atividade que está sendo iniciado é a atividade atual (no topo da pilha de volta), então a instância existente recebe uma chamada para onNewIntent(), em vez de criar uma nova instância da atividade. Isso produz o mesmo comportamento que a "singleTop", discutido na seção anterior. FLAG_ACTIVITY_CLEAR_TOP Se a atividade a ser iniciada já está em execução na tarefa atual, então ao invés de lançar uma nova instância da atividade, todas as outras atividades em cima dela são destruídas e essa intenção é entregue à instância retomada da atividade (agora no topo, através onNewIntent()). Não há nenhum valor para o launchMode que produz esse comportamento. FLAG_ACTIVITY_CLEAR_TOP é mais freqüentemente utilizado em conjunto com FLAG_ACTIVITY_NEW_TASK. Quando usados em conjunto, essas bandeiras são uma maneira de localizar uma atividade existente em outra tarefa e colocá-la em uma posição onde ela pode responder à intenção. Nota: Se o modo de lançamento da atividade designada é "standard", ela também é removida da pilha e uma nova instância é lançada em seu lugar para lidar com o intuito de entrada. Isso porque uma nova instância é sempre criada para uma nova intenção, quando a modalidade de lançamento é "standard”.
Manipulação de afinidades A afinidade indica a qual tarefa uma atividade prefere pertencer. Por padrão, todas as atividades da mesma aplicação têm afinidade entre si. Então, por padrão, todas as atividades no mesmo aplicativo preferem estar na mesma tarefa. No entanto, você pode modificar o padrão de afinidade para uma atividade. Atividades definidas em diferentes aplicações podem compartilhar uma afinidade, ou atividades definidas no mesmo aplicativo podem ter diferentes afinidades de tarefas.
ANDROID, uma visão geral – Anderson Duarte de Amorim
62
Você pode modificar a afinidade de uma atividade específica com o atributo taskAffinity do <activity> elemento. O atributo taskAffinity tem um valor de seqüência, que deve ser exclusivo do nome do pacote padrão declarada no elemento <manifest>, porque o sistema usa esse nome para identificar a afinidade de tarefas padrão para o aplicativo. A afinidade entra em jogo em duas circunstâncias: Quando
a
intenção
que
inicia
uma
atividade
contém
FLAG_ACTIVITY_NEW_TASK. o Uma nova atividade é, por padrão, lançada na tarefa da atividade que chamou startActivity(). É empurrada para o topo da pilha do chamador. No entanto,
se
a
intenção
passada
para
startActivity()
contém
o
FLAG_ACTIVITY_NEW_TASK, o sistema procura por uma tarefa diferente para abrigar a nova atividade. Muitas vezes, é uma nova tarefa. No entanto, ele não tem que ser. Se já existe uma tarefa, com a mesma afinidade que a nova atividade, a atividade é lançada nessa tarefa. Se não, ele começa uma nova tarefa. o Se este sinalizador faz uma atividade para iniciar uma nova tarefa e que o usuário pressiona a tecla HOME para deixá-lo, deve haver alguma maneira para o usuário navegar de volta para a tarefa. Algumas entidades (como o gerente de comunicação) sempre iniciam as atividades em uma tarefa externa, nunca como parte de si própria, assim eles sempre colocam FLAG_ACTIVITY_NEW_TASK nas intenções para passar a startActivity(). Se você tiver uma atividade que pode ser invocada por uma entidade externa que possa usar esta bandeira, tome cuidado para que o usuário tenha uma maneira independente para voltar para a tarefa que começou como um ícone do lançador (a atividade radicular da tarefa tem uma intenção de filtro CATEGORY_LAUNCHER). Quando uma atividade tem seu atributo allowTaskReparenting definido como "true". Neste caso, a atividade pode se mover da tarefa que começou para a tarefa que tem afinidade, quando essa tarefa vem em primeiro plano.
ANDROID, uma visão geral – Anderson Duarte de Amorim
63
Por exemplo, suponha que uma atividade que relata as condições meteorológicas em cidades selecionadas é definida como parte de um aplicativo de viagens. Ele tem a mesma afinidade como outras atividades na mesma aplicação (a afinidade aplicativo padrão) e permite que re-parentalidade com esse atributo. Quando uma de suas atividades inicia a atividade de reportar o tempo, inicialmente pertencente à mesma tarefa que a sua atividade. No entanto, quando a tarefa da aplicação de viagens volta ao primeiro plano, a atividade de reportar oo tempo é designada para essa tarefa e exibida dentro dela. Dica: Se um arquivo .apk contiver mais de uma "aplicação" do usuário, você provavelmente vai querer usar o atributo taskAffinity para atribuir diferentes afinidades para as atividades associadas a cada "pedido".
Limpando a pilha de volta Se o usuário deixar uma tarefa por um longo tempo, o sistema cancela a tarefa de todas as atividades exceto a atividade de raiz. Quando o usuário retorna para a tarefa novamente, somente a atividade da raiz é restaurada. O sistema se comporta desta maneira, porque, depois de um longo período de tempo, os usuários provavelmente abandonaram o que faziam antes e estão retornando para a tarefa para começar algo novo. Há alguns atributos de atividade que você pode usar para modificar esse comportamento: alwaysRetainTaskState Se este atributo é definido como "true" na atividade de raiz de uma tarefa, o comportamento padrão que acabamos de descrever não acontece. A tarefa retém todas as atividades na sua pilha, mesmo após um longo período. clearTaskOnLaunch Se este atributo é definido como "true" na atividade de raiz de uma tarefa, a pilha é limpa até a atividade de raiz sempre que o usuário deixa a tarefa e retorna a ela. Em outras palavras, é o oposto do alwaysRetainTaskState. O usuário sempre retorna para a tarefa em seu estado inicial, mesmo após estar deixando a tarefa por apenas um momento. ANDROID, uma visão geral – Anderson Duarte de Amorim
64
finishOnTaskLaunch Esse atributo é como clearTaskOnLaunch, mas opera em uma única atividade, não em uma tarefa inteira. Ela também pode fazer alguma atividade ir embora, incluindo a atividade de raiz. Quando é definido como "true", a atividade continua a ser parte da tarefa apenas para a sessão atual. Se o usuário sai e depois volta para a tarefa, ela não está mais presente.
Iniciando uma tarefa Você pode configurar uma atividade como o ponto de entrada para uma tarefa, dandolhe um filtro com a intenção "android.intent.action.MAIN" como a ação especificada e "android.intent.category.LAUNCHER" como a categoria especificada. Por exemplo: <activity ... > <intent-filter ... > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> ... </activity>
A intenção do filtro deste tipo faz um ícone e uma legenda para a atividade a ser exibida na tela do menu, dando aos usuários uma maneira de iniciar a atividade e para retornar para a tarefa que ele cria em qualquer momento depois de ter sido lançado. Esta segunda habilidade é importante: o usuário deve ser capaz de deixar uma tarefa e, em seguida, voltar a ela mais tarde com o lançador de atividade. Por esta razão, os dois modos de iniciar as atividades que marcam o início, como sempre, uma tarefa, "singleTask" e " "singleInstance" , devem ser usados somente quando a atividade tem um filtro ACTION_MAIN e CATEGORY_LAUNCHER. Imagine, por exemplo, o que poderia acontecer se o filtro estiver faltando: Uma intenção lança uma atividade "singleTask", iniciando uma nova tarefa, e o usuário passa algum tempo a trabalhar nessa tarefa. O usuário pressiona o HOME. A tarefa agora é enviada para o fundo e fica invisível. Porque ele não é representado na tela do aplicativo, o usuário não tem como voltar para a tarefa. Para os casos onde você não deseja que o usuário seja capaz de retornar a uma atividade, defina o elemento de <activity>, finishOnTaskLaunch, para "true".
ANDROID, uma visão geral – Anderson Duarte de Amorim
65
Serviços Um Service é um componente da aplicação que pode executar operações de longa duração em segundo plano e não oferece uma interface de usuário. Outro componente do aplicativo pode iniciar um serviço e vai continuar a rodar em segundo plano, mesmo se o usuário muda para outro aplicativo. Além disso, um componente pode se ligar a um serviço para interagir com ele e até mesmo realizar a comunicação entre processos (IPC). Por exemplo, um serviço pode lidar com as transações de rede, tocar música, executar I/O, ou interagir com um provedor de conteúdo, todos no fundo. Um serviço pode essencialmente de duas formas: Iniciado Um serviço é "iniciado" quando um componente da aplicação (como uma atividade) inicia-o chamando startService(). Uma vez iniciado, o serviço pode ser executado em segundo plano por tempo indeterminado, mesmo se o componente que o começou é destruído. Normalmente, um serviço iniciado executa uma única operação e não retorna um resultado para o chamador. Por exemplo, pode fazer o download ou upload de um arquivo pela rede. Quando a operação é feita, o serviço deve parar. Ligado Um serviço é "ligado" quando um componente da aplicação liga-se a ele chamando bindService(). Um serviço vinculado oferece uma interface clienteservidor que permite que os componentes interajam com o serviço, enviar pedidos, obter resultados, e até mesmo fazê-lo através de processos de comunicação entre processos (IPC). Um serviço vinculado só é executado enquanto outro componente de aplicação está vinculado a ele. Vários componentes podem ligar para o serviço de uma só vez, mas quando todos eles se desvinculam, o serviço é destruído. Embora essa documentação geralmente aborda esses dois tipos de serviços separadamente, o serviço pode funcionar nos dois sentidos, ele pode ser iniciado (para rodar indefinidamente) e também permitir a ligação. É simplesmente uma questão de
ANDROID, uma visão geral – Anderson Duarte de Amorim
66
saber se você implementa a dupla de métodos: onStartCommand() para permitir ao os componentes iniciá-lo e onBind() para permitir a ligação. Independentemente de sua aplicação ser “iniciada”, “ligada”, ou ambos, qualquer componente de aplicativo pode usar o serviço (mesmo a partir de um aplicativo separado), da mesma forma que qualquer componente pode usar uma atividade – iniciando-a com uma Intent. No entanto, você pode declarar o serviço como privado, no arquivo de manifesto, e bloquear o acesso de outros aplicativos. Atenção: O serviço é executado no segmento principal de sua hospedagem, o serviço de processo não cria seu próprio segmento e não é executado em um processo separado (a menos que você especifique o contrário). Isso significa que, se o serviço vai fazer todo o trabalho intensivo da CPU ou o bloqueio de operações (como a reprodução de MP3 ou de rede), você deve criar um novo segmento dentro do serviço para fazer esse trabalho. Ao utilizar uma thread separada, você vai reduzir o risco de erros como aplicação não responde (ANR) e o thread principal do aplicativo pode permanecer dedicado à interação do usuário com suas atividades.
O básico Você deve utilizar um serviço ou um thread? O serviço é simplesmente um componente que pode ser executado em segundo plano, mesmo quando o usuário não está interagindo com o aplicativo. Assim, você deve criar um serviço só para o que você precisa. Se você precisa realizar o trabalho fora de sua linha principal, mas apenas enquanto o usuário está interagindo com o aplicativo, então você deve, provavelmente, criar uma nova thread e não um serviço. Por exemplo, se você quiser tocar algumas músicas, mas apenas quando sua atividade está em execução, você pode criar uma lista de discussão em onCreate(), começar a utilizar em onStart() , e depois parar em onStop(). Também considere usar AsyncTask ou HandlerThread, em vez da tradicional classe Thread. Lembre-se que se você usar um serviço, ele ainda é executado no
ANDROID, uma visão geral – Anderson Duarte de Amorim
67
Para criar um serviço,
thread principal do aplicativo por padrão, então você deve ainda
você deve criar uma
criar um novo segmento dentro do serviço se executa operações
subclasse de Service (ou
intensivas ou bloqueio.
uma de suas subclasses existentes). Em sua execução, é necessário substituir alguns métodos de callback para lidar com aspectos essenciais do ciclo de vida do serviço e fornecer um mecanismo de componentes para ligar para o serviço, se for o caso. Os métodos mais importantes de retorno são: onStartCommand() O sistema chama este método quando outro componente, como uma atividade, solicita que o serviço seja iniciado, chamando startService(). Uma vez que este método é executado, o serviço é iniciado e pode rodar em segundo plano por tempo indeterminado. Se você implementar essa, é sua a responsabilidade parar o serviço quando seu trabalho é feito, chamando stopSelf() ou stopService(). (Se você apenas quiser fornecer ligação, você não precisa aplicar esse método.) onBind() O sistema chama este método quando um outro componente quer se vincular com o serviço (por exemplo, executar RPC), chamando bindService(). Na implementação deste método, você deve fornecer uma interface que os clientes usam para se comunicar com o serviço, retornando um IBinder. Você sempre deve implementar este método, mas se você não quer permitir a ligação, então você deve retornar null. onCreate() O sistema chama este método quando o serviço é criado, para executar os procedimentos de configuração (antes de chamar qualquer onStartCommand() ou onBind()). Se o serviço já está em execução, este método não é chamado. onDestroy() O sistema chama este método quando o serviço não é mais usado e está sendo destruído. Seu serviço deve implementar isso para limpar quaisquer recursos, tais como threads, ouvintes registrados, receptores, etc. Esta é a última chamada que o serviço recebe. ANDROID, uma visão geral – Anderson Duarte de Amorim
68
Se um componente inicia o serviço chamando startService() (o que resulta em uma chamada para onStartCommand()), o serviço continua funcionando até que ele pare com stopSelf() ou outro componente deixa-o chamando stopService(). Se um componente chama bindService() para criar o serviço (e onStartCommand() não é chamado), o serviço funciona somente enquanto o componente está ligado a ele. Depois que o serviço é desvinculado de todos os clientes, o sistema se destrói. O sistema Android força a parada de um serviço somente quando estiver com pouca memória e deve recuperar os recursos do sistema para a atividade que tem o foco do usuário. Se o serviço está vinculado a uma atividade que tem o foco do usuário, então é menos provável de ser morto, e se o serviço é declarado a ser executado no primeiro plano (discutido mais tarde), então ele quase nunca vai ser morto. Caso contrário, se o serviço foi iniciado e está rodando há muito tempo, então o sistema irá baixar a sua posição na lista de tarefas em segundo plano ao longo do tempo e o serviço se tornará altamente suscetível a matar-se - se o serviço é iniciado, então você deve projetá-lo elegantemente para lançar reinício pelo sistema. Se o sistema mata o seu serviço, ele reinicia logo que os recursos se tornam novamente disponíveis (embora isso também dependa do valor que você retornar do onStartCommand(), como será discutido mais tarde).
Declarando um serviço no manifesto Como atividades (e outros componentes), você deve declarar todos os serviços do arquivo de manifesto do aplicativo. Para declarar seu serviço, adicione um elemento <service> como um filho do elemento <application>. Por exemplo: <manifest ... > ... <application ... > <service android:name=".ExampleService" /> ... </application> </manifest>
Existem outros atributos que você pode incluir no <service> para definir propriedades, como as permissões necessárias para iniciar o serviço e o processo em que o serviço deve ser executado.
ANDROID, uma visão geral – Anderson Duarte de Amorim
69
Assim como uma atividade, um serviço pode definir filtros que permitem a intenção de outros componentes para invocar o serviço utilizando as intenções implícitas. Ao declarar a intenção de filtros, componentes de qualquer aplicativo instalados no aparelho do usuário podem, potencialmente, iniciar o seu serviço se o serviço de declarar a intenção de filtro que corresponde à intenção outro aplicativo passa a startService() . Se você planeja usar o seu serviço apenas localmente (as outras aplicações não o usam), então você não precisa (e não deve) prestar quaisquer filtros de intenção. Sem qualquer intenção de filtros, você deve iniciar o serviço usando uma intenção explícita dos nomes de classe do serviço. Além disso, você pode garantir que seu serviço seja privado, basta somente você incluir o atributo android:exported e defini-lo como "false". É eficaz mesmo se o serviço fornece filtros de intenção.
Criando um serviço iniciado Um
serviço
outro
Segmentação Android 1.6 ou inferior
chamando
Se você estiver construindo uma
startService(), resultando em uma chamada
aplicação para o Android 1.6 ou
para o método onStartCommand() do serviço.
inferior, você precisa implementar
Quando um serviço é iniciado, ele tem um
onStart(),
ciclo de vida que é independente do
onStartCommand() (no Android 2.0,
componente que começou e o serviço pode
onStart() foi depreciado em favor do
funcionar em segundo plano por tempo
onStartCommand()).
indeterminado, mesmo se o componente que
Para obter mais informações sobre a
o começou é destruído. Como tal, o serviço
prestação
deve parar quando seu trabalho é feito
versões do Android superiores a 2.0,
chamando stopSelf(), ou outro componente
consulte
pode pará-lo, chamando stopService().
onStartCommand().
componente
iniciado
é
que
inicia
se
um
de
em
de
a
vez
compatibilidade
de
com
documentação
de
Um componente de aplicação, como uma atividade, pode iniciar o serviço chamando startService() e passando uma Intent que especifica o serviço e inclui todos os dados para o serviço deve usar. O serviço recebe essa Intent no método onStartCommand().
ANDROID, uma visão geral – Anderson Duarte de Amorim
70
Por exemplo, suponha que uma atividade precisa salvar alguns dados para um banco de dados on-line. A atividade pode iniciar um serviço e entregá-lo para guardar os dados, passando
a
intenção
de
startService().
O
serviço
recebe
a
intenção
em
onStartCommand(), se conecta à Internet e executa a operação de banco de dados. Quando a transação estiver concluída, o serviço o pára e ele é destruído. Atenção: Um serviço executa no mesmo processo do aplicativo no qual ele é declarado e na thread principal da aplicação, por padrão. Assim, se o serviço realiza intensivo ou o bloqueio de operações, enquanto o usuário interage com uma atividade a partir do mesmo aplicativo, o serviço vai abrandar o desempenho da atividade. Para evitar afetar o desempenho do aplicativo, você deve iniciar uma nova thread dentro do serviço. Tradicionalmente, há duas classes que você pode estender para criar um serviço iniciado: Service Esta é a classe base para todos os serviços. Quando você estender essa classe, é importante que você crie um novo segmento para fazer todo o trabalho, pois o serviço usa a linha principal do aplicativo, por padrão, o que poderia diminuir o desempenho de qualquer atividade de sua aplicação que está rodando. IntentService Esta é uma subclasse de Service que utiliza um thread de trabalho para lidar com todos os pedidos de início, um de cada vez. Esta é a melhor opção se você não exigir que o serviço de lidar com várias solicitações em simultâneo. Tudo que você precisa fazer é implementar onHandleIntent(), que recebe a intenção de cada solicitação de início para que você possa fazer o trabalho de fundo.
Estendendo a classe IntentService Porque a maioria dos serviços iniciados não precisam lidar com múltiplas solicitações ao mesmo tempo (que pode realmente ser um cenário perigoso de multi-threading), é melhor se você implementar o seu serviço usando o IntentService. O IntentService faz o seguinte:
ANDROID, uma visão geral – Anderson Duarte de Amorim
71
Cria um thread de trabalho padrão que executa todas as intenções entregues ao onStartCommand() em separado do thread principal de sua aplicação. Cria uma fila de trabalho que passa uma intenção de cada vez para seu onHandleIntent() de execução, para que não tenha que se preocupar com multithreading. Interrompe o serviço, após todos os pedidos de início ter sido manipulados, então você nunca tem que chamar stopSelf(). Fornece implementação padrão de onBind() que retorna nulo. Fornece uma implementação padrão de onStartCommand() que envia a intenção da fila de trabalho e, em seguida, à onHandleIntent() de execução. Tudo isto se acrescenta ao fato de que tudo que você precisa fazer é implementar onHandleIntent() para fazer o trabalho fornecido pelo cliente. (Embora, você também precisa fornecer um construtor pequeno para o serviço). Aqui está um exemplo de implementação de IntentService : public class HelloIntentService extends IntentService { /** * A constructor is required, and must call the super IntentService(String) * constructor with a name for the worker thread. */ public HelloIntentService() { super("HelloIntentService"); } /** * The IntentService calls this method from the default worker thread with * the intent that started the service. When this method returns, IntentService * stops the service, as appropriate. */ @Override protected void onHandleIntent(Intent intent) { // Normally we would do some work here, like download a file. // For our sample, we just sleep for 5 seconds. long endTime = System.currentTimeMillis() + 5*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) { }
ANDROID, uma visão geral – Anderson Duarte de Amorim
72
} } } }
Isso é tudo que você precisa: um construtor e uma implementação de onHandleIntent(). Se você decidir também substituir os métodos de retorno de chamada, tais como onCreate(), onStartCommand(), ou onDestroy(), não se esqueça de chamar a implementação de super, de modo que o IntentService possa lidar corretamente com a vida do thread. Por exemplo, onStartCommand() deve retornar a implementação padrão (que é como a intenção é entregue a onHandleIntent()): @Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); return super.onStartCommand(intent,flags,startId); }
Além do onHandleIntent(), o único método a partir do qual você não precisa chamar o super classe é onBind() (mas você só precisa implementar caso o serviço permita a ligação).
Estendendo a classe de serviço Como você viu na seção anterior, utilizar IntentService faz sua implementação de um serviço iniciado muito simples. Se, no entanto, é necessário o seu serviço executar multi-threading (em vez de processar pedidos através da fila de trabalho), então você pode estender a classe Service para lidar com cada intenção. Para efeito de comparação, o código de exemplo a seguir é uma implementação do Service que executa o mesmo trabalho exatamente como o exemplo acima usando IntentService. Ou seja, para cada solicitação, ele usa um thread para executar o trabalho e os processos de um único pedido por vez. public class HelloService extends Service { private Looper mServiceLooper; private ServiceHandler mServiceHandler; // Handler that receives messages from the thread private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) {
ANDROID, uma visão geral – Anderson Duarte de Amorim
73
super(looper); } @Override public void handleMessage(Message msg) { // Normally we would do some work here, like download a file. // For our sample, we just sleep for 5 seconds. long endTime = System.currentTimeMillis() + 5*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } // Stop the service using the startId, so that we don't stop // the service in the middle of handling another job stopSelf(msg.arg1); } } @Override public void onCreate() { // Start up the thread running the service. Note that we create a // separate thread because the service normally runs in the process's // main thread, which we don't want to block. We also make it // background priority so CPU-intensive work will not disrupt our UI. HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); // Get the HandlerThread's Looper and use it for our Handler mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); // For each start request, send a message to start a job and deliver the // start ID so we know which request we're stopping when we finish the job Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; mServiceHandler.sendMessage(msg); // If we get killed, after returning from here, restart return START_STICKY; } @Override public IBinder onBind(Intent intent) { // We don't provide binding, so return null return null; }
ANDROID, uma visĂŁo geral â&#x20AC;&#x201C; Anderson Duarte de Amorim
74
@Override public void onDestroy() { Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); } }
ANDROID, uma visão geral – Anderson Duarte de Amorim
75
Como você pode ver, dá muito mais trabalho do que usar um IntentService. No entanto, como você lida com cada chamada para onStartCommand() por si só, você pode executar várias solicitações em simultâneo. Isso não é o que este exemplo faz, mas se é isso que você quer então você pode criar um novo tópico para cada pedido e executá-los imediatamente (em vez de aguardar a solicitação anterior para terminar). Observe que o método onStartCommand() deve retornar um inteiro. O inteiro é um valor que descreve como o sistema deve continuar o serviço a partir do evento em que o sistema o mata (como discutido acima, a implementação padrão para IntentService trata isso para você, mas você é capaz de modificá-lo). O valor de retorno de onStartCommand() deve ser uma das seguintes constantes: START_NOT_STICKY Se o sistema mata o serviço após onStartCommand() retornar, não recria o serviço, salvo se houver pendência de intenções para entregar. Esta é a opção mais segura para evitar a execução de seu serviço quando não for necessário e quando a sua aplicação pode simplesmente reiniciar os trabalhos inacabados. START_STICKY Se o sistema mata o serviço após onStartCommand() retornar, recria o serviço e chamar onStartCommand(), mas não entrega novamente a última intenção. Em vez disso, o sistema chama onStartCommand() com uma intenção nula, a menos que houver pendência de intenções para iniciar o serviço, nesse caso, os propósitos são entregues. Isso é adequado para media players (ou afins) que não estão executando comandos, mas rodam indefinidamente à espera de uma interação. START_REDELIVER_INTENT Se o sistema mata o serviço após onStartCommand() retornar, recria o serviço e chama onStartCommand() com a última intenção que foi entregue para o serviço. Quaisquer intenções pendentes são entregues em troca. Isso é adequado para os serviços que estão ativamente realizando um trabalho que deve ser imediatamente reiniciado, como baixar um arquivo.
ANDROID, uma visão geral – Anderson Duarte de Amorim
76
Iniciando um serviço Você pode iniciar um serviço de uma atividade ou componente de outro aplicativo por meio de um Intent (especificando o serviço para iniciar) para startService(). O sistema Android chama o método onStartCommand() do serviço do e passa a Intent. (Você nunca deve chamar onStartCommand() diretamente.) Por exemplo, uma atividade pode iniciar o serviço de exemplo na seção anterior (HelloSevice) com a intenção explícita com startService(): Intent intent = new Intent(this, HelloService.class); startService(intent);
O startService() retorna imediatamente e o sistema Android chama o método onStartCommand() do serviço. Se o serviço não estiver sendo executado, o sistema chama primeiro onCreate(), em seguida, chama onStartCommand(). Se o serviço não é provê ligação, a intenção entregue com startService() é o único modo de comunicação entre o componente de aplicação e o serviço. No entanto, se você quiser o serviço para enviar um resultado de volta, o cliente que inicia o serviço pode criar uma PendingIntent para uma transmissão (com getBroadcast()) e entregá-lo ao serviço da Intent que inicia o serviço. O serviço pode então usar a transmissão para fornecer um resultado. Vários pedidos para iniciar o resultado do serviço em várias correspondências chamam o onStartCommand() do serviço. No entanto, apenas um pedido para parar o serviço (com stopSelf() ou stopService()) é necessário.
Parando um serviço O serviço iniciado deve gerenciar seu próprio ciclo de vida. Ou seja, o sistema não para ou destrói o serviço a menos que ele deva recuperar a memória do sistema e o serviço continua a funcionar após onStartCommand() retornar. Assim, o serviço deve parar, chamando stopSelf() ou outro componente pode pará-lo, chamando stopService(). Uma vez solicitado a parar com stopSelf() ou stopService(), o sistema destrói o serviço o mais rapidamente possível. No entanto, se o serviço trabalha com pedidos múltiplos para onStartCommand() ao mesmo tempo, então você não deve interromper o serviço quando tiver terminado o ANDROID, uma visão geral – Anderson Duarte de Amorim
77
processamento de um pedido inicial, porque você pode ter uma vez recebido um pedido novo começo (parando no final do primeiro pedido iria encerrar a segunda). Para evitar esse problema, você pode usar stopSelf(int) para garantir que o seu pedido para parar o serviço é sempre baseado no início pedido mais recente. Ou seja, quando você chamar stopSelf(int), você passa o ID do pedido inicial (o startId entregue para onStartCommand()) ao qual o pedido de parada corresponde. Então, se o serviço recebeu um pedido antes de você fosse capaz de chamar stopSelf(int) , o ID não corresponderá e o serviço não vai parar. Atenção: É importante que o aplicativo pare seus serviços quando o trabalho está pronto, para evitar o desperdício de recursos do sistema e consumo de bateria. Se necessário, outros componentes podem interromper o serviço pela chamada de stopService(). Mesmo se você permitir chamada ao serviço, você deve sempre parar o serviço a si mesmo se ele já recebeu uma chamada para onStartCommand().
Criando um serviço vinculado Um serviço vinculado é aquele que permite que os componentes do aplicativo se vinculem a ele chamando bindService() para criar uma ligação de longa data (e geralmente não permitem aos componentes iniciá-lo, chamando startService()). Você deve criar um serviço ligado quando você quiser interagir com o serviço de atividades e outros componentes em seu aplicativo ou para expor algumas das funcionalidades do aplicativo para outros aplicativos, através da comunicação entre processos (IPC). Para criar um serviço vinculado, você deve implementar o método onBind()para retornar um IBinder que define a interface de comunicação com o serviço. Outros componentes do aplicativo pode então chamar bindService() para recuperar a interface e começar a chamar os métodos do serviço. O serviço só vive para servir o componente de aplicação que é ligado a ele, então quando não há componentes vinculados ao serviço, o sistema o destrói (você não precisa parar um serviço ligado no jeito que você tem quando o serviço é iniciado através onStartCommand()). Para criar um serviço vinculado, a primeira coisa que você deve fazer é definir a interface que especifica como um cliente pode se comunicar com o serviço. Essa interface entre o serviço e o cliente deve ser uma implementação de IBinder e é o que o ANDROID, uma visão geral – Anderson Duarte de Amorim
78
seu serviço deve retornar a partir do método de retorno onBind(). Uma vez que o cliente recebe a IBinder , pode começar a interagir com o serviço por meio dessa interface. Vários clientes podem se ligar ao serviço de uma só vez. Quando um cliente concluiu a interação com o serviço, ele chama unbindService() para se desvincular. Uma vez que não há clientes vinculados ao serviço, o sistema destrói o serviço.
Enviando notificações para o usuário Uma vez em execução, um serviço pode notificar o usuário de eventos usando notificações toast ou notificações da barra de status. Uma notificação toast é uma mensagem que aparece na superfície da janela atual por um momento e depois desaparece, enquanto uma notificação de barra de status fornece um ícone na barra de status com uma mensagem, que o usuário pode selecionar a fim de tomar uma ação (como iniciar uma atividade). Normalmente, uma notificação de barra de status é a melhor técnica quando algum trabalho de fundo tenha sido concluído (como um download do arquivo completo) e agora o usuário pode agir sobre ela. Quando o usuário seleciona a notificação a partir da visão expandida, a notificação pode iniciar uma atividade (tal como para visualizar o arquivo baixado).
Executando um serviço em primeiro plano Um serviço de primeiro plano é um serviço que é considerado como sendo algo que o usuário esteja atento e, portanto, não é um candidato para o sistema matar quando com pouca memória. Um serviço de primeiro plano deve prever uma notificação na barra de status, que é colocado abaixo da posição "em curso", o que significa que a notificação não pode ser dispensada a menos que o serviço ou está parado ou foi removido do primeiro plano. Por exemplo, um leitor de música que toca a partir de um serviço, deve ser definido para ser executado em primeiro plano, porque o usuário está explicitamente consciente do seu funcionamento. A notificação na barra de status pode indicar a música atual e permitir que o usuário inicie uma atividade para interagir com o leitor de música.
ANDROID, uma visão geral – Anderson Duarte de Amorim
79
Para solicitar que o serviço seja executado em primeiro plano, chame startForeground(). Este método tem dois parâmetros: um número inteiro que identifica a notificação e a notificação na barra de status. Por exemplo: Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text), System.currentTimeMillis()); Intent notificationIntent = new Intent(this, ExampleActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_message), pendingIntent); startForeground(ONGOING_NOTIFICATION, notification);
Para remover o serviço do primeiro plano, chama-se stopForeground(). Este método tem um valor booleano, indicando se deseja remover a notificação de barra de status também. Este método não para o serviço. No entanto, se você parar o serviço enquanto ele ainda está executando em primeiro plano a notificação também é removida. Nota: Os métodos startForeground() e stopForeground() foram introduzidos no Android 2.0 (API Nível 5).
Gerenciando ciclo de vida de um serviço O ciclo de vida de um serviço é muito mais simples do que o de uma atividade. No entanto, é ainda mais importante que você preste atenção à forma como o serviço é criado e destruído, porque um serviço pode ser executado em segundo plano, sem que o utilizador perceba. O ciclo de vida do serviço – desde quando ele é criado até quando ele é destruído - pode seguir dois caminhos diferentes: O serviço começou O serviço é criado quando um outro componente chama startService(). O serviço é executado indefinidamente e, em seguida, deve ser parado chamando stopSelf(). Outro componente também pode interromper o serviço pela chamada de stopService(). Quando o serviço for interrompido, o sistema o destrói. Um serviço vinculado O serviço é criado quando um outro componente (um cliente) chama bindService(). O cliente se comunica com o serviço através de uma interface IBinder. O cliente
ANDROID, uma visão geral – Anderson Duarte de Amorim
80
pode fechar a conexão chamando unbindService(). Vários clientes podem chamar o mesmo serviço e quando todos eles desvincularem-se, o sistema destrói o serviço. (O serviço não precisa parar si mesmo). Estes dois caminhos não são totalmente distintos. Ou seja, você pode chamar um serviço que já foi iniciado com startService(). Por exemplo, um serviço de música de fundo pode ser iniciado chamando startService() com uma Intent que identifica a música a tocar. Mais tarde, possivelmente quando o usuário deseja exercer algum controle sobre o player ou obter informações sobre a música atual, uma atividade pode se ligar ao serviço chamando bindService(). Em casos como este, stopService() ou stopSelf() não chegam a parar o serviço até que todos os clientes se desacoplem.
Aplicando o ciclo de vida dos callbacks Como uma atividade, um ciclo de vida do serviço tem métodos de retorno que você pode implementar para monitorar as mudanças no estado do serviço e realizar o trabalho no momento oportuno. O seguinte esqueleto de serviço demonstra todos os métodos de ciclo de vida:
Figura 2. O ciclo de vida do serviço. O diagrama à esquerda mostra o ciclo de vida quando o serviço é criado com startService() e o diagrama da direita mostra o ciclo de vida quando o serviço é criado com bindService().
ANDROID, uma visão geral – Anderson Duarte de Amorim
81
public class ExampleService extends Service { int mStartMode; // indicates how to behave if the service is killed IBinder mBinder; // interface for clients that bind boolean mAllowRebind; // indicates whether onRebind should be used @Override public void onCreate() { // The service is being created } @Override public int onStartCommand(Intent intent, int flags, int startId) { // The service is starting, due to a call to startService() return mStartMode; } @Override public IBinder onBind(Intent intent) { // A client is binding to the service with bindService() return mBinder; } @Override public boolean onUnbind(Intent intent) { // All clients have unbound with unbindService() return mAllowRebind; } @Override public void onRebind(Intent intent) { // A client is binding to the service with bindService(), // after onUnbind() has already been called } @Override public void onDestroy() { // The service is no longer used and is being destroyed } }
Nota: Ao contrário métodos de retorno do ciclo de vida da atividade, você não é obrigado a chamar a implementação da superclasse dos métodos de callback. Ao implementar esses métodos, você pode controlar dois loops aninhados do ciclo de vida do serviço: A vida inteira de um serviço acontece entre o momento onCreate() ser chamado e o tempo onDestroy() retornado. Como uma atividade, um serviço tem a sua configuração inicial em onCreate() e libera todos os recursos remanescentes em onDestroy(). Por exemplo, um serviço de reprodução de música pode criar o segmento onde a música será tocada em onCreate(), então para a thread em onDestroy().
ANDROID, uma visão geral – Anderson Duarte de Amorim
82
Os métodos onCreate() e onDestroy() são chamados para todos os serviços, sejam eles criados por startService() ou bindService() . A vida ativa de um serviço começa com uma chamada de um onStartCommand() ou onBind(). Cada método entrega a Intent que foi passado tanto startService() quanto bindService(), respectivamente. Se o serviço for iniciado, o tempo de vida ativa termina ao mesmo tempo em que toda a vida termina (o serviço continua ativo mesmo após onStartCommand() retornar). Se o serviço está vinculado, a vida ativa termina quando onUnbind() retorna. Nota: Apesar de um serviço iniciado ser interrompido por uma chamada para uma stopSelf() ou stopService(), não há um retorno para o respectivo serviço (não há onStop() de callback). Então, a menos que o serviço esteja vinculado a um cliente, o sistema destrói quando o serviço for interrompido, onDestroy() é o retorno recebido apenas. A figura 2 ilustra os métodos típicos de retorno de um serviço. Embora a figura separa os serviços que são criados por startService() daquelas criadas pelos bindService() , tenha em mente que qualquer serviço, não importa como ele é iniciado, pode potencialmente permitir que os clientes chamem-no. Assim, um serviço que foi inicialmente iniciado com onStartCommand() (por um cliente chamando startService() ) pode ainda receber uma chamada para onBind() (quando um cliente solicita bindService()).
ANDROID, uma visão geral – Anderson Duarte de Amorim
83
Serviços vinculados Um serviço vinculado é o servidor em uma interface cliente-servidor. O serviço permite que os componentes ligados (como em atividades) vinculem-se ao serviço, enviem pedidos, recebam respostas, e até mesmo realizem a comunicação entre processos (IPC). Um serviço ligado normalmente vive apenas enquanto ela serve a outro componente da aplicação e não é executado em segundo plano por tempo indeterminado.
O básico Um serviço ligado é uma implementação Service
da permite
Vinculação a um serviço iniciado
que
Conforme discutido no capítulo sobre Serviço, você pode
outros aplicativos se liguem e
criar um serviço que seja iniciado e vinculado. Ou seja, o
interajam com ele.
Para
serviço pode ser iniciado chamando startService(), que
um
permite que o serviço seja executado indefinidamente, e
fornecer
que
classe
ligação
serviço,
você
implementar método
para
de
o
deve onBind()
retorno.
Esse
método retorna um objeto
também permite que um cliente se amarre ao serviço chamando bindService(). Se você permitir que seu serviço seja iniciado e ligado, em seguida, quando o serviço for iniciado, o sistema não destruirá o serviço quando todos os clientes desacoplarem.
IBinder que define a interface
Em vez disso, você deve explicitamente parar o serviço,
de
chamando stopSelf() ou stopService().
programação
clientes
podem
que usar
os para
interagir com o serviço.
Embora você geralmente deva implementar onBind() ou onStartCommand(), às vezes é necessário aplicar a ambos. Por exemplo, um tocador de música pode ser útil para
Um cliente pode se ligar ao serviço
permitir o serviço a ser executado indefinidamente e
chamando
também fornecer vínculo. Desta forma, uma atividade pode
bindService(). Quando isso
iniciar o serviço para jogar alguma música e a música
acontecer, ele deve fornecer
continua a tocar mesmo se o usuário sai do aplicativo.
uma
de
Então, quando o usuário retorna para a aplicação, a
que
atividade pode ligar para o serviço para recuperar o
implementação
ServiceConnection,
monitora a conexão com o
controle da reprodução.
serviço.
Certifique-se de ler a seção sobre Gerenciamento do ciclo
bindService()
O
método retorna
imediatamente sem um valor,
de vida de um serviço vinculado, para obter mais informações sobre o ciclo de vida do serviço, quando for feita adição de ligação para um serviço iniciado.
ANDROID, uma visão geral – Anderson Duarte de Amorim
84
mas quando o sistema Android cria a conexão entre o cliente e o serviço, ele chama onServiceConnected() na ServiceConnection para entregar o IBinder que o cliente pode usar para se comunicar com o serviço. Vários clientes podem se conectar ao serviço de uma só vez. No entanto, o sistema chama o método onBind() do serviço para recuperar o IBinder somente quando o cliente liga-se em primeiro lugar. O sistema, em seguida, oferece os mesmos IBinder para quaisquer clientes que ligam, sem chamar onBind() novamente. Quando o libera último cliente do serviço, o sistema destrói o serviço (a menos que o serviço também foi iniciado por startService() ). Quando você implementar o seu serviço vinculado, a parte mais importante é definir a interface que o seu método callback onBind() retorna. Existem algumas maneiras diferentes que você pode definir o seu serviço da interface IBinder e a seção a seguir discute cada técnica.
Criando um serviço ligado Ao criar um serviço que oferece ligação, você deve fornecer um IBinder que fornece a interface de programação que os clientes podem utilizar para interagir com o serviço. Existem três maneiras com as quais você pode definir a interface: Estendendo a classe Binder Se o serviço é privado para sua própria aplicação e é executado no mesmo processo do cliente (o que é comum), você deve criar a sua interface extendendo o Binder e retornando uma instância a partir onBind(). O cliente recebe o Binder e pode usá-lo para acessar diretamente os métodos públicos disponíveis em qualquer um dos Binder implementados ou até mesmo o Service. Esta é a técnica preferida quando o serviço é apenas um trabalhador de fundo para o seu próprio aplicativo. A única razão em que você não deve criar a interface desta maneira é porque o serviço é utilizado por outras aplicações ou através de processos separados.
ANDROID, uma visão geral – Anderson Duarte de Amorim
85
Usando um Messenger Se você precisa de sua interface para trabalhar em processos diferentes, você pode criar uma interface para o serviço com um Messenger. Desta forma, o serviço define um Handler, que responde a diferentes tipos de mensagem de objetos. Este Handler é a base para um Messenger que podem compartilhar um IBinder com o cliente, permitindo que o cliente envie comandos para o serviço usando mensagem de objetos. Além disso, o cliente pode definir um Messenger próprio para que o serviço possa enviar mensagens de volta. Esta é a maneira mais simples para realizar a comunicação entre processos (IPC), porque o Messenger enfilera todas as solicitações em um único segmento para que você não tenha que projetar seu serviço a ser um thread-safe. Usando AIDL AIDL (Android Interface Definition Language) realiza todo o trabalho de decompor os objetos primitivos em que o sistema operacional possa entender através de processos para executar IPC. A técnica anterior, usando um Messenger, é realmente baseado em AIDL como a sua estrutura subjacente. Como mencionado acima, o Messenger cria uma fila de todas as solicitações do cliente em um único segmento, para que o serviço receba solicitações de um de cada vez. Se, no entanto, você quiser que o seu serviço lide com múltiplas solicitações ao mesmo tempo, então você pode usar AIDL diretamente. Neste caso, o serviço deve ser capaz de multi-threading e ser construído thread-safe. Para usar AIDL diretamente, você deve criar uma arquivo .aidl que define a interface de programação. As ferramentas do Android SDK usam esse arquivo para gerar uma classe abstrata que implementa a interface e lida com o IPC, que você pode estender dentro do seu serviço. Nota: A maioria dos aplicativos não devem usar AIDL para criar um serviço ligado, porque ele pode exigir recursos de multithreading e pode resultar em uma implementação mais complicada. Como tal, AIDL não é adequado para a maioria das aplicações e este documento não explica como usá-lo para seu serviço.
ANDROID, uma visão geral – Anderson Duarte de Amorim
86
Estendendo a classe Binder Se o serviço é usado somente pela aplicação local e não precisa trabalhar em todos os processos, então você pode implementar seu próprio Binder que fornece o acesso direto do cliente com os métodos públicos no serviço. Nota: Isto só funciona se o cliente e o serviço estão no mesmo aplicativo e processo, o que é mais comum. Por exemplo, isso iria funcionar bem para um aplicativo de música que tem o efeito de vincular uma atividade a seu próprio serviço de música que está tocando no fundo. Veja como configurá-lo: 1.
Em seu serviço, criar uma instância do Binder que: o
contém métodos públicos que o cliente pode chamar
o
retorna o atual Service, que tem métodos públicos que o cliente pode chamar
o
ou, retorna uma instância de outra classe hospedada pelo serviço com métodos públicos que o cliente pode chamar
2.
Retornar essa instância do Binder do método de retorno onBind().
3.
No cliente, receber a Binder do método de retorno onServiceConnected() e fazer chamadas para o serviço vinculado usando os métodos fornecidos.
Nota: A razão pela qual o serviço e o cliente devem estar no mesmo pedido é porque o cliente pode converter o objeto retornado e chamar propriamente de sua API. O serviço e o cliente também devem estar no mesmo processo, porque essa técnica não exerce qualquer triagem em todos os processos. Por exemplo, aqui está um serviço que oferece aos clientes o acesso a métodos no serviço por meio de um Binder de execução: public class LocalService extends Service { // Binder given to clients private final IBinder mBinder = new LocalBinder(); // Random number generator private final Random mGenerator = new Random();
ANDROID, uma visão geral – Anderson Duarte de Amorim
87
/** * Class used for the client Binder. Because we know this service always * runs in the same process as its clients, we don't need to deal with IPC. */ public class LocalBinder extends Binder { LocalService getService() { // Return this instance of LocalService so clients can call public methods return LocalService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } /** method for clients */ public int getRandomNumber() { return mGenerator.nextInt(100); } }
O LocalBinder fornece o método getService() de clientes para recuperar a instância atual do LocalService. Isso permite aos clientes chamarem métodos públicos no serviço. Por exemplo, os clientes podem chamar getRandomNumber() do serviço. Aqui está uma atividade que se liga a LocalService e solicita getRandomNumber() quando um botão é clicado: public class BindingActivity extends Activity { LocalService mService; boolean mBound = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onStart() { super.onStart(); // Bind to LocalService Intent intent = new Intent(this, LocalService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); // Unbind from the service if (mBound) {
ANDROID, uma visão geral – Anderson Duarte de Amorim
88
unbindService(mConnection); mBound = false; } } /** Called when a button is clicked (the button in the layout file attaches to * this method with the android:onClick attribute) */ public void onButtonClick(View v) { if (mBound) { // Call a method from the LocalService. // However, if this call were something that might hang, then this request should // occur in a separate thread to avoid slowing down the activity performance. int num = mService.getRandomNumber(); Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show(); } } /** Defines callbacks for service binding, passed to bindService() */ private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { // We've bound to LocalService, cast the IBinder and get LocalService instance LocalBinder binder = (LocalBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } }; }
O exemplo acima mostra como o cliente liga para o serviço usando uma implementação do ServiceConnection e o callback de onServiceConnected(). Nota: O exemplo acima não é explicitamente desvinculado do serviço, mas todos os clientes devem desvincular em um tempo adequado (por exemplo, quando a atividade pausa).
ANDROID, uma visão geral – Anderson Duarte de Amorim
89
Usando um Messenger Se você precisar de seu serviço para se comunicar com processos remotos, então você pode usar um Messenger para fornecer a interface para o serviço. Esta
técnica
permite
realizar
a
comunicação entre processos (IPC), sem a necessidade de utilizar AIDL.
Comparado com AIVD Quando você precisa realizar IPC, utilizar um Messenger para a sua interface é mais simples do que implementá-la com AIDL, porque Messenger enfilera todas as chamadas para o serviço, que, uma
interface
AIDL
pura
envia
pedidos
simultâneos para o serviço, que deve, então, lidar
Aqui está um resumo de como usar um
com multi-threading. Para a maioria das aplicações, o serviço não
Messenger:
precisa executar multi-threading, portanto, usar
O
serviço
implementa
um
um Messenger permite ao serviço lidar com uma
Handler que recebe um callback
chamada ao mesmo tempo. Se é importante que o
para cada chamada de um
seu serviço seja multi-threaded, então você deve
cliente.
usar AIDL para definir sua interface.
O Handler é usado para criar uma Messenger (que é uma referência para o Handler). O Messenger cria um IBinder que o serviço retorna para clientes de onBind(). Os clientes usam o IBinder para instanciar o Messenger (que faz referência ao serviço Handler), que o cliente usa para enviar mensagens para o serviço. O serviço recebe cada mensagem em seu Handler, especificamente, no método handleMessage(). Desta forma, não existem "métodos" para o cliente para chamar o serviço. Em vez disso, o cliente fornece as "mensagens" (objetos Message) que o serviço recebe em seu Handler. Aqui está um exemplo de serviço simples que usa um Messenger interface: public class MessengerService extends Service { /** Command to the service to display a message */ static final int MSG_SAY_HELLO = 1; /** * Handler of incoming messages from clients.
ANDROID, uma visão geral – Anderson Duarte de Amorim
90
*/ class IncomingHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SAY_HELLO: Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show(); break; default: super.handleMessage(msg); } } } /** * Target we publish for clients to send messages to IncomingHandler. */ final Messenger mMessenger = new Messenger(new IncomingHandler()); /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. */ @Override public IBinder onBind(Intent intent) { Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show(); return mMessenger.getBinder(); } }
Observe que o método handleMessage() no Handler é onde o serviço recebe a entrada Message e decide o que fazer, com base no membro what. Tudo o que um cliente precisa fazer é criar um Messenger com base no IBinder retornado pelo serviço e enviar uma mensagem usando o send(). Por exemplo, aqui é uma atividade simples, que liga para o serviço e oferece a mensagem MSG_SAY_HELLO para o serviço: public class ActivityMessenger extends Activity { /** Messenger for communicating with the service. */ Messenger mService = null; /** Flag indicating whether we have called bind on the service. */ boolean mBound; /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) {
ANDROID, uma visão geral – Anderson Duarte de Amorim
91
// This is called when the connection with the service has been // established, giving us the object we can use to // interact with the service. We are communicating with the // service using a Messenger, so here we get a client-side // representation of that from the raw IBinder object. mService = new Messenger(service); mBound = true; } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. mService = null; mBound = false; } }; public void sayHello(View v) { if (!mBound) return; // Create and send a message to the service, using a supported 'what' value Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0); try { mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onStart() { super.onStart(); // Bind to the service bindService(new Intent(this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); // Unbind from the service if (mBound) { unbindService(mConnection); mBound = false; } } }
ANDROID, uma visĂŁo geral â&#x20AC;&#x201C; Anderson Duarte de Amorim
92
Observe que este exemplo não mostra como o serviço pode responder ao cliente. Se você deseja que o serviço responda, então você também precisa criar um Messenger no cliente. Então, quando o cliente recebe a chamada onServiceConnected(), ele envia uma mensagem para o serviço, que inclui o Messenger do cliente no parâmetro replyTo d método send(). Você pode ver um exemplo de como fornecer mensagens bidirecionais no MessengerService.java (serviço) e MessengerServiceActivities.java (cliente).
Vinculação a um serviço Componentes da aplicação (clientes) podem se ligar a um serviço chamando bindService(). O sistema Android, em seguida, chama o serviço de onBind(), método que retorna um IBinder para interagir com o serviço. A ligação é assíncrona, bindService() retorna imediatamente e não retorna a IBinder para o cliente. Para receber o IBinder, o cliente deve criar uma instância de ServiceConnection e o passa para bindService(). O ServiceConnection inclui um método de resposta que o sistema chama para entregar o IBinder. Nota: Só as atividades, serviços e provedores de conteúdo podem se ligar a um serviço - você não pode se ligar a um serviço a partir de um receptor de broadcast. Assim, para ligar a um serviço de seu cliente, você deve: 1. Implementar ServiceConnection . A implementação deve substituir dois métodos: onServiceConnected(): O sistema chama isso para entregar o IBinder retornado pelo método onBind() do serviço. onServiceDisconnected(): O sistema Android chama isso quando a conexão com o serviço é perdida inesperadamente, como quando o serviço foi paralisada ou tenha sido morta. Isto não é chamado quando o cliente libera. 2. Call bindService(), passando a implementação ServiceConnection.
ANDROID, uma visão geral – Anderson Duarte de Amorim
93
3. Quando o sistema chama o método de retorno onServiceConnected(), você pode começar a fazer chamadas para o serviço, utilizando os métodos definidos pela interface. 4. Para desligar do serviço, chame unbindService(). Quando o cliente for destruído, ele irá desvincular-se do serviço, mas você deve sempre desvincular quando terminar a interação com o serviço ou quando a atividade pausar, então o serviço pode parar enquanto não está sendo utilizado. Por exemplo, o seguinte trecho liga o cliente ao serviço criado anteriormente por estender a classe Binder, então tudo o que deve fazer é o lançar o IBinder retornado ao LocalService e solicitar a LocalService: LocalService mService; private ServiceConnection mConnection = new ServiceConnection() { // Called when the connection with the service is established public void onServiceConnected(ComponentName className, IBinder service) { // Because we have bound to an explicit // service that is running in our own process, we can // cast its IBinder to a concrete class and directly access it. LocalBinder binder = (LocalBinder) service; mService = binder.getService(); mBound = true; } // Called when the connection with the service disconnects unexpectedly public void onServiceDisconnected(ComponentName className) { Log.e(TAG, "onServiceDisconnected"); mBound = false; } };
Com este ServiceConnection, o cliente pode ligar para um serviço, passando este para bindService(). Por exemplo: Intent intent = new Intent(this, LocalService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
O primeiro parâmetro de bindService() é uma Intent que, explicitamente, nomeia o serviço a ser vinculado (pensando que a intenção pode ser implícita). O segundo parâmetro é o objeto ServiceConnection. O terceiro parâmetro é um sinalizador que indica as opções para a ligação. Deve ser geralmente BIND_AUTO_CREATE, a fim de criar o serviço se ainda não ANDROID, uma visão geral – Anderson Duarte de Amorim
94
estiver vivo. Outros valores possíveis são BIND_DEBUG_UNBIND e BIND_NOT_FOREGROUND, ou 0 para nenhum.
Notas adicionais Aqui estão algumas anotações importantes sobre a ligação a um serviço: Você deve sempre lançar exceções DeadObjectException, que são lançados quando a conexão foi quebrada. Esta é a única exceção acionada por métodos remotos. Os objetos são referência contada através de processos. Você normalmente deverá emparelhar a ligação e desligamento durante a correspondente destruição e desmontagem de momentos do ciclo de vida do cliente. Por exemplo: o
Se você só precisa interagir com o serviço, enquanto sua atividade é visível, você deve se ligar durante onStart() e desvincular durante onStop().
o
Se você quiser que a sua atividade receba as respostas, mesmo quando ele está parado no fundo, então você pode se ligar durante onCreate() e desvincular durante onDestroy(). Cuidado com o que isso implica que a sua atividade necessita para usar o serviço durante todo o tempo ele está rodando (mesmo no fundo), por isso, se o serviço está em outro processo, então você aumentar o peso do processo e torna-se mais provável do sistema matá-lo.
Nota: Você normalmente não deveria vincular e desvincular a sua atividade durante o onResume() e onPause(), porque esses retornos ocorrem em todas as transições do ciclo de vida e você deve manter o processamento que ocorre nessas transições para um nível mínimo. Além disso, se há várias atividades em sua aplicação para vincular o mesmo serviço e não há uma transição entre duas dessas atividades, o serviço pode ser destruído e recriado como desassocia a atividade atual (durante a pausa) antes da próxima ligação (durante a retomada).
ANDROID, uma visão geral – Anderson Duarte de Amorim
95
Gerenciando o ciclo de vida de um serviço de associação
Figura 1. O ciclo de vida de um serviço que é iniciado e também permite a ligação.
Quando um serviço é desvinculado de todos os clientes, o sistema Android o destrói (a menos foi iniciado com onStartCommand()). Como tal, você não tem que gerenciar o ciclo de vida do seu serviço se ele é meramente um limite de serviço - o sistema Android gerencia isso para você saber se ele está ligado a algum cliente. No entanto, se você optar por implementar o método de retorno onStartCommand(), então você deve explicitamente parar o serviço, porque o serviço é considerado agora a
ANDROID, uma visão geral – Anderson Duarte de Amorim
96
ser iniciado. Neste caso, o serviço é executado até que o serviço para com stopSelf() ou outro componente chama stopService(), independentemente se está ligado a outros clientes. Além disso, se o serviço é iniciado e aceita a ligação, então quando o sistema solicita sua onUnbind(), você pode opcionalmente retornar true se você gostaria de receber uma chamada para onRebind() na próxima vez que um cliente chama o serviço (ao invés de receber uma chamada para onBind()). onRebind() retorna void, mas o cliente ainda recebe o IBinder na sua onServiceConnected(). A figura 1 ilustra a lógica para este tipo de ciclo de vida.
ANDROID, uma visão geral – Anderson Duarte de Amorim
97
Processos e threads Quando um componente de aplicativo se inicia e a aplicação não possui nenhum outro componente rodando, o sistema Android inicia um novo processo Linux para a aplicação com um único thread. Por padrão, todos os componentes do mesmo aplicativo executam no mesmo processo e thread (chamada de thread "main"). Se um componente do aplicativo é iniciado e já existe um processo para aquela aplicação (porque um outro componente do aplicativo existe), então o componente é iniciado dentro desse processo e usa o mesmo thread de execução. No entanto, você pode arranjar para diferentes componentes em seu aplicativo para serem executados em processos separados, e você pode criar threads adicionais para qualquer processo.
Processos Por padrão, todos os componentes do mesmo aplicativo executam no mesmo processo e a maioria das aplicações não devem mudar isso. No entanto, se você achar que você precisa controlar a que processo um determinado componente pertence, pode fazê-lo no arquivo de manifesto. A entrada de manifesto para cada tipo de elemento - <activity>, <service , <receiver> e <provider> - suporta um atributo android:process que pode indicar um processo em que esse componente deve ser executado. Você pode definir esse atributo para que cada componente seja executado em seu próprio processo, ou que algumas componentes compartilham um processo, enquanto outros não. Você também pode definir android:process de modo que os componentes de diferentes aplicações sejam executados no mesmo processo - desde que as aplicações compartilhem o mesmo ID de usuário do Linux e assinem com os mesmos certificados. O elemento <application> também suporta um atributos android:process, para definir um valor padrão que se aplica a todos os componentes. Android pode decidir encerrar um processo em algum momento, quando houver pouca memória e exigido por outros processos que estão mais imediatamente servindo ao usuário. Componentes de aplicativos em execução no processo que está morto são conseqüentemente destruídos. Um processo é iniciado novamente para esses componentes, quando há trabalho novamente para que eles façam.
ANDROID, uma visão geral – Anderson Duarte de Amorim
98
Ao decidir quais os processos matar, o sistema Android pesa a sua importância relativa para o usuário. Por exemplo, é mais facilmente desligado um processo hospedando atividades que não são mais visíveis na tela, em comparação com um processo hospedando atividades visíveis. A decisão de encerrar um processo, portanto, depende do estado dos componentes em execução no processo.
Ciclo de vida do processo O sistema Android tenta manter um processo de candidatura pelo maior tempo possível, mas, eventualmente, precisa remover os processos antigos para recuperar a memória para novos processos ou mais importantes. Para determinar quais os processos manter e quais matar, o sistema coloca cada processo em uma "hierarquia de importância" com base nos componentes em execução no processo e o estado desses componentes. Processos com menor importância são eliminadas em primeiro lugar, em seguida, aqueles com a menor importância seguinte, e assim por diante, sempre que necessário para recuperar os recursos do sistema. Há cinco níveis na hierarquia de importância. A lista a seguir apresenta os diferentes tipos de processos em ordem de importância (o primeiro processo é o mais importante e é morto por último): 1. Processo em primeiro plano: Um processo que é necessário para que o usuário esteja fazendo atualmente. Um processo é considerado em primeiro plano, se qualquer uma das seguintes condições forem verdadeiras: Abriga uma Activity com a qual o usuário está interagindo com (o método onResume() da Activity foi chamado). Abriga um Service que está vinculado à atividade que o usuário está interagindo. Abriga um Service que está sendo executado "em primeiro plano", o serviço chamado é startForeground(). Abriga um Service que está executando um ciclo de vida de seus retornos (onCreate(), onStart(), ou onDestroy()). Abriga um BroadcastReceiver que está executando a sua onReceive(). Geralmente, apenas um processo de background existe em um determinado momento. Eles são mortos apenas como um último recurso - se a memória é tão
ANDROID, uma visão geral – Anderson Duarte de Amorim
99
pouca que ele não pode continuar rodando. Geralmente, nesse ponto, o dispositivo atingiu um estado de paginação de memória, então, matar alguns processos de primeiro plano é necessário para manter a interface de usuário sensível. 2. Processo Visível Um processo que não tem nenhum componente de primeiro plano, mas ainda pode afetar o que o usuário vê na tela. Um processo é considerado visível se qualquer uma das seguintes condições forem verdadeiras: Abriga uma Activity que não está em primeiro plano, mas ainda é visível para o usuário (seu método onPause() foi chamado). Isso pode ocorrer, por exemplo, se a atividade iniciou um plano de diálogo, que permite que a atividade anterior possa ser vista por trás dele. Abriga um Service que está vinculado a uma atividade (ou plano) visível. Um processo visível é considerado extremamente importante e não vai ser morto a menos que isso é necessário para manter todos os processos de primeiro plano em execução. 3. Processo de serviço Um processo que está executando um serviço que foi iniciado com o método startService() e não se enquadra em nenhuma das duas categorias acima. Embora os processos de serviço não estejam diretamente ligados a qualquer coisa que o usuário vê, eles estão geralmente fazendo coisas que o usuário se preocupa (como a reprodução de música no fundo ou baixando arquivo na rede), então o sistema os mantém rodando, a menos que não haja memória suficiente para retêlos, juntamente com o primeiro plano e processos visíveis. 4. Processo em segundo plano Um processo que mantém uma atividade que não é visível para o usuário no momento (o método onStop() da atividade foi chamado). Esses processos não têm impacto direto sobre a experiência do usuário, e o sistema pode matá-los a qualquer momento para recuperar a memória para primeiro plano, visibilidade, ou processo de serviço. Normalmente, há muitos processos em execução no
ANDROID, uma visão geral – Anderson Duarte de Amorim
100
fundo, então eles são mantidos em uma LRU (menos recentemente usada), lista para garantir que o processo com a atividade que mais recentemente foi visto pelo usuário é o último a ser morto. Se uma atividade implementa métodos de seu ciclo de vida corretamente, e salva o seu estado atual, matar o seu processo não terá um efeito visível sobre a experiência do usuário, pois quando o usuário navega de volta para a atividade, a atividade restaura todo o seu estado visível. 5. Processo vazio Um processo que não possui todos os componentes do aplicativo ativos. A única razão para manter esse tipo de processo vivo é para fins de armazenamento em cache, para melhorar o tempo de inicialização da próxima vez que um componente precisa ser executado. O sistema mata muitas vezes estes processos a fim de equilibrar os recursos do sistema global entre os caches do processo e os cachês do kernel subjacente. Android enfileira um processo ao mais alto nível que pode, com base na importância do componente ativo no processo. Por exemplo, se um processo hospeda um serviço e uma atividade visível, o processo é classificado como um processo visível, e não um processo de serviço. Além disso, o ranking de um processo pode ser acrescido, pois outros processos são dependentes dele - um processo que está servindo a outro processo nunca pode ser menor classificado do que o processo que está servindo. Por exemplo, se um provedor de conteúdo em um processo A está a servir um cliente no processo de B, ou se um serviço em um processo A está ligado a um componente no processo de B, processo A é sempre considerado pelo menos tão importante como o processo B. Porque um processo executando um serviço é melhor classificado do que um processo com atividades em segundo plano, uma atividade que inicia uma operação de longa duração pode muito bem começar um serviço para essa operação, ao invés de simplesmente criar uma thread de trabalhador - particularmente se a operação provavelmente durará mais que a atividade. Por exemplo, uma atividade que carrega uma foto em um site deve iniciar um serviço para realizar o upload, então o upload pode continuar em segundo plano, mesmo se o usuário deixar a atividade. Usar um serviço garante que a operação terá, pelo menos, prioridade "no processo do serviço",
ANDROID, uma visão geral – Anderson Duarte de Amorim
101
independentemente do que acontece com a atividade. Este é o mesmo motivo que os receptores de broadcast devem contratar serviços ao invés de simplesmente colocar operações demoradas em um thread.
Thread Quando uma aplicação é lançada, o sistema cria um thread de execução para o aplicativo, chamado de "main". Esta thread é muito importante, pois é responsável por despachar os eventos para os apropriados widgets de interface de usuário, incluindo eventos de desenho. É também o segmento em que sua aplicação interage com os componentes da UI Toolkit Android (componentes da android.widget e pacotes android.view). Como tal, o thread principal é chamado também às vezes de UI thread. O sistema não cria um thread separado para cada instância de um componente. Todos os componentes que são executados no mesmo processo são instanciados no thread de interface do usuário e as chamadas do sistema para cada componente são despachadas a partir desse thread. Conseqüentemente, os métodos que respondem às callbacks do sistema (como onKeyDown() para relatar as ações do usuário ou um método de retorno do ciclo de vida) sempre executam no thread da interface do usuário do processo. Por exemplo, quando o usuário toca um botão na tela, seu thread UI do aplicativo despacha o evento de toque para o widget, que por sua vez, define seu estado pressionado e lança uma requisição inválida para a fila de eventos. O thread UI desenfilera a requisição e notifica o widget que deve se redesenhar. Quando o aplicativo executa um trabalho intensivo em resposta à interação do usuário, este único thread pode produzir mal desempenho a menos que você implemente a sua aplicação de forma adequada. Especificamente, se tudo está acontecendo no thread de interface do usuário, realizando longas operações, tais como acesso à rede ou consultas de banco de dados, irá bloquear a interface toda. Quando o thread está bloqueado, nenhum evento pode ser enviado, incluindo eventos de desenho. Do ponto de vista do usuário, o aplicativo parece travar. Pior ainda, se o segmento de interface do usuário estiver bloqueado por mais de alguns segundos (cerca de 5 segundos atualmente) ao usuário é apresentado o abominável "aplicativo não responde" (ANR). O usuário pode, então, decidir abandonar a aplicação e desinstalá-la se está infeliz.
ANDROID, uma visão geral – Anderson Duarte de Amorim
102
Além disso, o Andoid UI Toolkit não é thread-safe. Então, você não deve manipular a sua interface a partir de um thread de trabalho - você deve fazer toda a manipulação de sua interface a partir de um thread UI. Assim, existem apenas duas regras para o modelo de thread única no Android: 1.
Não bloquear o thread de UI
2.
Não acessar o Android UI Toolkit de fora do thread UI
Threads funcionais Devido ao modelo de thread único acima descrito, é vital para a capacidade de resposta da interface do usuário do seu aplicativo que você não bloqueie o thread. Se você tiver de realizar operações que não são instantâneas, você deve certificar-se de fazê-las em threads separados ("background" ou threads "worker"). Por exemplo, abaixo está um código para um usuário que faz o download de uma imagem de um thread separado e é exibida em uma ImageView: public void onClick(View v) { new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork("http://example.com/image.png"); mImageView.setImageBitmap(b); } }).start(); }
Em princípio, isso parece funcionar bem, porque cria uma nova thread para lidar com a operação da rede. No entanto, ele viola a segunda regra do modelo de única thread: não acessar o Android UI Toolkit de fora da UI-thread - esta amostra modifica o ImageView do thread de trabalho em vez do thread UI. Isso pode resultar em um comportamento indefinido e inesperado, que pode ser difícil e demorado para rastrear. Para corrigir esse problema, o Android oferece várias maneiras para acessar a thread de interface do usuário a partir de outros threads. Aqui está uma lista de métodos que podem ajudar: Activity.runOnUiThread(Runnable) View.post(Runnable) View.postDelayed(Runnable, long)
ANDROID, uma visão geral – Anderson Duarte de Amorim
103
Por
exemplo,
você
pode
corrigir
o
código
acima
usando
o
método
View.post(Runnable): public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start(); }
Agora, essa implementação é thread-safe: o funcionamento da rede é feito a partir de um segmento separado, enquanto o ImageView é manipulado a partir do thread de interface de usuário. No entanto, como a complexidade da operação cresce, este tipo de código pode ser complicado e difícil de manter. Para lidar com interações mais complexas com um thread de trabalho, você pode considerar usar um Handler, para processar as mensagens entregues a partir do thread. Talvez a melhor solução, porém, é estender a classe AsyncTask, o que simplifica a execução de tarefas do thread de trabalho que precisam interagir com a interface do usuário.
Usando AsyncTask AsyncTask permite executar trabalho assíncrono em sua interface com o usuário. Ela executa as operações de bloqueio em um segmento de trabalho e, em seguida, publica os resultados no segmento de interface do usuário, sem que você precise lidar com tópicos e/ou manipuladores de si mesmo. Para usá-lo, você deve incorporar AsyncTask e implementar o método de retorno doInBackground(), que é executado em um pool de threads de background. Para atualizar sua interface, você deve implementar onPostExecute(), que fornece o resultado da doInBackground() e é executado no thread da interface do usuário, assim você pode escolher atualizar o UI. Você pode então executar a tarefa, chamando execute() do thread UI.
ANDROID, uma visão geral – Anderson Duarte de Amorim
104
Por exemplo, você pode implementar o exemplo anterior usando AsyncTask desta forma: public void onClick(View v) { new DownloadImageTask().execute("http://example.com/image.png"); } private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> { /** The system calls this to perform work in a worker thread and * delivers it the parameters given to AsyncTask.execute() */ protected Bitmap doInBackground(String... urls) { return loadImageFromNetwork(urls[0]); } /** The system calls this to perform work in the UI thread and delivers * the result from doInBackground() */ protected void onPostExecute(Bitmap result) { mImageView.setImageBitmap(result); } }
Agora a interface do usuário é segura e o código é simples, porque separa o trabalho na parte que deve ser feita em um thread de trabalho e a parte que deve ser feita no thread de interface de usuário. Você deve ler o AsyncTask de referência para um entendimento completo de como usar essa classe, mas aqui está uma visão geral de como funciona: Você pode especificar o tipo dos parâmetros, os valores de progresso e o valor final da tarefa, usando genéricos O método doInBackground() é executado automaticamente em um segmento de trabalho onPreExecute(), onPostExecute(), e onProgressUpdate() são invocados no thread de interface do usuário O valor retornado por doInBackground() é enviado para onPostExecute() Você pode chamar publishProgress() em qualquer outro momento do doInBackground() para executar onProgressUpdate() na thread da UI Você pode cancelar a tarefa a qualquer hora, de qualquer thread Atenção: Outro problema que se pode encontrar ao usar um segmento de trabalho é reinícios inesperados na sua atividade devido a uma alteração de configuração em tempo de execução (como quando o usuário muda a orientação da tela), que podem
ANDROID, uma visão geral – Anderson Duarte de Amorim
105
destruir seu segmento de trabalho. Para ver como você pode manter a sua tarefa durante um desses reinícios e como fazer corretamente um cancelamento da tarefa quando a atividade for destruída, veja o código fonte para o aplicativo de amostra da Prateleira.
Métodos de Thread-safe Em algumas situações, os métodos a implementar poderiam ser chamados de mais de um thread e, portanto, deve ser escrito para ser thread-safe. Isto é principalmente verdadeiro para métodos que podem ser chamados remotamente, como métodos de um serviço vinculado. Quando uma chamada de um método implementado em um IBinder se origina no mesmo processo em que o IBinder está executando, o método é executado na thread de quem chamou. No entanto, quando a chamada é originada em outro processo, o método é executado em um thread escolhido de um pool de threads que o sistema mantém no mesmo processo que o IBinder (não é executado no thread da interface do usuário do processo). Por exemplo, considerando que um método onBind() do serviço seria chamado do de interface do usuário do processo de serviço, métodos implementados no objeto que onBind() retorna (por exemplo, uma subclasse que implementa métodos RPC) seriam chamados threads no pool. Como um serviço pode ter mais de um cliente, mais de uma pool de threads pode envolver o mesmo método IBinder, ao mesmo tempo. Métodos IBinder devem, portanto, ser implementadas para ser thread-safe. Da mesma forma, um provedor de conteúdo pode receber dados de pedidos que se originam em outros processos. Embora o ContentResolver e ContentProvider ocultam os detalhes de como a comunicação entre processos é gerenciada, métodos ContentProvider que respondem a esses pedidos - os métodos query(), insert(), delete(), update(), e getType() são chamados a partir de um pool de threads no processo de provedor de conteúdo, e não o thread UI para o processo. Como esses métodos podem ser chamados a partir de qualquer número de threads ao mesmo tempo, eles também devem ser implementados para ser thread-safe.
Comunicação entre processos Android oferece um mecanismo para comunicação entre processos (IPC) através de chamadas de procedimento remoto (RPCs), em que um método é chamado por uma ANDROID, uma visão geral – Anderson Duarte de Amorim
106
atividade ou um componente de outro aplicativo, mas executado remotamente (em outro processo), com qualquer resultado retornado de volta para o chamador. Isto implica a decomposição de uma chamada de método e de seus dados a um nível que o sistema operacional possa entender, transmiti-lo a partir do processo do processo local e espaço de endereçamento para o processo remoto e espaço de endereço, em seguida, remontar e reencenar a chamada. Os valores de retorno são transmitidos na direção oposta. Android fornece todo o código para executar essas operações IPC, para que você possa se concentrar na definição e implementação da interface de programação do RPC. Para executar o IPC, o aplicativo deve ligar para um serviço, utilizando bindService().
ANDROID, uma visão geral – Anderson Duarte de Amorim
107
Interface de usuário Em um aplicativo do Android, a interface do usuário é construída usando objetos View e ViewGroup. Existem muitos tipos de views e view groups, cada um dos quais é um descendente da classe View. Objetos view são as unidades básicas de expressão da interface do usuário na plataforma Android. A classe View serve como base para as subclasses chamadas de "widgets", que oferecem completa implementação de objetos de interface do usuário, como campos de texto e botões. A classe ViewGroup serve como base para as subclasses chamadas de "layouts", que oferecem diferentes tipos de layout de arquitetura, como a linear, tabular e relativa. Um objeto View é uma estrutura de dados, cujas propriedades armazenam os parâmetros de layout e de conteúdo para uma determinada área retangular da tela. Um objeto View lida com sua própria medida, layout, desenho, mudança de foco, scrolling e interações telcla/gesto para a área retangular da tela na qual ele reside. Como um objeto na interface do usuário, uma View é também um ponto de interação para o usuário e o receptor de eventos de interação.
Hierarquia de view Sobre a plataforma Android, você define uma interface de atividades, usando uma hierarquia de view e nós ViewGroup, como mostrado no diagrama abaixo. Esta árvore de hierarquia pode ser tão simples ou complexa como você precisa que ela seja, e você pode construí-la usando o conjunto do Android de widgets e layouts pré-definidos, ou com views customizados que você mesmo cria.
ANDROID, uma visão geral – Anderson Duarte de Amorim
108
A fim de fixar a árvore de hierarquia de views à tela para renderização, sua atividade deve chamar o método setContentView() e passar uma referência ao objeto nó raiz. O sistema Android recebe esta referência e usa-o para invalidar, medir e desenhar a árvore. O nó raiz da hierarquia requisita que seus nós filhos desenhem a si próprios - por sua vez, cada nó do ViewGroup é responsável por chamar cada um de seus views filhos para desenhar a si mesmos. Os filhos podem solicitar um tamanho e localização com os pais, mas o objeto-mãe tem a decisão final sobre onde e qual o tamanho que cada criança pode ter. Android analisa os elementos do layout em ordem (a partir do topo da árvore de hierarquia), instanciando os Views e adicionando-os aos seus pais. Porque estes são desenhados em ordem, se há elementos que se sobrepõem posições, o último a ser elaborado vai ficar em cima dos outros previamente elaborados para aquele espaço.
Como o Android desenha views Quando uma atividade recebe o foco, ela será convidada a desenhar o layout. O framework Android vai lidar com o processo de elaboração, mas a atividade deve apresentar o nó raiz da sua hierarquia de layout. Desenho começa com o nó raiz do layout. É solicitado para medir e desenhar a árvore de layout. Desenho é tratado pelo pé da árvore e renderização de cada vista que cruza a região inválida. Por sua vez, cada grupo View é responsável por solicitar a cada um dos seus filhos a ser desenhado (com o método draw()) e cada View é responsável pelo desenho de si. Como a árvore é percorrida em ordem, isso significa que os pais serão desenhados antes do que seus filhos, com irmãos elaborados na ordem em que aparecem na árvore.
ANDROID, uma visão geral – Anderson Duarte de Amorim
109
Desenhar o layout é um processo de duas passagens: a passagem de medidas e uma passagem de layout. A passagem de medição é implementada com measure(int, int) e é uma passagem top-down da árvore. Cada view empurra as especificações de dimensão para baixo na árvore durante a recursividade. No final da passagem de medidas, cada View tem armazenado suas medidas. A segunda fase acontece no layout(int, int, int, int) e também é top-down. Durante o passar, cada pai é responsável pelo posicionamento de todos os seus filhos usando os tamanhos computados na passagem de medidas. Durante um método de retorno measure() da view, os valores getMeasuredWidth() e getMeasuredHeight() devem ser definidos, juntamente com as de todos os descendentes da view. Os valores de medição de largura e altura da view devem respeitar as restrições impostas pelos pais da view. Isso garante que, no final da passagem de medidas, todos os pais aceitam todas as medições de seus filhos. Um pai view pode chamar measure() mais de uma vez sobre seus filhos. Por exemplo, os pais podem medir cada filho uma vez com dimensões não especificadas para descobrir o quão grande eles querem ser, então chama measure() neles novamente com números reais, se a soma dos tamanhos dos filhos irrestrita é muito grande ou muito pequena (Ou seja, se os filhos não concordam entre si a respeito de quanto espaço cada um ganha, o pai vai intervir e estabelecer as regras na segunda passagem). A passagem de medidas utiliza duas classes para
Para dar início a um esquema, chama-
comunicar
se
dimensões.
A
classe
requestLayout().
Este
método
View.MeasureSpec é usada por views para
normalmente é chamado por uma view
contar aos pais como eles querem ser medidos e
sobre si mesma quando se acredita que
posicionados. A classe base LayoutParams apenas descreve quão grandes as views querem
é possível não caber mais dentro de seus limites atuais.
ser para a largura e altura. Para cada dimensão, pode especificar um dos seguintes: um número exato FILL_PARENT, o que significa que o view quer ser tão grande quanto seu pai (menos padding) WRAP_CONTENT, o que significa que o view quer ser grande o suficiente para colocar o seu conteúdo (mais padding).
ANDROID, uma visão geral – Anderson Duarte de Amorim
110
Há subclasses de LayoutParams para diferentes subclasses de ViewGroup. Por exemplo, RelativeLayout tem a sua própria subclasse de LayoutParams, que inclui a capacidade de centralizar os filhos horizontalmente e verticalmente. MeasureSpecs são usados para empurrar os requisitos para baixo da árvore de pai para filho. Um MeasureSpec pode estar em um dos três modos: NÃO ESPECIFICADO: Isto é usado por um dos pais para determinar a dimensão desejada de um filho View. Por exemplo, um LinearLayout pode chamar measure() em seu filho com a altura indefinida e uma largura de exatamente 240. EXATAMENTE: Este é usado pelos pais para impor um tamanho exato sobre a criança. A criança deve usar esse tamanho, e garante que todos os seus descendentes se encaixem dentro desse tamanho. NO MÁXIMO: Este é usado pelos pais para impor um tamanho máximo para a criança. A criança deve garantir que ele e todos os seus descendentes vão caber dentro deste tamanho.
Layout A maneira mais comum para definir o seu layout e expressar a hierarquia de view é com um arquivo de layout XML. XML oferece uma estrutura legível para o layout, muito parecido com HTML. Cada elemento em XML é ou um View ou ViewGroup (ou descendentes dos mesmos). Objetos view são folhas da árvore, objetos ViewGroup são ramos da árvore. O nome de um elemento XML é respectivo para a classe Java que representa. Assim, um elemento <TextView> cria um na sua interface do usuário, e um elemento <LinearLayout> cria um ViewGroup. Quando você carregar um recurso de layout, o sistema Android inicializa esses objetos em tempo de execução, correspondentes aos elementos em seu layout. Por exemplo, um layout simples vertical, com um text view e um botão parece com este: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
ANDROID, uma visão geral – Anderson Duarte de Amorim
111
android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, I am a TextView" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, I am a Button" /> </LinearLayout>
Observe que o elemento LinearLayout contém o TextView e o Botão. Você pode aninhar outro LinearLayout (ou outro tipo de grupo de exibição) aqui dentro, para alongar a hierarquia de view e criar um layout mais complexo. Dica: Você também pode desenhar View e ViewGroups em código Java, utilizando métodos para inserir dinamicamente novos objetos View e ViewGroup. Há uma variedade de maneiras em que você pode formatar os seus view. Usando mais tipos diferentes de grupos de exibição, você pode estruturar views herdados e view groups em um número infinito de maneiras. Alguns view groups pré-definidos oferecidas pelo Android (chamando layouts) incluem LinearLayout, RelativeLayout, TableLayout, GridLayout e outros. Cada um oferece um conjunto exclusivo de parâmetros de layout que são usados para definir as posições das views herdadas e a estrutura de layout. To learn about some of the different kinds of view groups used for a layout, read . Para saber mais sobre alguns dos diferentes tipos de grupos de vista usado para um layout, leia os objetos de layout comum .
Widgets Um widget é um objeto View que serve como uma interface de interação com o usuário. Android fornece um conjunto de widgets plenamente implementadas, como botões, caixas de seleção, e os campos de entrada de texto, assim você pode construir rapidamente sua interface do usuário. Alguns widgets fornecidos pelo Android são mais complexos, como um selecionador de data, um relógio, e controles de zoom. Mas você não está limitado aos tipos de elementos fornecidos pela plataforma Android. Se você quiser fazer algo mais personalizado e criar seus próprios elementos, pode, através da
ANDROID, uma visão geral – Anderson Duarte de Amorim
112
definição de seu objeto View próprio ou através da extensão e combinação de elementos existentes.
ANDROID, uma visão geral – Anderson Duarte de Amorim
113
Eventos UI Uma vez que você adicionou alguns views/widgets para a interface do usuário, você provavelmente quer saber sobre a interação do usuário com eles, para que você possa executar ações. Para ser informado de eventos de interface, você precisa fazer uma de duas coisas: Definir um receptor de evento e registrá-lo com a view. Muitas vezes, é assim que você vai receber os eventos. A classe View contém uma coleção de interfaces aninhadas nomeadas <something> Listener, cada um com um método de retorno de chamada On <something>(). Por exemplo, View.OnClickListener (para lidar com os "cliques" na View), View.OnTouchListener (para lidar com eventos de tela de toque em uma exibição), e View.OnKeyListener (para lidar com teclas pressionadas no dispositivo dentro de uma View). Então se você quer sua View para ser notificado quando ela é "clicada" (como quando um botão é selecionado), implemente OnClickListener e defina o seu método de retorno onClick() (onde você executar a ação após o clique), e registra-o para a View com setOnClickListener(). Substituir um método de retorno existente para a View. Isto é o que você deve fazer quando você implementou sua própria classe View e quer receber eventos específicos que ocorrem dentro dele. Exemplificando eventos você pode manipular inclusão quando a tela é tocada (onTouchEvent()), quando o trackball é movido (onTrackballEvent()), ou quando uma tecla no dispositivo é pressionada (onKeyDown()). Isso permite que você defina o comportamento padrão para cada evento dentro da sua View personalizada e determinar se o evento deve ser transmitido para alguma outra View filho. Novamente, essas são chamadas de retorno para a classe View, assim, sua única chance de defini-los é quando você cria um componente personalizado.
Menus Os menus de aplicativos são outra parte importante da interface do usuário de um aplicativo. Menus oferecem uma interface confiável, que revela funções de aplicativos e configurações. O menu de aplicativos mais comuns é revelado, pressionando a tecla MENU no dispositivo. No entanto, você também pode adicionar menus de contexto,
ANDROID, uma visão geral – Anderson Duarte de Amorim
114
que podem ser revelados quando o usuário pressiona e mantém pressionado em um item. Os menus também são estruturados usando uma hierarquia View, mas você não define essa estrutura por si mesmo. Em vez disso, você define métodos de retorno como onCreateOptionsMenu() ou onCreateContextMenu() para a sua atividade, e declara os itens que você deseja incluir em seu menu. Em momento oportuno, o Android irá criar automaticamente a necessária hierarquia View para o menu e desenhar cada um dos seus itens de menu na mesma. Menus também lidam com seus próprios eventos, por isso não há necessidade de registrar eventos listeners sobre os itens do seu menu. Quando um item no seu menu é selecionado, o método onOptionsItemSelected() ou onContextItemSelected() será chamado pelo framework. E, assim como o layout do aplicativo, você tem a opção de declarar os itens para seu menu em um arquivo XML.
Tópicos Avançados Uma vez que você aprendeu os fundamentos da criação de uma interface, você pode explorar algumas características avançadas para a criação de uma interface de aplicação mais complexa.
Adaptadores Às vezes você deseja preencher um view group com algumas informações que não podem ser codificados, em vez disso, você quer associar a sua view a uma fonte externa de dados. Para fazer isso, use um AdapterView como seu grupo de visão e cada filho view é inicializado e preenchido com os dados do adaptador. O objeto AdapterView é uma implementação do ViewGroup que determina as views dos filhos com base em um determinado adaptador. O adaptador funciona como um mensageiro entre a fonte de dados (talvez um array de strings externa) e os AdapterView, que exibe. Existem várias implementações da classe Adapter, para tarefas específicas, como a CursorAdapter para leitura de dados banco de dados de um cursor ou um ArrayAdapter para a leitura de uma matriz arbitrária.
ANDROID, uma visão geral – Anderson Duarte de Amorim
115
Estilos e Temas Talvez você não esteja satisfeito com a aparência dos widgets padrão. Para revê-los, você pode criar alguns dos seus próprios estilos e temas. Um estilo é um conjunto de um ou mais atributos de formatação que você pode aplicar como uma unidade de elementos individuais em seu layout. Por exemplo, você pode definir um estilo que especifica um determinado tamanho de texto e cor, em seguida, aplicá-lo apenas a elementos específicos. Um tema é um conjunto de um ou mais atributos de formatação que você pode aplicar como uma unidade para todas as atividades em uma aplicação, ou apenas uma atividade única. Por exemplo, você pode definir um tema que define as cores específicas para a moldura da janela e o fundo do painel, e define o tamanho dos textos e cores dos menus. Este tema pode ser aplicado a atividades específicas ou todo o aplicativo. Estilos e temas são recursos. Android oferece alguns recursos de estilo padrão e tema que você pode usar, ou você pode declarar o seu próprio estilo customizado e recursos de tema.
ANDROID, uma visão geral – Anderson Duarte de Amorim
116
Declarando Layout Seu layout é a arquitetura para interface de usuário em uma atividade. Ela define a estrutura de layout e possui todos os elementos que aparecem para o usuário. Você pode declarar o seu layout de duas maneiras: Declare elementos UI em XML - Android fornece um vocabulário XML simples que corresponde às classes View e subclasses, tais como widgets e layouts. Instanciar elementos de layout em tempo de execução - O aplicativo pode criar objetos de View e ViewGroup (e manipular suas propriedades) de forma programaticamente. O framework Android lhe dá a flexibilidade para usar um ou ambos os métodos para declarar e gerenciar a interface do usuário do seu aplicativo. Por exemplo, você poderia declarar layouts padrão de seu aplicativo em XML, incluindo os elementos da tela que aparecerão nela e suas propriedades. Você pode então adicionar o código em seu aplicativo que iria alterar o estado dos objetos da tela, incluindo aqueles declarados em XML, em tempo de execução. A vantagem de declarar a sua interface em XML é que ela permite melhor separar a apresentação da sua aplicação do código que controla o
O plugin ADT para Eclipse oferece um preview do seu layout XML - com o arquivo XML aberto, selecione a guia Layout.
seu comportamento. Suas descrições
Você também deve tentar a ferramenta de
de interface do usuário são externas à
Hierarquia Viewer para depuração de
sua aplicação, o que significa que
layouts – ela revela propriedade de layout,
você pode modificá-lo ou adaptá-lo
desenha
sem ter que modificar seu código-
padding/margin
fonte e recompilar. Por exemplo, você
renderizadas enquanto você depurar no
pode
emulador ou dispositivo.
criar
orientações diferentes dispositivo,
layouts de
tela
tamanhos e
XML
para
diferentes, de
línguas
tela
do
wireframes e
com
indicadores
view
completas
A ferramenta layoutopt permite analisar rapidamente os seus layouts e hierarquias de ineficiências ou outros problemas.
diferentes.
Além disso, declarar o layout em XML torna mais fácil visualizar a estrutura de sua
ANDROID, uma visão geral – Anderson Duarte de Amorim
117
interface do usuário, por isso é mais fácil depurar problemas. Como tal, este documento centra-se em ensiná-lo a declarar o seu layout em XML. Se você estiver interessado em instanciar objetos view em tempo de execução, consulte as classes ViewGroup e View. Em geral, o vocabulário XML para a declaração de elementos da interface segue de perto a estrutura e nomenclatura das classes e métodos, onde os nomes dos elementos correspondem aos nomes de classes e nomes de atributos correspondem aos métodos. De fato, a correspondência é muitas vezes tão direta que você pode adivinhar o atributo XML corresponde a um método de classe, ou adivinhar o que a classe corresponde a um determinado elemento XML. No entanto, note que nem todo vocabulário é idêntico. Em alguns casos, existem ligeiras diferenças de nomenclatura. Por exemplo, o elemento EditText tem um atributo text que corresponde a EditText.setText() . Dica: Saiba mais sobre os diferentes tipos de layout em Common Layout Objects.
Escreve o XML Usando o vocabulário do Android XML, você pode rapidamente criar layouts de interface do usuário e os elementos de tela que eles contêm, da mesma forma que você cria páginas web em HTML - com uma série de elementos aninhados. Cada arquivo de layout deve conter exatamente um elemento de raiz, que deve ser um objeto de view ou ViewGroup. Depois de definir o elemento raiz, você pode adicionar objetos de layout adicionais ou widgets como elementos filho para criar gradualmente uma hierarquia de exibição que define o layout. Por exemplo, aqui está um esquema XML que usa um LinearLayout vertical que contém um TextView e um Button : <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, I am a TextView" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, I am a Button" /> </LinearLayout>
ANDROID, uma visão geral – Anderson Duarte de Amorim
118
Depois de ter declarado o seu layout em XML, salve o arquivo com a extensão .xml em seu projeto Android no diretório res/layout/, assim ele vai compilar corretamente.
Carregar os recursos XML Quando você compilar sua aplicação, cada arquivo de layout XML é compilado em um View de recurso. Você deve carregar o recurso de layout de sua aplicação, em sua implementação de retorno Activity.onCreate(). Faça isso chamando setContentView(), passando a referência ao seu recurso de layout na forma de: R.layout. layout_file_name. Por exemplo, se o seu layout XML é salvo como main_layout.xml, você deve carregá-lo para a sua atividade da seguinte forma: public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_layout); }
O método de retorno onCreate() em sua atividade é chamado pelo framework Android quando sua atividade é lançada.
Atributos Cada objeto View e ViewGroup apóia a sua própria variedade de atributos XML. Alguns atributos são específicos para um objeto View (por exemplo, TextView apóia o atributo textSize), mas esses atributos também são herdadas por qualquer objetos View que podem estender esta classe. Alguns são comuns a todos objetos View, porque eles são herdados da classe View raiz (como o atributo id). E, outros atributos são considerados "parâmetros de layout", que são atributos que descrevem determinadas orientações de layout do objeto View.
ID Qualquer objeto View pode ter um ID de inteiro associado a ele, para identificar o View dentro da árvore. Quando o aplicativo é compilado, essa identificação é referenciada como um inteiro, mas a identificação é normalmente atribuída no layout do arquivo XML como uma string, no atributo id. Este é um atributo XML comum a todos os objetos View (definido pela classe View) e você vai usá-lo muitas vezes. A sintaxe para uma identificação, dentro de uma tag XML é:
ANDROID, uma visão geral – Anderson Duarte de Amorim
119
android:id="@+id/my_button"
O símbolo de arroba (@) no início da string indica que o analisador XML deve analisar e ampliar o resto da seqüência de identificação e identificá-lo como um recurso de identificação. O sinal de mais (+) significa que este é um novo nome de recurso que deve ser criado e adicionado aos nossos recursos (no arquivo R.java). Há uma série de recursos de outro tipo de identificação que são oferecidos pela estrutura do Android. Ao fazer referência a uma identificação de recurso Android, você não precisa do sinal de mais, mas deve adicionar o namespace de pacote android, assim: android:id="@android:id/empty"
Com o namespace de pacote android no lugar, agora estamos fazendo referência a uma ID da classe de recursos android.R, ao invés da classe de recursos locais. A fim de criar views e referenciá-los a partir da aplicação, um padrão comum é: 1. Definir uma view/widget no arquivo de layout e atribuir um ID único: <Button android:id="@+id/my_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/my_button_text"/>
2. Em seguida, crie uma instância do objeto view e capture-o a partir do layout (geralmente no método onCreate()): Button myButton = (Button) findViewById(R.id.my_button);
Definir os IDs dos objetos view é importante ao criar um RelativeLayout. Em um RelativeLayout, views irmãos podem definir a sua disposição em relação a outro view irmão, que é referenciada pela ID exclusiva. Uma identificação não precisa ser única para toda a árvore, mas deve ser exclusiva dentro da parte da árvore que você está procurando (o que pode muitas vezes ser a árvore inteira, por isso é melhor ser completamente original quando possível).
Parâmetros de layout Atributos de layout XML chamados layout_something definem os parâmetros de layout para a view que sejam adequadas para a ViewGroup em que ele reside.
ANDROID, uma visão geral – Anderson Duarte de Amorim
120
Cada
classe
ViewGroup
implementa
uma
classe
aninhada
que
estende
ViewGroup.LayoutParams. Esta subclasse contém os tipos de propriedades que definem o tamanho e a posição de cada view filha, conforme apropriado para o view group. Como você pode ver na figura 1, o grupo de exibição pai define os parâmetros de layout para cada view filho (incluindo o view group dos filhos).
Figura 1. Visualização de uma hierarquia de views com os parâmetros de layout associado a cada exibição.
Note que cada subclasse LayoutParams tem sua própria sintaxe para definir valores. Cada elemento filho deve definir LayoutParams que são apropriadas para seu pai, embora possa também definir LayoutParams diferentes para seus filhos. Todos os view groups incluem uma largura e altura (layout_width e layout_height), e cada view é necessária para defini-los. Muitos LayoutParams também incluem margens opcional e fronteiras. Você pode especificar a largura e altura com medidas exatas, embora você provavelmente não irá querer fazer isso com freqüência. Mais freqüentemente, você vai usar uma dessas constantes para definir a largura ou altura: wrap_content diz a seu view a arranjar a si próprio para as dimensões exigidas pelo seu conteúdo.
ANDROID, uma visão geral – Anderson Duarte de Amorim
121
fill_parent (rebatizada match_parent na API Nível 8) diz à sua view para se tornar tão grande quanto o view group dos pais irá permitir. Em geral, especificar uma largura de layout e altura, utilizando unidades absolutas, tais como pixels não é recomendada. Em vez disso, por meio de medidas como a densidade relativa independente unidades de pixel (DP), wrap_content ou fill_parent, é uma abordagem melhor, porque ajuda a garantir que seu aplicativo irá exibir corretamente através de uma variedade de tamanhos de tela do dispositivo. Os tipos de medição aceitos são definidos no documento Recursos Disponíveis.
Posição de Layout A geometria de uma view é a de um retângulo. Uma view tem uma localização, expresso como um par de coordenadas esquerda e superior, e duas dimensões, expressa em uma largura e uma altura. A unidade para a localização e as dimensões é o pixel. É possível recuperar o local de uma visão chamando os métodos getLeft() e getTop(). O primeiro retorna a esquerda, ou X, de coordenadas do retângulo que representa o view. O segundo retorna o topo, ou Y, coordenadas do retângulo que representa o view. Estes dois métodos devolvem o local da view em relação ao seu pai. Por exemplo, quando o Getleft() retorna 20, significa que o ponto de vista está localizado a 20 pixels para a direita da borda esquerda da sua controladora direta. Além disso, vários métodos de conveniência são oferecidos para evitar cálculos desnecessários, ou seja, getRight() e getBottom(). Esses métodos retornam as coordenadas das bordas direita e inferior do retângulo que representa o view. Por exemplo, chamando getRight() é semelhante ao seguinte cálculo: getLeft() + getWidth().
Tamanho, padding e margin O tamanho de uma view é expressa com uma largura e uma altura. Uma visão realmente possue dois pares de valores de largura e altura. O primeiro par é conhecido como largura medida e altura medida. Essas dimensões definem quão grande quer ser em vista de seu pai. As dimensões medidas podem ser obtidas chamando getMeasuredWidth() e getMeasuredHeight().
ANDROID, uma visão geral – Anderson Duarte de Amorim
122
O segundo par é conhecido simplesmente como largura e altura, ou às vezes a largura de desenho e altura de desenho. Essas dimensões definem o tamanho real do view sobre tela, em tempo de desenho e depois de layout. Esses valores podem ser, mas não tem que ser diferentes da largura e altura medidos. A largura e altura podem ser obtidas chamando getWidth() e getHeight(). Para medir as suas dimensões, uma view leva em conta o seu preenchimento. O preenchimento é expresso em pixels para as partes esquerda, superior, direita e inferior do view. Padding pode ser utilizado para compensar o conteúdo da visão de uma determinada quantidade de pixels. Por exemplo, um padding-left de 2 vai empurrar o conteúdo do view por 2 pixels à direita da margem esquerda. Padding pode ser definido usando o método setPadding(int, int, int, int) e consultado chamando getPaddingLeft(), getPaddingTop(), getPaddingRight() e getPaddingBottom(). Apesar de uma exibição poder definir um padding, isso não prevê qualquer apoio para as margens. No entanto, view groups prestam esse apoio.
ANDROID, uma visão geral – Anderson Duarte de Amorim
123
Criando Menus Os menus são uma parte importante da interface de uma atividade do usuário, que fornecem aos usuários uma forma familiar para executar ações. Android oferece um quadro simples para você adicionar menus padrão para seu aplicativo. Existem três tipos de menus de aplicação:
Menu de Opções A coleção de itens do menu principal para uma atividade, que aparece quando o usuário toca no botão MENU. Quando o aplicativo está rodando o Android 3.0 ou posterior, você pode fornecer acesso rápido para selecionar itens de menu, colocando-os diretamente na barra de ação, como "itens de ação."
Menu de Contexto Uma lista de itens de menu flutuante que aparece quando o usuário toca e tem uma visão que está registrada para fornecer um menu de contexto.
Submenu Uma lista de itens de menu flutuante que aparece quando o usuário toca um item de menu que contém um menu aninhado.
Criando um recurso de menu Em vez de instanciar um Menu no seu código do aplicativo, você deve definir um menu e todos os seus itens em um XML menu resource, em seguida, inflar o recurso de menu (carregá-lo como um objeto programável) no seu código do aplicativo. Utilizando um recurso de menu para definir o seu menu é uma boa prática, pois separa o conteúdo do menu de seu código do aplicativo. É também mais fácil de visualizar a estrutura e o conteúdo de um menu em XML. Para criar um recurso de menu, crie um arquivo XML dentro de seu projeto do diretório res/menu/ e crie o menu com os seguintes elementos:
ANDROID, uma visão geral – Anderson Duarte de Amorim
124
<menu> Define um Menu, que é um recipiente para itens de menu.
Um elemento
<menu> deve ser o nó raiz para o arquivo e pode conter um ou mais <item> e elementos <group>. <item> Cria um MenuItem, o que representa um único item em um menu. Este elemento pode conter um elemento <menu> aninhado, a fim de criar um submenu. <group> Um opcional, recipiente invisível para elementos <item>. Ele permite que você categorize itens de menu para que compartilhe as propriedades tais como estado ativo e visibilidade. Aqui está um exemplo de menu chamado game_menu.xml: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/new_game" android:icon="@drawable/ic_new_game" android:title="@string/new_game" /> <item android:id="@+id/help" android:icon="@drawable/ic_help" android:title="@string/help" /> </menu>
Este exemplo define um menu com dois itens. Cada item inclui os atributos: android:id A identificação do recurso que é único para o item, que permite que o aplicativo possa reconhecer o item quando o usuário seleciona-o. android:icon Uma referência para um desenho para usar como ícone do item. android:title Uma referência a uma string para usar como título do item.
ANDROID, uma visão geral – Anderson Duarte de Amorim
125
Há muito mais atributos que você pode incluir em um <item>, incluindo alguns que especificam como o item pode aparecer na barra de ação.
Inflar um recurso de menu A partir de seu código de aplicativo, você pode inflar um recurso de menu (converter o recurso de XML em um objeto programável) utilizando MenuInflater.inflate(). Por exemplo, o seguinte código infla o arquivo game_menu.xml definido acima, durante o método de retorno onCreateOptionsMenu(), para usar o menu como a atividade do Menu de Opções: @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.game_menu, menu); return true; }
O método getMenuInflater() retorna uma MenuInflater para a atividade. Com este objetivo, você pode chamar inflate(),que infla um recurso de menu em um objeto Menu. Neste exemplo, o recurso de menu definido por game_menu.xml é inflado no Menu que foi passado para onCreateOptionsMenu().
Criando um Menu de Opções Quando executado em um dispositivo com Android 2.3 e inferior, o Menu de Opções aparece na parte inferior da tela, como mostrado na figura 1. Quando aberto, a primeira porção visível do Menu de Opções é o menu do ícone. Ele mantém os seis primeiros itens do menu. Se você adicionar mais de seis itens do Menu de Opções, o Android coloca o sexto item e aqueles após no menu de estouro, que o usuário pode abrir tocando no item "More" do menu.
Figura 1. Screenshot do menu de opções no browser.
ANDROID, uma visão geral – Anderson Duarte de Amorim
126
No Android 3.0 e superior, os itens do Menu de Opções são colocados na barra de ação, que aparece no topo da atividade no lugar da barra de título tradicional. Por padrão todos os itens do Menu de Opções são colocados no menu de estouro, que o usuário pode abrir ao tocar no ícone do menu no lado direito da barra de ação. No entanto, você pode colocar itens de menu diretamente na Barra de ação como "itens de ação", para acesso instantâneo, como mostrado na figura 2.
Figura 2. Imagem da barra de ação na aplicação de e-mail, com dois itens de ação do Menu de Opções, além do menu de estouro.
Quando o sistema Android cria o Menu de Opções, pela primeira vez, ele chama o método onCreateOptionsMenu() da sua atividade. Substituir este método em sua atividade e preencher o Menu que é passado para o método, Menu inflando um recurso de menu como descrito acima em Inflating a Menu Resource. Por exemplo: @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.game_menu, menu); return true; }
Você também pode preencher o menu em código, usando add() para adicionar itens ao Menu. Nota: No Android 2.3 e inferiores, o sistema chama onCreateOptionsMenu() para criar o menu de opções quando o usuário abre pela primeira vez, mas no Android 3.0 e superior, o sistema cria assim que a atividade é criada, a fim e preencher a barra de ação.
Respondendo à ação do usuário Quando o usuário seleciona um item de menu do Menu de Opções (incluindo itens de ação na barra de ação), o sistema chama o método onOptionsItemSelected() da sua atividade. Este método passa o MenuItem que o usuário selecionou. Você pode identificar o item de menu chamando getItemId(), que retorna o ID único para o item de menu (definido pelo atributo android:id no recurso de menu ou com um número inteiro
ANDROID, uma visão geral – Anderson Duarte de Amorim
127
dado ao método add()). Você pode combinar esta identificação contra itens do menu conhecidos e executar a ação apropriada. Por exemplo: @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle item selection switch (item.getItemId()) { case R.id.new_game: newGame(); return true; case R.id.help: showHelp(); return true; default: return super.onOptionsItemSelected(item); } }
Neste exemplo, getItemId() consulta o ID do item de menu selecionado e o comando switch compara o ID contra os IDs de recursos que foram atribuídos aos itens do menu no recurso XML. Quando um switch case manipula o item de menu, ela retorna true para indicar que a seleção de item foi tratada. Caso contrário, a declaração padrão passa o item de menu para a super classe, no caso dele poder lidar com o item selecionado. (Se você diretamente estendeu a classe Activity, então, a super classe retorna false, mas é uma boa prática passar itens de menu não tratados para a classe super em vez de diretamente retornar false). Além disso, o Android 3.0 adiciona a capacidade de definir o comportamento de clicar em um item de menu no menu de recursos XML, usando o atributo android:onClick. Então você não precisa implementar onOptionsItemSelected(). Usando o atributo android:onClick, você pode especificar um método a ser chamado quando o usuário seleciona o item de menu. Sua atividade deve, então, aplicar o método especificado no android:onClick para que ele aceite um único MenuItem de parâmetro, quando o sistema chama este método, ele passa o item de menu selecionado. Dica: Se seu aplicativo contém atividades múltiplas e algumas delas oferecem o mesmo Menu de Opções, considere a criação de uma atividade que não executa nada, exceto os métodos onCreateOptionsMenu() e onOptionsItemSelected(). Em seguida, estenda esta classe para cada atividade que deve compartilhar o mesmo menu de opções. Dessa forma, você tem que gerenciar apenas um conjunto de código para manipular ações de menu
e
cada
classe
descendente
herda
o
comportamento
ANDROID, uma visão geral – Anderson Duarte de Amorim
do
menu.
128
Se você quiser adicionar itens de menu para um dos descendentes de suas atividades, sobreponha
onCreateOptionsMenu()
nessa
atividade.
Chame
super.onCreateOptionsMenu(menu) para que os itens do menu inicial sejam criados, em seguida, adicione novos itens de menu com menu.add(). Você também pode substituir o comportamento da super classe para os itens de menu individuais.
Alterando os itens de menu em tempo de execução Uma vez que a atividade é criada, o método onCreateOptionsMenu() é chamado apenas uma vez, como descrito acima. O sistema mantém e re-utiliza o Menu que você define neste método até que sua atividade seja destruída. Se você quiser mudar o menu de opções a qualquer momento após ter sido criado pela primeira vez, você deve substituir o método onPrepareOptionsMenu(). Isto lhe passa o objeto Menu como ele existe atualmente. Isso é útil se você quiser remover, adicionar, desativar ou ativar itens de menu, dependendo do estado atual de sua aplicação. No Android 2.3 e inferiores, o sistema chama onPrepareOptionsMenu() cada vez que o usuário abre o Menu de Opções. No Android 3.0 e superior, você deve chamar invalidateOptionsMenu() quando você desejar atualizar o menu, porque o menu está sempre aberto. O sistema irá então chamar onPrepareOptionsMenu(), assim você pode atualizar os itens de menu. Nota: Você nunca deve alterar itens no menu de opções com base no View atualmente em foco. Quando estiver no modo de toque (quando o usuário não estiver usando um trackball ou d-pad), views podem não ter foco, então você nunca deve usar o foco como base para a modificação de itens do Menu de Opções. Se você quiser fornecer itens de menu que são sensíveis ao contexto para uma View, use um menu de contexto. Se você está desenvolvendo para o Android 3.0 ou superior, não se esqueça de ler também Usando a Barra de Ação.
Criando um Menu de Contexto Um menu de contexto é conceitualmente semelhante ao menu exibido quando o usuário executa um "clique-direito" em um PC. Você deve usar um menu de contexto para proporcionar ao usuário o acesso às ações que pertencem a um item específico na ANDROID, uma visão geral – Anderson Duarte de Amorim
129
interface do usuário. No Android, um menu de contexto é exibido quando o usuário executa um "toque longo" (pressiona e segura) em um item. Você pode criar um menu de contexto para qualquer view, apesar de menus de contexto serem mais freqüentemente utilizados para os itens em um ListView. Quando o usuário pressiona longamente em um item em uma ListView e a lista está registrada para fornecer um menu de contexto, o item da lista sinaliza para o usuário que um menu de contexto está disponível animando sua cor de fundo, que faz a transição do laranja ao branco antes de abrir o menu de contexto. (O aplicativo Contatos demonstra esta característica.) A fim de fornecer um menu de contexto, você deve "registrar" a view de um menu
Registre uma ListView
Chame
Se a sua atividade usa um ListView e você
registerForContextMenu() e passe a View
deseja que todos os itens da lista forneçam
que você quer dar um menu de contexto.
um menu de contexto, registre todos os
Quando esta view receber um toque de
itens de um menu de contexto, passando o
longa duração, ela exibe um menu de
ListView para registerForContextMenu().
contexto.
Por exemplo, se você estiver usando uma
de
contexto.
ListActivity, registre todos os itens da lista Para definir a aparência e comportamento do menu de contexto, substitua seus
como esta: registerForContextMenu( getListView() );
métodos de retorno do menu de contexto da atividade, onCreateContextMenu() e onContextItemSelected(). Por exemplo, aqui está um onCreateContextMenu() que usa o recurso de menu context_menu.xml: @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.context_menu, menu); }
MenuInflater é usado para inflar o menu de contexto de um recurso de menu. (Você também pode usar o add() para adicionar itens de menu). Os parâmetros de método de retorno incluem o View que o usuário selecionou e ContextMenu.ContextMenuInfo que
ANDROID, uma visão geral – Anderson Duarte de Amorim
130
fornece informações adicionais sobre o item selecionado. Você pode usar esses parâmetros para determinar qual menu de contexto deve ser criado, mas neste exemplo, todos os menus de contexto para a atividade são os mesmos. Então, quando o usuário seleciona um item no menu de contexto, o sistema chama onContextItemSelected(). Aqui está um exemplo de como você pode manipular os itens selecionados: @Override public boolean onContextItemSelected(MenuItem item) { AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); switch (item.getItemId()) { case R.id.edit: editNote(info.id); return true; case R.id.delete: deleteNote(info.id); return true; default: return super.onContextItemSelected(item); } }
A estrutura deste código é semelhante ao exemplo de criação de um Menu de Opções, em que getItemId() consulta o ID do item de menu selecionado e um switch corresponde ao item para as identificações que são definidos no recurso de menu. E como o exemplo do menu de opções, a declaração padrão chama a super classe no caso dela poder lidar com itens de menu não tratados aqui, se necessário. Neste exemplo, o item selecionado é um item da ListView. Para executar uma ação sobre o item selecionado, o aplicativo precisa saber o ID da lista para o item selecionado (a sua posição no ListView). Para obter o ID, o aplicativo chama getMenuInfo(), que retorna um objeto AdapterView.AdapterContextMenuInfo que inclui a identificação de lista para o item selecionado no campo id. Os métodos locais editNote() e deleteNote() aceitam essa identificação da lista para executar uma ação nos dados especificados pelo ID da lista. Nota: Os itens em um menu de contexto não suportam ícones ou teclas de atalho.
ANDROID, uma visão geral – Anderson Duarte de Amorim
131
Criando Submenus Um submenu é um menu que o usuário pode abrir por selecionar um item em outro menu. Você pode adicionar um submenu a qualquer menu (com exceção de um submenu). Submenus são úteis quando seu aplicativo tem um monte de funções que podem ser organizadas em tópicos, como os itens na barra de uma aplicação para PC de menu (Arquivo, Editar, Exibir, etc.) Ao criar o seu recurso de menu, você pode criar um submenu, adicionando um elemento <menu> como o filho de um <item>. Por exemplo: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/file" android:icon="@drawable/file" android:title="@string/file" > <!-- "file" submenu --> <menu> <item android:id="@+id/create_new" android:title="@string/create_new" /> <item android:id="@+id/open" android:title="@string/open" /> </menu> </item> </menu>
Quando o usuário seleciona um item do submenu, o respectivo método de retorno onitem-selected do menu pai recebe o evento. Por exemplo, se o menu acima é aplicado como um Menu de Opções, então o método onOptionsItemSelected() é chamado quando um item de submenu é selecionado. Você também pode usar addSubMenu() para adicionar dinamicamente um SubMenu a um Menu já existente. Isso retorna o novo objeto SubMenu, para o qual você pode adicionar itens de submenu, usando add().
Outras funções do menu Aqui estão algumas outras características que podem ser aplicadas a itens do menu.
Grupos de Menu Um grupo de menu é uma coleção de itens de menu que compartilham certas características.
ANDROID, uma visão geral – Anderson Duarte de Amorim
132
Com um grupo, você pode: Mostrar ou ocultar todos os itens com setGroupVisible() Ativar ou desativar todos os itens com setGroupEnabled() Especificar se todos os itens são verificados com setGroupCheckable() Você pode criar um grupo aninhando elementos <item> dentro de um elemento <group> em seu recurso de menu ou pela especificação de um ID de grupo com o método add(). Aqui está um exemplo de recurso de menu que inclui um grupo: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/item1" android:icon="@drawable/item1" android:title="@string/item1" /> <!-- menu group --> <group android:id="@+id/group1"> <item android:id="@+id/groupItem1" android:title="@string/groupItem1" /> <item android:id="@+id/groupItem2" android:title="@string/groupItem2" /> </group> </menu>
Os itens que estão no grupo aparecem da mesma forma que o primeiro item que não está em um grupo - todos os três itens no menu são irmãos. No entanto, você pode modificar as características dos dois elementos do grupo, referenciando o ID do grupo e utilizando os métodos listados acima.
Itens de menu verificados Um menu pode ser útil como uma interface fazendo das opções ativas e inativas, usando uma caixa de seleção para as opções de stand-alone, ou radion buttons para grupos com opções mutuamente exclusivas. A Figura 2 mostra um submenu com os itens que são verificados com radio buttons.
Figura 3. Screenshot de um submenu com os itens verificados.
ANDROID, uma visão geral – Anderson Duarte de Amorim
133
Nota: Os itens de menu no menu de ícone (no menu Opções) não podem exibir uma caixa de seleção ou radio button. Se você optar por fazer os itens no menu do ícone selecionáveis, você deve manualmente indicar o estado marcado por trocar o ícone e/ou texto cada vez que ocorrem as mudanças de estado. Você pode definir o comportamento checkable para itens individuais de menu usando o atributo android:checkable no elemento <item>, ou para um grupo inteiro com o atributo android:checkableBehavior no elemento <group>. Por exemplo, todos os itens deste grupo de menu são verificados com um botão de rádio: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <group android:checkableBehavior="single"> <item android:id="@+id/red" android:title="@string/red" /> <item android:id="@+id/blue" android:title="@string/blue" /> </group> </menu>
O atributo android:checkableBehavior aceita tanto: single Apenas um item do grupo pode ser verificado (botões de rádio) all Todos os itens podem ser verificados (caixas) none Nenhum item pode ser verificado Você pode aplicar um estado padrão verificado de um item usando o atributo android:checked no elemento <item> e alterá-lo em código com o método setChecked(). Quando um item checkable é selecionado, o sistema chama o seu método de retorno item-selected (como onOptionsItemSelected()). É aqui que você deve definir o estado da caixa, pois um botão de opção ou de rádio não muda o seu estado automaticamente.
ANDROID, uma visão geral – Anderson Duarte de Amorim
134
Você pode consultar o estado atual do item (como era antes que o usuário selecionou) com isChecked() e, em seguida, definir o estado de verificado com setChecked(). Por exemplo: @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.vibrate: case R.id.dont_vibrate: if (item.isChecked()) item.setChecked(false); else item.setChecked(true); return true; default: return super.onOptionsItemSelected(item); } }
Se você não definir o estado de “verificado” dessa forma, o estado visível do item (a caixa de seleção ou botão de rádio) não vai mudar quando o usuário selecioná-lo. Quando você definir o estado, a atividade preserva o estado de verificado do item para que quando o usuário abra o menu mais tarde, o estado de verificado que você definiu esteja visível. Nota: os itens de menu verificáveis se destinam a serem usados apenas em uma sessão base e não serão salvos após a aplicação ser destruída. Se você tiver as configurações do aplicativo que você gostaria de salvar para o usuário, você deve armazenar os dados usando Preferências Compartilhadas.
As teclas de atalho Para facilitar o acesso rápido aos itens do menu de opções quando o dispositivo do usuário tem um teclado físico, você pode adicionar atalhos de acesso rápido usando letras
e/ou
números,
com
os
atributos
android:alphabeticShortcut
e
android:numericShortcut no elemento <item>. Você também pode usar os métodos setAlphabeticShortcut(char) e setNumericShortcut(char). Teclas de atalho não são case sensitive. Por exemplo, se você aplicar o caractere "s" como um atalho alfabético para um item "salvar" de menu, então quando o menu está aberto (ou quando o usuário segura o botão MENU) e o usuário pressiona a tecla "s", o item "Salvar" é selecionado.
ANDROID, uma visão geral – Anderson Duarte de Amorim
135
Esta tecla de atalho é exibida como uma dica no item de menu, abaixo do nome do item de menu (com exceção de itens no menu do ícone, que são exibidas somente se o usuário segura o botão MENU). Nota: as teclas de atalho para itens de menu só funcionam em dispositivos com um teclado de hardware. Atalhos não podem ser adicionado a itens de um menu de contexto.
Adicionar intenções em menu dinamicamente Às vezes você quer um item de menu para iniciar uma atividade usando uma Intent (seja uma atividade em seu aplicativo ou outro aplicativo). Quando você sabe a intenção que você deseja usar e tem um item de menu específico que deve iniciar a intenção, você pode executar a intenção com startActivity() durante o apropriado método de retorno on-item-selected (como o callback onOptionsItemSelected()). No entanto, se você não tiver certeza de que o dispositivo do usuário contém um aplicativo que lida com a intenção, então adicionar um item de menu que chama isso pode resultar em um item de menu que não funciona, porque a intenção pode não resolver a uma atividade. Para resolver isso, Android permite a você adicionar dinamicamente os itens do menu para o menu quando Android encontra atividades sobre o dispositivo que lidam com sua intenção. Para adicionar itens de menu baseado em atividades disponíveis que aceitam uma intenção: 1. Definir uma intenção, com a categoria CATEGORY_ALTERNATIVE e/ou CATEGORY_SELECTED_ALTERNATIVE,
além
de
quaisquer
outras
exigências. 2. Chamar Menu.addIntentOptions(). Android então procura todas as aplicações que podem executar a intenção e adiciona-os ao seu menu. Se não houver aplicativos instalados que satisfazem a intenção, então não há itens de menu que sejam adicionados.
ANDROID, uma visão geral – Anderson Duarte de Amorim
136
Nota: CATEGORY_SELECTED_ALTERNATIVE é usada para segurar o elemento selecionado atualmente na tela. Assim, ele só deve ser utilizado para a criação de um menu em onCreateContextMenu(). Por exemplo: @Override public boolean onCreateOptionsMenu(Menu menu){ super.onCreateOptionsMenu(menu); // Create an Intent that describes the requirements to fulfill, to be included // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE. Intent intent = new Intent(null, dataUri); intent.addCategory(Intent.CATEGORY_ALTERNATIVE); // Search and populate the menu with acceptable offering applications. menu.addIntentOptions( R.id.intent_group, // Menu group to which new items will be added 0, // Unique item ID (none) 0, // Order for the items (none) this.getComponentName(), // The current activity name null, // Specific items to place first (none) intent, // Intent created above that describes our requirements 0, // Additional flags to control items (none) null); // Array of MenuItems that correlate to specific items (none) return true; }
Para cada atividade descoberta que fornece uma intenção de filtro correspondente a intenção definida um item de menu é adicionado, utilizando o valor na intenção do filtro android:label como o título do item de menu e o ícone do aplicativo como o item ícone do menu. O método addIntentOptions() retorna o número de itens de menu que foram acrescentados. Nota: Quando você chamar addIntentOptions(), ela substitui qualquer e todos os itens do menu, o grupo de menu especificado no primeiro argumento.
Permitindo a sua atividade a ser adicionada para outros menus Você também pode oferecer os serviços de sua atividade para outras aplicações, para que seu aplicativo possa ser incluído no menu de outros (inverter os papéis descritos acima).
ANDROID, uma visão geral – Anderson Duarte de Amorim
137
Para ser incluído em outros menus de aplicativos, você precisa definir uma intenção de filtro,
como
de
costume,
CATEGORY_ALTERNATIVE
mas e/ou
não
se
esqueça
de
incluir
o
CATEGORY_SELECTED_ALTERNATIVE
para o filtro de categoria intenções. Por exemplo: <intent-filter label="Resize Image"> ... <category android:name="android.intent.category.ALTERNATIVE" /> <category android:name="android.intent.category.SELECTED_ALTERNATIVE" /> ... </intent-filter>
Leia mais sobre como escrever filtros de intenção em Intents and Intents Filters.
ANDROID, uma visão geral – Anderson Duarte de Amorim
138
Usando a barra de ação A barra de ação é um widget para atividades que substitui a tradicional barra de título no topo da tela. Por padrão, a barra de ação inclui o logotipo da aplicação do lado esquerdo, seguido do título da atividade, e todos os itens disponíveis no menu Opções no lado direito. A Barra de ação oferece vários recursos úteis, incluindo a capacidade de: Exibir itens do Menu de Opções diretamente na Barra de ação, como "itens de ação" - fornecendo acesso imediato às ações-chave dos usuários. Os itens de menu que não aparecem como itens de ação são colocados no menu de estouro, revelado por uma lista suspensa na barra de ações. Fornecer as guias para navegar entre os fragmentos. Forneça uma lista drop-down para a navegação. Fornecer interativas "visões de ação" no lugar de itens de ação (como uma caixa de pesquisa).
Figura 1. Uma imagem da barra de ação na aplicação de e-mail, contendo itens de ação para compor novo email e atualizar a caixa de entrada.
Adicionando a barra de ação A barra de ação é incluída por padrão em todas as atividades que visam o Android 3.0 ou superior. Mais especificamente, todas as atividades que usam o novo tema "holográfico" incluem a barra de ação, e qualquer aplicação que tem como alvo o Android 3.0 automaticamente recebe esse tema. Uma aplicação é considerada para "o alvo" Android 3.0 quando é definido tanto o atributo android:minSdkVersion ou android:targetSdkVersion no elemento <uses-sdk> para "11" ou superior. Por exemplo:
ANDROID, uma visão geral – Anderson Duarte de Amorim
139
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.helloworld" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="11" /> <application ... > ... </application> </manifest>
Neste exemplo, a aplicação requer uma versão mínima do API Nível 4 (Android 1.6), mas também atinge API Nível 11 (Android 3.0). Dessa forma, quando o aplicativo é instalado em um dispositivo rodando Android 3.0 ou superior, o sistema aplica o tema holográfico para cada atividade, e assim, cada atividade inclui a Barra de Ações. No entanto, se você quiser usar APIs de Barra de Ações, tais como guias para adicionar ou modificar estilos da barra de ação, você precisa definir o android:minSdkVersion para "11", assim você pode acessar a classe ActionBar.
Removendo a barra de ação Se você quiser remover a barra de ação para uma determinada atividade, defina o tema da atividade para Theme.Holo.NoActionBar. Por exemplo: <activity android:theme="@android:style/Theme.Holo.NoActionBar">
Dica: Se você tem um tema de atividade personalizado no qual você gostaria de remover a barra de ação, defina a propriedade de estilo android:windowActionBar false. Você também pode ocultar a barra de ação em tempo de execução chamando hide(), e depois mostrá-la novamente chamando show(). Por exemplo: ActionBar actionBar = getActionBar(); actionBar.hide();
Quando a barra de ação se esconde, o sistema ajusta seu conteúdo das atividades para preencher todo o espaço disponível na tela. Nota: Se você remover a Barra de ação usando um tema, a janela não vai permitir a barra de ação a todos, então você não pode adicioná-la em tempo de execução chamar getActionBar() irá retornar null.
ANDROID, uma visão geral – Anderson Duarte de Amorim
140
Adicionando itens de ação Um item de ação que é simplesmente um item do menu de opções que você declara deve aparecer diretamente na barra de ações. Um item de ação pode incluir um ícone e/ou texto. Se um item de menu não aparece como um item de ação, o sistema coloca no menu de estouro, que o usuário pode abrir com o ícone do menu no lado direito da barra de ação.
Figura 2. Uma barra de ação com dois itens de ação e menu de estouro.
Quando a primeira atividade começa, o sistema preenche a barra de ação e menu de estouro chamando onCreateOptionsMenu() para sua atividade. Conforme discutido no guia para a criação de menus, é neste método callback que você define o menu de opções para a atividade. Você pode especificar um item de menu para aparecer como um item de ação, se não houver espaço para isso, a partir do seu recurso de menu, declarando android:showAsAction="ifRoom" para o elemento <item>. Desta forma, o item de menu aparece na barra de ação para um acesso rápido apenas se houver espaço disponível para ele. Se não houver espaço suficiente, o item é colocado no menu de transbordamento (revelado pelo ícone do menu no lado direito da barra de ação). Você também pode declarar um item de menu para aparecer como um item de ação a partir do seu código de aplicativo, chamando setShowAsAction() no MenuItem e passando SHOW_AS_ACTION_IF_ROOM. Se o item de menu fornece tanto um título como um ícone, então o item de ação mostra apenas o ícone por default. Se você quiser incluir o texto do item de ação, adicione a flag "with text": em XML, adicionar withText para o atributo android:showAsAction ou, no código do aplicativo, use a flag SHOW_AS_ACTION_WITH_TEXT ao chamar setShowAsAction(). A Figura 2 mostra uma barra de ação que tem dois itens de ação com o texto e o ícone para o menu de estouro. Aqui está um exemplo de como você pode declarar um item de menu como um item de ação em um arquivo de recurso de menu:
ANDROID, uma visão geral – Anderson Duarte de Amorim
141
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_add" android:icon="@drawable/ic_menu_save" android:title="@string/menu_save" android:showAsAction="ifRoom|withText" /> </menu>
Neste caso, tanto as flags ifRoom e withText estão definidas, de modo que quando este item aparece como um item de ação, inclui o texto do título, juntamente com o ícone. Um item de menu colocado na barra de ação dispara os mesmos métodos de callback que outros itens do menu de opções. Quando o usuário seleciona um item de ação, sua atividade recebe uma chamada para onOptionsItemSelected(), passando o ID do item. Nota: Se você adicionou o item de menu de um fragmento, o respectivo método onOptionsItemSelected()é chamado para esse fragmento. No entanto, a atividade tem a chance de manipulá-lo primeiro, então o sistema chama onOptionsItemSelected() da atividade antes de chamar o fragmento. Você também pode declarar um item que sempre aparece como um item de ação, mas você deve evitar fazê-lo, porque ele pode criar uma interface confusa se houver muitos itens de ação e também podem colidir com outros elementos na barra de ações.
Usando o ícone do aplicativo como um item de ação Por padrão, o ícone do aplicativo aparece na barra de ações, no lado esquerdo. Ele também responde à interação do usuário (quando o usuário toca, ele responde visualmente do mesmo jeito que os itens de ação) e que é sua responsabilidade fazer algo quando o usuário toca-os.
Figura 3. Barra de ação de email, com o ícone do aplicativo no lado esquerdo.
O comportamento deve ser normal para a sua aplicação para regressar à "HOME" à atividade ou ao estado inicial (como quando a atividade não mudou, mas os fragmentos foram alterados) quando o usuário toca o ícone. Se o usuário já está em casa ou no estado inicial, então você não precisa fazer nada.
ANDROID, uma visão geral – Anderson Duarte de Amorim
142
Quando o usuário toca o ícone, o sistema chama o método onOptionsItemSelected() de sua atividade com o ID android.R.id.home. Então, você precisa adicionar uma condição para o seu método onOptionsItemSelected() para receber android.R.id.home e executar a ação apropriada, como iniciar a atividade inicial ou remover recentes transações do fragmento fora da pilha. Se você responder para o ícone do aplicativo, retornando à atividade inicial, você deve incluir o flag FLAG_ACTIVITY_CLEAR_TOP na Intent. Com este indicador, se a atividade que você está começando já existe na tarefa atual, então todas as atividades em cima dela são destruídas e essa é trazida para frente. Você deve favorecer essa abordagem, porque ir para "HOME" é uma ação que é equivalente a "voltar atrás" e você geralmente não deve criar uma nova instância da atividade inicial. Caso contrário, você pode acabar com uma longa pilha de atividades na tarefa atual. Por exemplo, aqui está uma implementação do onOptionsItemSelected() que retorna para a aplicação da "HOME" de atividade: @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: // app icon in Action Bar clicked; go home Intent intent = new Intent(this, HomeActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); return true; default: return super.onOptionsItemSelected(item); } }
Usando o ícone do aplicativo para navegar "para cima" Você também pode usar o ícone do aplicativo para oferecer uma navegação "para cima" para o usuário. Isto é especialmente útil quando o aplicativo é composto por atividades que geralmente aparecem em uma certa ordem e se pretende facilitar a habilidade do usuário para navegar na hierarquia de atividade.
A maneira como você responde a este evento é a mesma de quando navegando para HOME (como discutido acima, exceto se você iniciar uma atividade diferente, com base ANDROID, uma visão geral – Anderson Duarte de Amorim
143
na atividade corrente). Tudo que você precisa fazer para indicar ao usuário que o comportamento é diferente é definir a barra de ação para "mostrar a HOME como „para cima‟". Você pode fazer isso chamando setDisplayHomeAsUpEnabled(true) na barra de ações da sua atividade. Quando o fizer, o sistema retira o ícone do aplicativo com uma seta indicando o comportamento „para cima‟, como mostrado acima. Por exemplo, aqui está como você pode mostrar o ícone do aplicativo como uma ação "para cima": @Override protected void onStart() { super.onStart(); ActionBar actionBar = this.getActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); }
Então, sua atividade deve responder ao usuário tocar no ícone, a partir do onOptionsItemSelected(), recebendo o ID android.R.id.home (como mostrado acima). Neste caso, ao navegar para cima, é ainda mais importante que você use o FLAG_ACTIVITY_CLEAR_TOP na Intent, de modo que você não cria uma nova instância da atividade do pai, se já existe um.
Adicionando uma exibição de ação
Figura 5. Uma visão de ação com um widget SearchView.
Uma exibição de ação é um widget que aparece na barra de ação como um substituto para um item de ação. Por exemplo, se você tem um item no menu opções para "Busca", você pode adicionar uma exibição de ação para o item que fornece um widget SearchView na barra de ação sempre que o item é ativado como um item de ação. Ao adicionar uma visão de ação para um item de menu, é importante que você ainda permita ao item se comportar como um item de menu normal quando ela não aparece na barra de ações. Por exemplo, um item de menu para realizar uma pesquisa deve, por padrão, abrir a janela de pesquisa do Android, mas se o item é colocado na barra de ação, a visão de ação aparece com um widget SearchView. A Figura 5 mostra um exemplo do SearchView em uma visão de ação.
ANDROID, uma visão geral – Anderson Duarte de Amorim
144
A melhor forma de declarar uma visão de ação para um item está em seu recurso de menu, usando o atributo android:actionLayout ou android:actionViewClass: O valor para android:actionLayout deve ser um ponteiro de recurso para um arquivo de layout. Por exemplo: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_search" android:title="Search" android:icon="@drawable/ic_menu_search" android:showAsAction="ifRoom" android:actionLayout="@layout/searchview" /> </menu>
O valor para android:actionViewClass deve ser um nome de classe qualificado para o View que você deseja usar. Por exemplo: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_search" android:title="Search" android:icon="@drawable/ic_menu_search" android:showAsAction="ifRoom" android:actionViewClass="android.widget.SearchView" /> </menu>
Você deve incluir android:showAsAction="ifRoom" para que o item apareça como uma visão de ação quando o room está disponível. Se necessário, no entanto, pode forçar o item que sempre apareça como uma visão de ação, definindo android:showAsAction para "always". Agora, quando o item de menu é exibido como um item de ação, esta vista de ação aparece em vez do ícone e/ou texto do título. No entanto, se não houver espaço suficiente na barra de ação, o item aparece no menu de estouro como um item de menu normal
e
você
deve
responder
a
ela
a
partir
do
método
de
retorno
onOptionsItemSelected(). Quando a primeira atividade começa, o sistema preenche a barra de ação e menu de estouro chamando onCreateOptionsMenu(). Depois de ter inflado seu menu neste método, você pode adquirir elementos em uma visão de ação (talvez a fim de anexar ouvintes) chamando findItem() com o ID do item de menu, então getActionView() no
ANDROID, uma visão geral – Anderson Duarte de Amorim
145
MenuItem retornado. Por exemplo, o widget de busca do modelo acima é adquirido como este: @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.options, menu); SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); // Set appropriate listeners for searchView ... return super.onCreateOptionsMenu(menu); }
Adicionando abas
Figura 6. Screenshot de abas na barra de ação, do modelo de aplicação do Honeycomb Gallery.
A Barra de ação pode exibir guias que permitem ao usuário navegar entre diferentes fragmentos na atividade. Cada guia pode incluir um título e/ou um ícone. Para começar, o esquema deve incluir um View, em que cada Fragment associado com uma guia é exibida. Tenha certeza de que tem uma identificação que pode ser usada para fazer referência a ela em seu código. Para adicionar guias para a barra de ações: 1. Criar uma aplicação de ActionBar.TabListener para manipular os eventos de interação na barra de guias de ação. Você deve implementar todos os métodos: onTabSelected(), onTabUnselected(), e onTabReselected(). Cada método de retorno passa a ActionBar.Tab que recebeu o evento e um FragmentTransaction para você efetuar as transações do fragmento (adicionar ou remover fragmentos).
ANDROID, uma visão geral – Anderson Duarte de Amorim
146
Por exemplo: private class MyTabListener implements ActionBar.TabListener { private TabContentFragment mFragment; // Called to create an instance of the listener when adding a new tab public TabListener(TabContentFragment fragment) { mFragment = fragment; } @Override public void onTabSelected(Tab tab, FragmentTransaction ft) { ft.add(R.id.fragment_content, mFragment, null); } @Override public void onTabUnselected(Tab tab, FragmentTransaction ft) { ft.remove(mFragment); } @Override public void onTabReselected(Tab tab, FragmentTransaction ft) { // do nothing } }
Esta implementação de ActionBar.TabListener adiciona um construtor que salva o Fragment associado com um guia para que cada retorno possa adicionar ou remover esse fragmento. 2. Receba a ActionBar para a sua atividade, chamando getActionBar() de sua atividade, durante onCreate() (mas não se esqueça de fazer isso depois de você ter chamado setContentView()). 3. Chame setNavigationMode(NAVIGATION_MODE_TABS) para habilitar o modo guia para a ActionBar . 4. Criar cada guia para a barra de ação: a. Criar uma nova ActionBar.Tab chamando newTab() na ActionBar . b. Acrescentar o texto do título e/ou um ícone para o guia, chamando setText() e/ou setIcon(). Dica: Esses métodos retornam a mesma instância ActionBar.Tab, assim você pode encadear as chamadas juntas.
ANDROID, uma visão geral – Anderson Duarte de Amorim
147
c. Declare o ActionBar.TabListener para usar a guia por meio de um exemplo de sua aplicação para setTabListener(). 5. Adicione cada ActionBar.Tab à barra de ação, chamando addTab() na ActionBar e passe a ActionBar.Tab. Por exemplo, o código a seguir combina as etapas 2-5 para criar duas guias e adicionálas à Barra de ação: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // setup Action Bar for tabs final ActionBar actionBar = getActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); // remove the activity title to make space for tabs actionBar.setDisplayShowTitleEnabled(false); // instantiate fragment for the tab Fragment artistsFragment = new ArtistsFragment(); // add a new tab and set its title text and tab listener actionBar.addTab(actionBar.newTab().setText(R.string.tab_artists) .setTabListener(new TabListener(artistsFragment))); Fragment albumsFragment = new AlbumsFragment(); actionBar.addTab(actionBar.newTab().setText(R.string.tab_albums) .setTabListener(new TabListener(albumsFragment))); }
Todos os comportamentos que ocorrem quando uma guia é selecionada devem ser definidos pelo seu método de callback ActionBar.TabListener. Quando uma guia é selecionada, ela recebe uma chamada para onTabSelected() e é aí que você deve adicionar o fragmento apropriado para a exibição designada em seu layout, utilizando add() com o previsto FragmentTransaction. Da mesma forma, quando uma guia é desmarcada (porque outra guia é selecionada), você deve remover o fragmento do layout, utilizando remove(). Atenção: Você não deve chamar commit() para essas operações, o sistema chama-o para você e pode lançar uma exceção se você chamá-lo. Você também não pode adicionar essas transações de fragmento para o fundo da pilha. Se a sua atividade é interrompida, você deve manter a guia selecionada no momento com o estado salvo de modo que quando o usuário retorna à sua aplicação, você pode ANDROID, uma visão geral – Anderson Duarte de Amorim
148
abrir a guia. Quando é hora de salvar o estado, você pode consultar a atual guia selecionada com getSelectedNavigationIndex(). Isso retorna a posição do índice da guia selecionada. Atenção: É importante que você salve o estado de cada fragmento quando necessário, pois quando o usuário alterna fragmentos com as guias, e em seguida, retorna a um fragmento anterior, aparece o caminho que ele deixou. Para obter informações sobre como salvar o seu estado de fragmento, consulte o guia do desenvolvedor sobre Fragmentos.
Adicionando de navegação drop-down Como um outro modo de navegação em sua atividade, você pode fornecer uma lista suspensa na barra de ações. Por exemplo, a lista drop-down pode fornecer os meios alternativos para classificar o conteúdo da atividade ou mudar a conta do usuário. Aqui está uma lista rápida de medidas para permitir a navegação drop-down: 1. Criar um SpinnerAdapter que fornece a lista de itens selecionáveis para o dropdown e a disposição para usar ao desenhar cada item na lista. 2. Implementar ActionBar.OnNavigationListener para definir o comportamento quando o usuário seleciona um item da lista. 3. Habilitar o modo de navegação para a barra de ação com setNavigationMode(). Por exemplo: ActionBar actionBar = getActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
Nota: Você deve executar isto durante a sua atividade do método onCreate(). 4. Em
seguida,
defina
o
retorno
para
a
lista
drop-down
com
setListNavigationCallbacks(). Por exemplo: actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationCallback);
Este método tem a sua SpinnerAdapter e ActionBar.OnNavigationListener. Essa é a configuração básica. No entanto, a implementação da SpinnerAdapter e ActionBar.OnNavigationListener é onde a maioria do trabalho é feito. Há muitas
ANDROID, uma visão geral – Anderson Duarte de Amorim
149
maneiras que você pode aplicar isto para definir a funcionalidade para o seu drop-down de navegação e implementar vários tipos de SpinnerAdapter além do escopo deste documento (você deve referenciar para a classe SpinnerAdapter para obter mais informações). No entanto, abaixo há um exemplo simples para um SpinnerAdapter e ActionBar.OnNavigationListener para você começar.
Exemplo de SpinnerAdapter e OnNavigationListener SpinnerAdapter é um adaptador que fornece dados para um widget spinner, como a lista drop-down na barra de ações. SpinnerAdapter é uma interface que você pode implementar, mas Android inclui algumas implementações úteis que você pode estender, como ArrayAdapter e SimpleCursorAdapter. Por exemplo, aqui está uma maneira fácil de criar um SpinnerAdapter usando a implementação de ArrayAdapter, que usa uma matriz de string como fonte de dados: SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.action_list, android.R.layout.simple_spinner_dropdown_item);
O método createFromResource() usa três parâmetros: a aplicação Context, a identificação de recursos para array de string, e o layout a ser usado para cada item da lista. Um array de string é definido em um recurso parecido com este: <?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="action_list"> <item>Mercury</item> <item>Venus</item> <item>Earth</item> </string-array> </pre>
O ArrayAdapter retornado por createFromResource() está completo e pronto para você passar a setListNavigationCallbacks() (na etapa 4 acima). Antes de fazer, porém, você precisa criar o OnNavigationListener. A implementação do ActionBar.OnNavigationListener é onde você lida com as mudanças de fragmento ou outras modificações à sua atividade quando o usuário seleciona um item da lista drop-down. Há apenas um método callback para implementar o receptor: onNavigationItemSelected().
ANDROID, uma visão geral – Anderson Duarte de Amorim
150
O método onNavigationItemSelected() recebe a posição do item na lista e um ID de único item fornecido pela SpinnerAdapter. Aqui
está
um
exemplo
que
instancia
uma
implementação
anônima
de
OnNavigationListener, que insere um Fragment dentro do recipiente layout identificado por R.id.fragment_container: mOnNavigationListener = new OnNavigationListener() { // Get the same strings provided for the drop-down's ArrayAdapter String[] strings = getResources().getStringArray(R.array.action_list); @Override public boolean onNavigationItemSelected(int position, long itemId) { // Create new fragment from our own Fragment class ListContentFragment newFragment = new ListContentFragment(); FragmentTransaction ft = openFragmentTransaction(); // Replace whatever is in the fragment container with this fragment // and give the fragment a tag name equal to the string at the position selected ft.replace(R.id.fragment_container, newFragment, strings[position]); // Apply changes ft.commit(); return true; } };
Esta instância de OnNavigationListener está completo e agora você pode chamar setListNavigationCallbacks() (na etapa 4), passando o ArrayAdapter e este OnNavigationListener. Neste exemplo, quando o usuário seleciona um item da lista drop-down, um fragmento é adicionado ao layout (que substitui o fragmento atual no R.id.fragment_container vista). O fragmento adicional é dada uma etiqueta que identifica-lo, que é a mesma seqüência de caracteres usado para identificar o fragmento na lista drop-down.
ANDROID, uma visão geral – Anderson Duarte de Amorim
151
Aqui está um olhar da classe ListContentFragment que define cada fragmento neste exemplo: public class ListContentFragment extends Fragment { private String mText; @Override public void onAttach(Activity activity) { // This is the first callback received; here we can set the text for // the fragment as defined by the tag specified during the fragment transaction super.onAttach(activity); mText = getTag(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // This is called to define the layout for the fragment; // we just create a TextView and set its text to be the fragment tag TextView text = new TextView(getActivity()); text.setText(mText); return text; } }
Estilizando a barra de ação A barra de ação é o título do seu aplicativo e um ponto de interação primário para os usuários, então você pode querer modificar alguns em seu projeto, a fim de torná-lo sentir mais integrado com o design do aplicativo. Há várias maneiras que você pode fazer isso se quiser. Para modificações simples para o ActionBar, você pode usar os métodos a seguir: setBackgroundDrawable() Define um drawable para usar como fundo da barra de ação. O drawable deve ser uma imagem Nine-patch, uma forma, ou uma cor sólida, para que o sistema pode redimensionar a drawable com base no tamanho da barra de ação (você não deve usar uma imagem bitmap de tamanho fixo). setDisplayUseLogoEnabled() Permite o uso de uma imagem alternativa (a "logo") na barra de ação, em vez do ícone do aplicativo padrão. Um logotipo é muitas vezes uma imagem mais ampla, mais detalhada que representa a aplicação. Quando isso estiver ativado, o ANDROID, uma visão geral – Anderson Duarte de Amorim
152
sistema utiliza a imagem do logotipo definido para a aplicação (ou a atividade individual) no arquivo de manifesto, com o atributo android:logo. O logotipo será redimensionado, conforme necessário para ajustar a altura da barra de ação. (Melhores práticas é projetar o logotipo com o mesmo tamanho do ícone do aplicativo.) Para personalizações mais complexas, você pode usar estilos e temas do Android para remodelar sua barra de ação de várias maneiras. A Barra de ação tem dois temas padrão, "dark" e "light". O tema escuro é aplicado com o tema padrão holográfico, conforme especificado pelo tema Theme.Holo. Se você quiser um fundo branco com texto escuro, em vez disso, você pode aplicar o tema Theme.Holo.Light para a atividade no arquivo de manifesto. Por exemplo: <activity android:name=".ExampleActivity" android:theme="@android:style/Theme.Holo.Light" />
Para ter mais controle, você pode substituir o tema Theme.Holo ou Theme.Holo.Light e aplicar estilos personalizados para determinados aspectos da Barra de Ações. Algumas das propriedades da barra de ação você pode personalizar incluindo o seguinte: android:actionBarTabStyle Estilo de abas na barra de ação. android:actionBarTabBarStyle Estilo para a barra que aparece abaixo das abas na barra de ação. android:actionBarTabTextStyle Estilo para o texto nos separadores. android:actionDropDownStyle Estilo para a lista drop-down utilizado para o menu de navegação de transbordamento e drop-down. android:actionButtonStyle Estilo para a imagem de fundo usado para os botões na barra de ação.
ANDROID, uma visão geral – Anderson Duarte de Amorim
153
Por exemplo, aqui está um arquivo de recurso que define um tema personalizado para a barra de ação, baseado no tema padrão Theme.Holo: <?xml version="1.0" encoding="utf-8"?> <resources> <!-- the theme applied to the application or activity --> <style name="CustomActionBar" parent="android:style/Theme.Holo.Light"> <item name="android:actionBarTabTextStyle">@style/customActionBarTabTextStyle</item> <item name="android:actionBarTabStyle">@style/customActionBarTabStyle</item> <item name="android:actionBarTabBarStyle">@style/customActionBarTabBarStyle</item> </style> <!-- style for the tab text --> <style name="customActionBarTabTextStyle"> <item name="android:textColor">#2966c2</item> <item name="android:textSize">20sp</item> <item name="android:typeface">sans</item> </style> <!-- style for the tabs --> <style name="customActionBarTabStyle"> <item name="android:background">@drawable/actionbar_tab_bg</item> <item name="android:paddingLeft">20dp</item> <item name="android:paddingRight">20dp</item> </style> <!-- style for the tab bar --> <style name="customActionBarTabBarStyle"> <item name="android:background">@drawable/actionbar_tab_bar</item> </style> </resources>
Nota: Para que a imagem de fundo guia mude, dependendo do estado de separador atual (selecionado, pressionado, não selecionado), o recurso drawable utilizado deve ser uma lista de estado drawable. Também é certo que o tema declara um tema principal, da qual ele herda todos os estilos não explicitamente declarados em seu tema. Você pode aplicar o seu tema personalizado para o aplicativo inteiro ou para atividades individuais no arquivo de manifesto, como este: <application android:theme="@style/CustomActionBar" ... />
Além disso, se você quer criar um tema personalizado para a sua atividade que remove a barra de ação completamente, use os atributos de estilo a seguir:
ANDROID, uma visão geral – Anderson Duarte de Amorim
154
android:windowActionBar Defina esta propriedade de estilo como false para remover a barra de ações. android:windowNoTitle Defina esta propriedade de estilo como true também para remover a barra de título tradicional.
ANDROID, uma visão geral – Anderson Duarte de Amorim
155
Criando caixas de diálogo Uma caixa de diálogo é geralmente uma pequena janela que aparece na frente da atividade atual. A atividade em causa perde o foco e o diálogo aceita todas as interações do usuário. Os diálogos são normalmente utilizados para as notificações que devem interromper o usuário e para executar tarefas curtas que se relacionam diretamente com a aplicação em curso (como uma barra de progresso ou um prompt de login). A classe Dialog é a classe base para a criação de diálogos. No entanto, você geralmente não deve instanciar um Dialog diretamente. Em vez disso, você deve usar uma das seguintes subclasses: AlertDialog Uma caixa de diálogo que pode gerenciar zero, um, dois ou três botões, e/ou uma lista de itens selecionáveis que podem incluir caixas de verificação ou botões de rádio. O AlertDialog é capaz de construir a maioria das interfaces de diálogo com o usuário e é o tipo de caixa de diálogo sugerido. ProgressDialog Uma caixa de diálogo que exibe uma roda de progresso ou barra de progresso. Porque é uma extensão do AlertDialog, ele também suporta botões. DatePickerDialog Um diálogo que permite que o usuário selecione uma data. TimePickerDialog Um diálogo que permite ao usuário selecionar um tempo. Se você quiser personalizar a sua própria caixa de diálogo, você pode estender a base Dialog ou qualquer objeto das subclasses acima e definir um novo layout.
Mostrando uma caixa de diálogo Um diálogo é sempre criado e exibido como parte de uma Activity. Você normalmente deve criar diálogos de dentro do método de retorno onCreateDialog(int) da sua atividade. Quando você usa esta chamada, o sistema Android gerencia automaticamente
ANDROID, uma visão geral – Anderson Duarte de Amorim
156
o estado de cada diálogo e os ganchos para a atividade, efetivamente tornando-o o "dono" de cada diálogo. Como tal, cada diálogo herda algumas propriedades da atividade. Por exemplo, quando uma janela é aberta, a tecla Menu revela o menu de opções definidas para a atividade e as teclas de volume modificam o fluxo de áudio usado pela atividade. Nota: Se você decidir criar um diálogo fora do método onCreateDialog(), não irá ser anexado a uma atividade. Você pode, entretanto, anexá-lo a uma atividade com setOwnerActivity(Activity). Quando você quer mostrar uma janela, chame showDialog(int) e passe um número inteiro que identifica a caixa de diálogo que você deseja exibir. Quando
uma
janela
é
solicitada
pela
primeira
vez,
o
Android
chama
onCreateDialog(int) de sua atividade, que é onde você deve criar uma instância do Dialog. Neste método de retorno é passado o mesmo ID que você passou para showDialog(int). Depois de criar o diálogo, retorne o objeto no final do método. Antes que o diálogo ser exibido, o Android também chama o método callback opcional onPrepareDialog(int, Dialog). Defina nesse método se você deseja alterar as propriedades da caixa de diálogo cada vez que for aberta. Este método é chamado toda vez que uma caixa de diálogo é aberta, enquanto onCreateDialog(int) é chamado apenas na primeira vez que uma caixa de diálogo é aberta. Se você não definir onPrepareDialog(), então o diálogo continuará a ser o mesmo que era o tempo anterior que foi aberto. Este método também passa o ID do diálogo, além de um diálogo do objeto que você criou na onCreateDialog(). A melhor maneira de definir os métodos de retorno onCreateDialog(int) e onPrepareDialog(int, Dialog) é com uma instrução switch que verifica o parâmetro id que é passado para o método. Cada caso deve verificar se há uma identificação única de diálogo e, em seguida, criar e definir o respectivo diálogo. Por exemplo, imagine um jogo que usa dois diálogos distintos: um para indicar que o jogo tem uma pausa e outra para indicar que o jogo acabou. Primeiro, defina um ID de número inteiro para cada caixa de diálogo: static final int DIALOG_PAUSED_ID = 0; static final int DIALOG_GAMEOVER_ID = 1;
ANDROID, uma visão geral – Anderson Duarte de Amorim
157
Em seguida, defina a chamada onCreateDialog(int) com um caso de interruptor para cada ID: protected Dialog onCreateDialog(int id) { Dialog dialog; switch(id) { case DIALOG_PAUSED_ID: // do the work to define the pause Dialog break; case DIALOG_GAMEOVER_ID: // do the work to define the game over Dialog break; default: dialog = null; } return dialog; }
Nota: Neste exemplo, não há nenhum código dentro da declaração de caso, porque o procedimento para a definição de seu diálogo está fora do escopo desta seção. Quando é hora de mostrar um dos diálogos, chame showDialog(int) com o ID de um diálogo: showDialog(DIALOG_PAUSED_ID);
Dispensar um diálogo Quando estiver pronto para fechar o diálogo, você pode descartá-lo chamando dismiss() no objeto Dialog. Se necessário, você também pode chamar dismissDialog(int) da atividade, o que efetivamente chama dismiss() na caixa de diálogo para você. Se você estiver usando onCreateDialog(int) para gerir o seu estado de diálogos (como discutido na seção anterior), então cada vez que o diálogo é indeferido, o estado do objeto de diálogo é mantido pela atividade. Se você decidir que você não precisa mais desse objeto ou é importante que o estado esteja limpo, então você deve chamar removeDialog(int). Isto irá remover todas as referências internas ao objeto e se o diálogo está mostrando, vai dispensá-lo.
Usando demissão de receptores Se você quiser que seu aplicativo execute alguns procedimentos no momento em que um diálogo é dispensado, então você deve anexar um receptor „on-dismiss‟ no seu diálogo. ANDROID, uma visão geral – Anderson Duarte de Amorim
158
Primeiro defina a interface DialogInterface.OnDismissListener. Essa interface possui apenas um método, onDismiss(DialogInterface), que será chamado quando o diálogo for descartado. Depois, passe a sua implementação OnDismissListener para setOnDismissListener(). No entanto, note que os diálogos também podem estar "cancelados". Este é um caso especial que indica que o diálogo foi explicitamente cancelado por parte do usuário. Isso ocorrerá se o usuário pressiona o botão "BACK " para fechar a janela, ou se a caixa de diálogo solicita explicitamente cancel() (talvez a partir de um botão "Cancelar" na caixa de diálogo). Quando um diálogo for cancelado, o OnDismissListener ainda será notificado, mas se você gostaria de ser informado de que o diálogo foi expressamente cancelado (e não dispensado normalmente), então você deve registrar um DialogInterface.OnCancelListener com setOnCancelListener().
Criando um AlertDialog Um AlertDialog é uma extensão da classe Dialog. É capaz de construir a maioria das interfaces de usuário de diálogo e é o tipo de diálogo sugerido. Você deve usá-lo para o diálogo que usam qualquer uma das seguintes características: Um título Uma mensagem de texto Um, dois ou três botões Uma lista de itens selecionáveis (com checkbox ou radio-button) Para criar um AlertDialog, use a subclasse AlertDialog.Builder. Receba um construtor com AlertDialog.Builder(Context) e depois use os métodos públicos de classe para definir todas as propriedades AlertDialog. Depois que está finalizado com o construtor, recupere o objeto AlertDialog com create(). Os tópicos a seguir mostram como definir várias propriedades do AlertDialog usando a classe AlertDialog.Builder. Se você usar qualquer um dos seguintes códigos de exemplo dentro do seu método de retorno onCreateDialog(), você pode retornar o objeto resultante de diálogo para exibir o diálogo.
ANDROID, uma visão geral – Anderson Duarte de Amorim
159
Adicionando botões
Para criar um AlertDialog com botões lado a lado, como a mostrada na imagem à direita, use o método set...Button(): AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("Are you sure you want to exit?") .setCancelable(false) .setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { MyActivity.this.finish(); } }) .setNegativeButton("No", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); AlertDialog alert = builder.create();
Primeiro, adicione uma mensagem para o diálogo com setMessage(CharSequence) . Então, comece método de encadeamento e definir a janela para não ser cancelado (por isso o usuário não poderá fechar o diálogo com o botão traseiro) com setCancelable(boolean) . Para cada botão, use um dos set...Button() métodos, como setPositiveButton() , que aceita o nome do botão e um DialogInterface.OnClickListener que define as medidas a tomar quando o usuário seleciona o botão. Nota: Você só pode adicionar um botão de cada tipo à AlertDialog. Ou seja, você não pode ter mais de um botão "positivo". Isso limita o número de botões possíveis para três: positivo, neutro e negativo. Estes nomes são tecnicamente irrelevantes para a funcionalidade real de seus botões, mas deve ajudá-lo a acompanhar o que faz o quê.
ANDROID, uma visão geral – Anderson Duarte de Amorim
160
Adicionando uma lista
Para criar um AlertDialog com uma lista de itens selecionáveis como o mostrado à esquerda, use o método setItems(): final CharSequence[] items = {"Red", "Green", "Blue"}; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Pick a color"); builder.setItems(items, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { Toast.makeText(getApplicationContext(), items[item], Toast.LENGTH_SHORT).show(); } }); AlertDialog alert = builder.create();
Primeiro, adicione um título para o diálogo com setTitle(CharSequence). Em seguida, adicione uma lista de itens selecionáveis com setItems(), que aceita um conjunto de itens a serem exibidos e um DialogInterface.OnClickListener que define as medidas a tomar quando o usuário seleciona um item.
Adicionando caixas de seleção e botões de rádio Para criar uma lista de itens de escolha múltipla (caixas) ou itens de escolha única (botões de rádio) dentro
do
diálogo,
setMultiChoiceItems()
e
use
os
métodos
setSingleChoiceItems(),
respectivamente. Se você criar uma destas listas selecionáveis no método de retorno onCreateDialog(), o Android gerencia o estado da lista para você. Contanto que a atividade esteja ativa, o diálogo se lembra dos itens que foram previamente selecionados, mas quando o usuário sai da atividade, a seleção está perdida.
ANDROID, uma visão geral – Anderson Duarte de Amorim
161
Nota: Para salvar a seleção quando o usuário deixa ou faz pausa na atividade, você deve salvar e restaurar corretamente a configuração de todo o ciclo de vida de atividade. Para salvar permanentemente as seleções, mesmo quando o processo de atividade é completamente parado, você precisa salvar as configurações com uma das técnicas de Armazenamento de Dados. Para criar um AlertDialog com uma lista de itens de escolha simples, como a mostrada acima, use o mesmo código do exemplo anterior, mas substitua o método setItems() pelo setSingleChoiceItems(): final CharSequence[] items = {"Red", "Green", "Blue"}; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Pick a color"); builder.setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { Toast.makeText(getApplicationContext(), items[item], Toast.LENGTH_SHORT).show(); } }); AlertDialog alert = builder.create();
O segundo parâmetro no método setSingleChoiceItems() é um valor inteiro para o checkedItem, que indica a posição de lista com base zero do item selecionado padrão. Use "-1" para indicar que nenhum item deve ser selecionado por padrão.
Criar um ProgressDialog A
ProgressDialog
é
uma
extensão
da
classe
AlertDialog que pode exibir uma animação de progresso na forma de uma roda, para uma tarefa com o progresso indefinido, ou uma barra de progresso, para uma tarefa que tem uma progressão definida. O diálogo também pode fornecer botões, como um de cancelar um download. Abrir
uma
janela
de
progresso
pode
ser
tão
simples
como
chamar
ProgressDialog.show(). Por exemplo, o diálogo mostrado acima pode ser facilmente alcançado sem gerenciar o diálogo através da chamada onCreateDialog(int), conforme mostrado abaixo: ProgressDialog dialog = ProgressDialog.show(MyActivity.this, "", "Loading. Please wait...", true);
ANDROID, uma visão geral – Anderson Duarte de Amorim
162
O primeiro parâmetro é a aplicação Context, o segundo é um título para o diálogo (vazio), o terceiro é a mensagem e o último parâmetro é se o progresso é indeterminado (isso só é relevante quando cria uma barra de progresso, que é discutido na próxima seção). O estilo padrão de um diálogo de progresso é a roda. Se você deseja criar uma barra de progresso que mostra o progresso do carregamento com granularidade, mais código é necessário, como será discutido na próxima seção.
Mostrando uma barra de progresso
Para mostrar a progressão com uma barra de progresso animada: 1. Inicialize
o
ProgressDialog
com
o
construtor
da
classe,
ProgressDialog(Context). 2. Defina
o
estilo
de
progressos
para
"STYLE_HORIZONTAL"
com
setProgressStyle(int) e defina as outras propriedades, como a mensagem. 3. Quando estiver pronto para mostrar o diálogo, chame show() ou devolva o ProgressDialog do onCreateDialog(int) de retorno. 4. Você pode incrementar a quantidade de progresso exibida na barra chamando tanto setProgress(int) com um valor para a porcentagem total concluída ou incrementProgressBy(int) com um valor incremental para adicionar à porcentagem total concluída até agora. Por exemplo, sua configuração pode se parecer como esta: ProgressDialog progressDialog; progressDialog = new ProgressDialog(mContext); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setMessage("Loading..."); progressDialog.setCancelable(false);
ANDROID, uma visão geral – Anderson Duarte de Amorim
163
A configuração é simples. A maior parte do código necessário para criar um diálogo de progresso está realmente envolvida no processo que atualizá-lo. Você pode achar que é necessário criar um segundo thread em sua aplicação para este trabalho e, em seguida, relatar o progresso de volta à atividade do thread de interface do usuário com um Handler do objeto. Se você não está familiarizado com o uso de threads adicionais com um manipulador, vejo o exemplo abaixo, que utiliza um segundo thread para incrementar um diálogo de progresso gerido pela atividade. Exemplo ProgressDialog com um segundo thread Este exemplo usa um segundo thread para acompanhar o andamento de um processo (que na verdade só conta até 100). O thread envia uma Message de volta à atividade principal através de um Handler a cada hora em que algum progresso é feito. A atividade principal, em seguida, atualiza o ProgressDialog. package com.example.progressdialog; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class NotificationTest extends Activity { static final int PROGRESS_DIALOG = 0; Button button; ProgressThread progressThread; ProgressDialog progressDialog; /** Called when the activity is first created. */ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Setup the button that starts the progress dialog button = (Button) findViewById(R.id.progressDialog); button.setOnClickListener(new OnClickListener(){ public void onClick(View v) { showDialog(PROGRESS_DIALOG); } }); } protected Dialog onCreateDialog(int id) {
ANDROID, uma visão geral – Anderson Duarte de Amorim
164
switch(id) { case PROGRESS_DIALOG: progressDialog = new ProgressDialog(NotificationTest.this); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setMessage("Loading..."); return progressDialog; default: return null; } } @Override protected void onPrepareDialog(int id, Dialog dialog) { switch(id) { case PROGRESS_DIALOG: progressDialog.setProgress(0); progressThread = new ProgressThread(handler); progressThread.start(); } // Define the Handler that receives messages from the thread and update the progress final Handler handler = new Handler() { public void handleMessage(Message msg) { int total = msg.arg1; progressDialog.setProgress(total); if (total >= 100){ dismissDialog(PROGRESS_DIALOG); progressThread.setState(ProgressThread.STATE_DONE); } } }; /** Nested class that performs progress calculations (counting) */ private class ProgressThread extends Thread { Handler mHandler; final static int STATE_DONE = 0; final static int STATE_RUNNING = 1; int mState; int total; ProgressThread(Handler h) { mHandler = h; } public void run() { mState = STATE_RUNNING; total = 0; while (mState == STATE_RUNNING) { try { Thread.sleep(100); } catch (InterruptedException e) { Log.e("ERROR", "Thread Interrupted"); } Message msg = mHandler.obtainMessage(); msg.arg1 = total;
ANDROID, uma visão geral – Anderson Duarte de Amorim
165
mHandler.sendMessage(msg); total++; } } /* sets the current state for the thread, * used to stop the thread */ public void setState(int state) { mState = state; } } }
Criando uma caixa de diálogo personalizada Se você quiser um projeto personalizado para um diálogo, você pode criar seu próprio layout para a janela de diálogo com elementos de layout e de widget. Depois de definido o layout, passar o objeto View raiz ou identificação do recurso de layout para setContentView(View). Por exemplo, para criar o diálogo mostrado acima: 1. Criar um layout XML salvo como custom_dialog.xml: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout_root" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dp" > <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_marginRight="10dp" /> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="fill_parent" android:textColor="#FFF" /> </LinearLayout>
Esta XML define um ImageView e um TextView dentro de um LinearLayout. 2. Definir o layout acima como o conteúdo da View da caixa de diálogo e definir o conteúdo dos elementos ImageView e TextView:
ANDROID, uma visão geral – Anderson Duarte de Amorim
166
Context mContext = getApplicationContext(); Dialog dialog = new Dialog(mContext); dialog.setContentView(R.layout.custom_dialog); dialog.setTitle("Custom Dialog"); TextView text = (TextView) dialog.findViewById(R.id.text); text.setText("Hello, this is a custom dialog!"); ImageView image = (ImageView) dialog.findViewById(R.id.image); image.setImageResource(R.drawable.android);
Depois de instanciar o diálogo, definir o layout personalizado de conteúdo como conteúdo da View da caixa de diálogo com setContentView(int), passando o ID do recurso de layout. Agora que o diálogo tem um layout definido, você pode capturar objetos View do layout com findViewById(int) e modificar seu conteúdo. 3. É isso aí. Agora você pode mostrar o diálogo como descrito em Mostrando um Diálogo. Um diálogo feito com a classe de diálogo base deve ter um título. Se você não chamar setTitle(), o espaço usado para o título continua vazio, mas ainda visível. Se você não quer um título a todos, então você deve criar o seu diálogo personalizado usando a classe AlertDialog. No entanto, porque um AlertDialog é mais fácilmente criado com o AlertDialog.Builder, você não tem acesso ao método setContentView(int) utilizado acima. Em vez disso, você deve usar setView(View). Este método aceita um objeto View, por isso é necessário inflar o layout do objeto View da raiz do XML. Para inflar o layout XML, recuperar o LayoutInflater com getLayoutInflater() (ou getSystemService()), e depois chamar inflate(int, ViewGroup), onde o primeiro parâmetro é o ID do recurso layout e o segundo é a identificação da View raiz. Neste ponto, você pode usar o layout inflado para encontrar objetos View no layout e definir o conteúdo
dos
elementos
ImageView
e
TextView.
Então
instanciar
AlertDialog.Builder e definir o layout inflados para o diálogo com setView(View). Aqui está um exemplo, criando um layout personalizado em um AlertDialog: AlertDialog.Builder builder; AlertDialog alertDialog; Context mContext = getApplicationContext(); LayoutInflater inflater = (LayoutInflater)
ANDROID, uma visão geral – Anderson Duarte de Amorim
167
o
mContext.getSystemService(LAYOUT_INFLATER_SERVICE); View layout = inflater.inflate(R.layout.custom_dialog, (ViewGroup) findViewById(R.id.layout_root)); TextView text = (TextView) layout.findViewById(R.id.text); text.setText("Hello, this is a custom dialog!"); ImageView image = (ImageView) layout.findViewById(R.id.image); image.setImageResource(R.drawable.android); builder = new AlertDialog.Builder(mContext); builder.setView(layout); alertDialog = builder.create();
Usando um AlertDialog para o seu layout personalizado permite-lhe tirar partido das funcionalidades incorporadas AlertDialog como botões geridos, listas selecionáveis, um título, um ícone e assim por diante.
ANDROID, uma visão geral – Anderson Duarte de Amorim
168
Manipulando eventos de UI No Android, há mais de um caminho para interceptar os eventos de interação do usuário com seu aplicativo. Ao considerar os eventos dentro de sua interface de usuário, a abordagem consiste em capturar os eventos do objeto View específico com que o usuário interage. A classe View fornece os meios para fazê-lo. Entre as diversas classes View que você usará para compor seu layout, você pode observar vários métodos de retorno públicos que pareçam úteis para eventos de UI. Esses métodos são chamados pelo framework Android, quando a respectiva ação ocorre no objeto. Por exemplo, quando uma exibição (como um botão) é tocada, o método onTouchEvent() é chamado no objeto. No entanto, a fim de interceptar isso, você deve estender a classe e substituir o método. No entanto, estender cada objeto View, a fim de lidar com um evento como esse não seria prático. É por isso que a classe View também contém uma coleção de interfaces aninhadas com callbacks que podem ser muito mais fácil de definir. Essas interfaces, chamadas de event listeners, são o seu bilhete para capturar a interação do usuário com sua interface do usuário. Enquanto você vai utilizar mais comumente os ouvintes de evento para receber a interação do usuário, pode chegar um momento em que você quer estender uma classe, no intuito de construir um componente personalizado. Talvez você queira estender a classe Button para fazer algo mais extravagante. Neste caso, você será capaz de definir o comportamento de eventos padrão para sua classe usando a classe de manipuladores de eventos.
Os ouvintes de eventos Um receptor de evento é uma interface na classe View que contém um método de retorno único. Esses métodos serão chamados pelo framework Android quando o View para o receptor tenha sido registado é desencadeada pela interação do usuário com o item na interface do usuário.
ANDROID, uma visão geral – Anderson Duarte de Amorim
169
Incluído nas interfaces de ouvinte de evento são os métodos de retorno seguintes: onClick() A partir do View.OnClickListener. É chamado quando o usuário toca o item (quando em modo de tocar), ou incide sobre o item com a navegação por teclas ou trackball e pressiona a tecla "enter" ou pressiona o trackball. onLongClick() De View.OnLongClickListener. Isto é chamado quando o usuário toca e prende o item (quando no modo de tocar), ou incide sobre o item com a navegação por teclas ou trackball e pressiona e mantém a tecla "enter" ou pressiona e mantém pressionada a trackball (por um segundo). onFocusChange() De View.OnFocusChangeListener. Isto é chamado quando o usuário navega para ou longe do ponto, utilizando atalhos ou trackball. onKey() De View.OnKeyListener. Isto é chamado quando o usuário está centrado sobre o item e pressiona ou solta uma tecla no dispositivo. onTouch() De View.OnTouchListener. Isto é chamado quando o usuário executa uma ação qualificada como um evento de toque, incluindo pressionar, soltar, ou qualquer movimento na tela (dentro dos limites do item). onCreateContextMenu() De View.OnCreateContextMenuListener. Isto é chamado quando um menu de contexto está sendo construído (como o resultado de um "clique longo" sustentado). Esses métodos são os únicos habitantes da suas respectivas interfaces. Para definir um desses métodos e lidar com seus eventos, implemente a interface aninhada em sua
ANDROID, uma visão geral – Anderson Duarte de Amorim
170
atividade ou defina-a como uma classe anônima. Em seguida, passe uma instância da sua aplicação com os respectivos métodos View.set...Listener(). (Por exemplo, chamar setOnClickListener() e passá-la a implementação do OnClickListener). O exemplo abaixo mostra como registrar um receptor no clique de um botão. // Create an anonymous implementation of OnClickListener private OnClickListener mCorkyListener = new OnClickListener() { public void onClick(View v) { // do something when the button is clicked } }; protected void onCreate(Bundle savedValues) { ... // Capture our button from layout Button button = (Button)findViewById(R.id.corky); // Register the onClick listener with the implementation above button.setOnClickListener(mCorkyListener); ... }
Você também pode achar mais conveniente para implementar OnClickListener como parte de sua atividade. Isso irá evitar a carga horária extra e alocação de objetos. Por exemplo: public class ExampleActivity extends Activity implements OnClickListener { protected void onCreate(Bundle savedValues) { ... Button button = (Button)findViewById(R.id.corky); button.setOnClickListener(this); } // Implement the OnClickListener callback public void onClick(View v) { // do something when the button is clicked } ... }
Observe que a chamada de onClick()no exemplo acima não tem valor de retorno, mas alguns métodos ouvintes devem retornar um boolean. A razão depende do evento. Para os poucos que o fazem, aqui está o porquê: onLongClick() - Retorna um booleano para indicar se você tem consumido o evento e que não deve ser levado adiante. Ou seja, retornar true para indicar que você tem tratado o evento e deve parar por aqui; retornar false se você não tem
ANDROID, uma visão geral – Anderson Duarte de Amorim
171
lidado com isso e/ou o evento deve continuar para qualquer outro receptor onclick. onKey() - Retorna um booleano para indicar se você tem consumido o evento e que não deve ser levada adiante. Ou seja, retornar true para indicar que você tem tratado o evento e deve parar por aqui; retornar false se você não tem lidado com isso e/ou o evento deve continuar a todo ouvinte on-key. onTouch() - Retorna um booleano para indicar se o ouvinte consome este evento. O importante é que este evento pode ter várias ações que se sucedem. Então, se você retornar falso quando o evento de ação abaixo é recebido, você indica que não consumiram o evento e também não estão interessados em ações subseqüentes deste evento. Assim, você não será chamado para outras ações dentro do evento, como um gesto do dedo, ou um eventual evento de ação acima. Lembre-se que os principais eventos são sempre entregues para a corrente View em foco. Eles são enviados a partir do topo da hierarquia de View e, em seguida, para baixo, até chegar ao destino apropriado. Se a sua view (ou filho de sua view) atualmente tem o foco, então você pode ver o curso de eventos através do método dispatchKeyEvent(). Como alternativa à captura de eventos-chave através da sua view, você também pode receber todos os eventos dentro de sua atividade com onKeyDown() e onKeyUp(). Nota: O Android vai chamar os manipuladores de eventos e depois os manipuladores padrão apropriados a partir da definição de segunda classe. Como tal, retornando true destes ouvintes de evento irá parar a propagação do evento para ouvintes de eventos e também irá bloquear o retorno de chamada para o manipulador de eventos padrão no View. Então, tenha a certeza de que deseja encerrar o caso quando você retornar true.
Manipuladores de eventos Se você está construindo um componente personalizado de view, então você vai ser capaz de definir vários métodos de retorno usados como manipuladores de eventos padrão. No documento Construindo Componentes Personalizados, você aprenderá a ver alguns dos retornos comuns usados para tratamento de eventos, incluindo:
ANDROID, uma visão geral – Anderson Duarte de Amorim
172
onKeyDown(int, KeyEvent) - Chamado quando um evento de nova tecla ocorre. onKeyUp(int, KeyEvent) - Chamado quando um evento de tecla para cima ocorre. onTrackballEvent(MotionEvent) - Chamado quando um evento de movimento de trackball ocorre. onTouchEvent(MotionEvent) - Chamado quando um evento do movimento da tela de toque ocorre. onFocusChanged(boolean, int, Rect) - Chamado quando a view ganha ou perde o foco. Existem alguns outros métodos que você deve estar ciente de que não são parte da classe View, mas podem impactar diretamente a forma como você é capaz de manipular eventos. Portanto, ao gerenciar eventos mais complexos dentro de um layout, considere estes outros métodos: Activity.dispatchTouchEvent(MotionEvent) - Isso permite que sua atividade possa interceptar todos os eventos de toque antes de serem enviados para a janela. ViewGroup.onInterceptTouchEvent(MotionEvent) - Isso permite que um ViewGroup possa assistir a eventos como eles são distribuídos aos views filhos. ViewParent.requestDisallowInterceptTouchEvent(boolean) - Chamar esta em cima de um pai view para indicar que ela não deve interceptar eventos de contato com onInterceptTouchEvent(MotionEvent) .
Modo de toque Quando um usuário está navegando uma interface de usuário com as teclas direcionais ou um trackball, é necessário dar atenção aos itens de recurso (como botões) que o usuário possa ver o que vai aceitar a entrada. Se o dispositivo tem capacidades de toque, no entanto, o usuário começa a interagir com a interface ao tocá-lo, então ele não é mais
ANDROID, uma visão geral – Anderson Duarte de Amorim
173
necessário para destacar itens, ou dar enfoque a uma visão particular. Assim, existe um modo de interação com o nome "modo de toque." Para um dispositivo sensível ao toque, uma vez que o usuário toca a tela, o aparelho entra em modo de tocar. Deste ponto em diante, somente as views em que o isFocusableInTouchMode() está true poderão ser focadas, como os widgets de edição de texto. Outros views que são palpáveis, como botões, não vão tirar o foco quando tocado; eles vão simplesmente focar seus ouvintes com um clique, quando pressionados. Toda vez que um usuário pressiona uma tecla direcional ou rola com uma trackball, o aparelho sairá do modo de tocar, e encontrar um visual de tirar o foco. Agora, o usuário pode continuar interagindo com a interface do usuário sem tocar na tela. O estado modo de tocar é mantido ao longo de todo o sistema (todas as janelas e atividades). Para consultar o estado atual, você pode chamar isInTouchMode() para ver se o dispositivo está no modo de tocar.
Manipulação do foco O framework vai lidar com as rotinas de movimento do foco em resposta à entrada do usuário. Isso inclui a mudança do foco nas views que são removidas ou escondidas, ou quando as views novos se tornam disponíveis. Views indicam a sua disponibilidade para tirar o foco através do método isFocusable(). Para definir se uma View pode tirar o foco, chame setFocusable(). Quando em modo de tocar, você pode consultar se uma View permite focar com isFocusableInTouchMode(). Você pode mudar isso com setFocusableInTouchMode(). Movimento do foco é baseado em um algoritmo que encontra o vizinho mais próximo em uma determinada direção. Em casos raros, o algoritmo padrão pode não coincidir com o comportamento desejado para o desenvolvedor. Nessas situações, você pode fornecer substituições explícitas com os atributos XML a seguir no arquivo de layout: nextFocusDown, nextFocusLeft, nextFocusRight, e nextFocusUp. Adicione um desses atributos para o View a partir do qual o foco está saindo. Defina o valor do atributo a ser o ID do view para quem o foco deve ser dado. Por exemplo:
ANDROID, uma visão geral – Anderson Duarte de Amorim
174
<LinearLayout android:orientation="vertical" ... > <Button android:id="@+id/top" android:nextFocusUp="@+id/bottom" ... /> <Button android:id="@+id/bottom" android:nextFocusDown="@+id/top" ... /> </LinearLayout>
Normalmente, neste layout vertical, navegando a partir do primeiro botão não leva a lugar nenhum, nem iria navegar abaixo do segundo botão. Agora que o botão superior foi definido como um fundo com o nextFocusUp (e vice-versa), o foco de navegação irá circular de cima para baixo e de baixo para cima. Se você gostaria de declarar o view como passível de foco em sua interface do usuário (quando não é tradicional), adicione o atributo XML android:focusable para a view, na sua declaração de layout. Defina o valor true. Você pode também declarar uma view como
passível
de
foco
enquanto
em
modo
de
toque
com
android:focusableInTouchMode. Para solicitar uma exibição especial para ter foco, chame requestFocus(). Para ouvir os eventos de foco (ser notificado quando um view recebe ou perde foco), use onFocusChange().
ANDROID, uma visão geral – Anderson Duarte de Amorim
175
Notificar o usuário Vários tipos de situações podem surgir que requerem a notificação do usuário sobre um evento que ocorre em sua aplicação. Alguns eventos requerem que o usuário responda e outros não. Por exemplo: Quando um evento como salvar um arquivo for concluída, uma pequena mensagem deve aparecer para confirmar que o salvamento foi bem sucedido. Se o aplicativo é executado em segundo plano e requer atenção do usuário, o aplicativo deve criar uma notificação que permite ao usuário responder a conveniência dele ou dela. Se o aplicativo está executando o trabalho que o usuário deve aguardar (como carregar um arquivo), o aplicativo deve mostrar uma roda de progresso pairando ou uma barra. Cada uma dessas tarefas de notificação podem ser conseguidas usando uma técnica diferente: Uma notificação brinde, por breves mensagens que vêm do fundo. Uma notificação na barra de status, lembretes persistentes que vêm do fundo e solicitam resposta do usuário. Uma notificação de diálogo, para as notificações de atividades relacionadas.
Notificação brinde Uma notificação brinde é uma mensagem que aparece na superfície da janela. Ela só enche o espaço necessário para a mensagem e a atividade atual do usuário permanece visível e interativa. A notificação automaticamente desaparece, e não aceita eventos de interação. Como um brinde pode ser criado a partir de um serviço em background, aparece mesmo que o aplicativo não esteja visível.
ANDROID, uma visão geral – Anderson Duarte de Amorim
176
Um brinde é melhor para mensagens de texto curtas, como "Arquivo salvo", quando você está bastante certo de que o usuário está prestando a atenção na tela. Um brinde não pode aceitar eventos de interação do usuário, se você gostaria que o usuário respondesse e agisse, considere usar uma notificação na barra de status.
Criando notificações brinde A imagem abaixo mostra uma notificação brinde de exemplo da aplicação do alarme. Uma vez que um alarme é ativado, um brinde é exibido para garantir que o alarme foi ajustado. Um brinde pode ser criado e exibido a partir de uma Activity ou Service. Se você criar uma notificação de brinde de um serviço, ele aparece na frente da atividade atualmente em foco. Se a resposta do usuário com a notificação é exigida, considere usar uma notificação na barra de status. O Básico Primeiro, instanciar um objeto Toast com um dos métodos makeText(). Este método usa três parâmetros: a aplicação Context, a mensagem de texto e a duração para o brinde. Ele retorna um objeto Toast inicializado corretamente. Você pode exibir a notificação brinde com show(), como mostrado no exemplo a seguir: Context context = getApplicationContext(); CharSequence text = "Hello toast!"; int duration = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(context, text, duration); toast.show();
Este exemplo demonstra tudo o que precisa para a maioria das notificações brinde. Raramente é necessário algo a mais. Você pode, no entanto, querer a posição do brinde diferente ou até mesmo usar o seu próprio layout, em vez de uma simples mensagem de texto. As seções a seguir descrevem como você pode fazer essas coisas.
ANDROID, uma visão geral – Anderson Duarte de Amorim
177
Posicionar o seu brinde Uma notificação brinde padrão aparece perto da parte inferior da tela, centralizado horizontalmente. Você pode alterar esta posição com o método setGravity(int, int, int). Este aceita três parâmetros: a constante Gravity, um deslocamento da posição x e um deslocamento da posição y. Por exemplo, se você decidir que o brinde deve aparecer no canto superior esquerdo, você pode definir a gravidade como este: toast.setGravity(Gravity.TOP|Gravity.LEFT, 0, 0);
Se você quiser deslocar a posição para a direita, aumente o valor do segundo parâmetro. Para empurrá-lo para baixo, aumente o valor do último parâmetro. Criando uma exibição personalizada brinde Se uma mensagem de texto simples não é suficiente, você pode criar um layout personalizado para a sua notificação brinde. Para criar um layout personalizado, definir um layout de view, em XML ou no código do aplicativo, e passar a View raiz para o método setView(View). Por exemplo, você pode criar o layout para o brinde visível na imagem à esquerda, com o seguinte XML (salvo como toast_layout.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/toast_layout_root" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dp" android:background="#DAAA" > <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_marginRight="10dp" /> <TextView android:id="@+id/text"
ANDROID, uma visão geral – Anderson Duarte de Amorim
178
android:layout_width="wrap_content" android:layout_height="fill_parent" android:textColor="#FFF" /> </LinearLayout>
Observe que a identificação do elemento LinearLayout é "toast_layout". Você deve usar essa identificação para inflar o layout do XML, como mostrado aqui: LayoutInflater inflater = getLayoutInflater(); View layout = inflater.inflate(R.layout.toast_layout, (ViewGroup) findViewById(R.id.toast_layout_root)); ImageView image = (ImageView) layout.findViewById(R.id.image); image.setImageResource(R.drawable.android); TextView text = (TextView) layout.findViewById(R.id.text); text.setText("Hello! This is a custom toast!"); Toast toast = new Toast(getApplicationContext()); toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0); toast.setDuration(Toast.LENGTH_LONG); toast.setView(layout); toast.show();
Primeiro, recupere o LayoutInflater com getLayoutInflater() (ou getSystemService()), e depois infle o layout de XML usando inflate(int, ViewGroup). O primeiro parâmetro é o ID do recurso layout e o segundo é a View raiz. Você pode usar esse layout inflado para encontrar mais objetos view no layout, agora capturar e definir o conteúdo dos elementos ImageView e TextView. Finalmente, crie um novo brinde com Toast(Context) e defina algumas propriedades do brinde, tais como a gravidade e duração. Em seguida, chame setView(View) e passe seu layout inflado. Agora você pode exibir o brinde com o seu layout personalizado, chamando show(). Nota: Não use o construtor público para um brinde, a menos que vá definir o layout com setView(View). Se você não tem um layout personalizado para o uso, você deve usar makeText(Context, int, int) para criar o Toast.
Notificação na barra de status Uma notificação na barra de status adiciona um ícone para barra de status do sistema (com opcionais relógio/mensagem de
texto)
e
uma
mensagem
expandida
na
janela
"Notificações". Quando o usuário seleciona a mensagem
ANDROID, uma visão geral – Anderson Duarte de Amorim
179
expandida, o Android aciona uma Intent que é definida pela notificação (geralmente para lançar uma Activity). Você também pode configurar a notificação para alertar o usuário com um som, uma vibração e as luzes piscando no dispositivo. Este tipo de notificação é ideal quando o aplicativo está funcionando em um serviço de fundo e há necessidade de notificar o usuário sobre um evento. Se você precisa alertar o usuário sobre um evento que ocorre durante a sua atividade que ainda está em foco, considere usar uma notificação de diálogo em vez disso.
Criação de notificações da barra de status Uma notificação na barra de status deve ser usada para qualquer caso em que um serviço de background precisa alertar o usuário sobre um evento que exige uma resposta. Um serviço de fundo nunca deve iniciar uma atividade por conta própria a fim de receber a interação do usuário. O serviço deveria criar uma notificação na barra de status que vai lançar a atividade quando selecionado pelo usuário. A imagem abaixo mostra a barra de status com um ícone de notificação no lado esquerdo.
A imagem seguinte mostra mensagem expandida de notificação na janela "Notificações". O usuário pode visualizar a janela de notificações, puxando para baixo a barra de status (ou selecionando Notificações no menu de opções da Home).
O Básico Uma Activity ou Service pode iniciar uma notificação na barra de status. Porque uma atividade pode executar ações somente quando ela está ativa e em foco, você deve criar suas notificações de um serviço. Desta forma, a notificação pode ser criada a partir do ANDROID, uma visão geral – Anderson Duarte de Amorim
180
fundo, enquanto o usuário está usando outra aplicação ou quando o dispositivo estiver dormindo. Para criar uma notificação, você deve usar duas classes: Notification e NotificationManager. Use uma instância da classe Notification para definir as propriedades de sua notificação na barra de status, como o ícone da barra de status, a mensagem expandida e configurações extras, como um som para tocar. O NotificationManager é um serviço do sistema Android que executa e gerencia todas as notificações. Você não pode instanciar o NotificationManager. A fim de dar-lhe a sua notificação, você deve recuperar uma referência para o NotificationManager com getSystemService() e então, quando você quer notificar o usuário, passá-lo com seu objeto de notificação notify(). Para criar uma notificação de barra de status: 1. Obtenha uma referência para o NotificationManager: String ns NotificationManager getSystemService(ns);
= Context.NOTIFICATION_SERVICE; mNotificationManager = (NotificationManager)
2. Instanciar a notificação: int icon = R.drawable.notification_icon; CharSequence tickerText = "Hello"; long when = System.currentTimeMillis(); Notification notification = new Notification(icon, tickerText, when);
3. Definir mensagem expandida e intenção da notificação: Context context = getApplicationContext(); CharSequence contentTitle = "My notification"; CharSequence contentText = "Hello World!"; Intent notificationIntent = new Intent(this, MyClass.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
4. Passe a notificação ao NotificationManager: private static final int HELLO_ID = 1; mNotificationManager.notify(HELLO_ID, notification);
É isso aí. Seu usuário já foi notificado.
ANDROID, uma visão geral – Anderson Duarte de Amorim
181
Gerenciando suas notificações O NotificationManager é um serviço do sistema que gerencia todas as notificações. Você deve obter uma referência a ele com o método getSystemService(). Por exemplo: String ns = Context.NOTIFICATION_SERVICE; NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
Quando você quiser enviar sua notificação na barra de status, passar o objeto de notificação ao NotificationManager com notify(int, Notification). O primeiro parâmetro é o ID único para a notificação e o segundo é o objeto de notificação. O ID identifica a notificação a partir da sua aplicação. Isso é necessário se você precisa atualizar a notificação (se o aplicativo gerencia diferentes tipos de notificações) ou selecionar a ação apropriada quando o usuário retornar para a sua aplicação através da intenção definida na notificação. Para apagar a notificação de barra de status quando o usuário seleciona a partir da janela de notificações, adicione a flag "FLAG_AUTO_CANCEL" de seu objeto de notificação. Você também pode limpar manualmente com cancel(int), passando-lhe a identificação ou notificação ou limpar todas as suas notificações com cancelAll(). Criando uma notificação Um objeto Notification define os detalhes da mensagem de notificação que é exibida na barra de status e janela de "Notificações", e qualquer alerta de outras configurações, tais como sons e luzes piscando. Uma notificação de barra de status exige o seguinte: Um ícone da barra de status Uma mensagem expandida e título para o modo expandido (a menos que você defina uma exibição personalizada ampliada) Um PendingIntent, para ser acionado quando a notificação for selecionada As configurações opcionais para a notificação de barra de status incluem: Uma mensagem de texto-relógio para a barra de status Um som de alerta ANDROID, uma visão geral – Anderson Duarte de Amorim
182
Uma configuração vibrar Uma definição LED piscando O kit de arranque para uma nova notificação inclui o construtor Notification(int, CharSequence, long)
e o
método
setLatestEventInfo(Context,
CharSequence,
CharSequence, PendingIntent). Estes definem todas as definições necessárias para uma notificação. O trecho a seguir demonstra a configuração de notificação de base: int icon = R.drawable.notification_icon; // icon from resources CharSequence tickerText = "Hello"; // ticker-text long when = System.currentTimeMillis(); // notification time Context context = getApplicationContext(); // application Context CharSequence contentTitle = "My notification"; // expanded message title CharSequence contentText = "Hello World!"; // expanded message text Intent notificationIntent = new Intent(this, MyClass.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); // the next two lines initialize the Notification, using the configurations above Notification notification = new Notification(icon, tickerText, when); notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
Atualizando a notificação Você pode atualizar as informações em sua notificação de barra de status como eventos que continuam a ocorrer em seu aplicativo. Por exemplo, quando uma nova mensagem de texto SMS chega antes que as mensagens anteriores foram lidas, o aplicativo de mensagens atualiza as notificações existentes para exibir o número total de novas mensagens recebidas. Esta prática, de uma atualização de notificação existente é muito melhor do que a adição de novas notificações à NotificationManager porque evita a desordem na janela de notificações. Porque cada notificação é unicamente identificada pelo NotificationManager com um número de identificação inteiro, você pode revisar a notificação chamando setLatestEventInfo() com novos valores, mudar alguns valores de campo da notificação, e depois chamar notify() novamente. Você pode rever cada propriedade com os campos de membro de objeto (exceto para o contexto e no título da mensagem expandida e texto). Você deve sempre revisar a mensagem de texto quando você atualizar a notificação chamando setLatestEventInfo() com novos valores para contentTitle e contentText. Em seguida, chamar notify() para
ANDROID, uma visão geral – Anderson Duarte de Amorim
183
atualizar a notificação. (Claro, se você criou uma exibição personalizada expandida, atualizar esses valores de texto e título não terá efeito) Adicionando um som Você pode alertar o usuário com o som de notificação padrão (que é definido pelo usuário) ou com um som especificado pelo seu aplicativo. Para utilizar o usuário padrão do som, adicione "DEFAULT_SOUND" para o campo de padrões: notification.defaults |= Notification.DEFAULT_SOUND;
Para usar um som diferente com as suas notificações, passe uma referência URI para o campo de som. O exemplo a seguir usa um arquivo de áudio conhecido gravado no cartão SD do dispositivo: notification.sound = Uri.parse("file:///sdcard/notification/ringer.mp3");
No próximo exemplo, o arquivo de áudio é escolhido do MediaStore 's ContentProvider: notification.sound = Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "6");
Neste caso, a identificação exata do arquivo de mídia ("6") é conhecido e anexado ao conteúdo Uri. Se você não souber a identificação exata, você deve consultar todos os meios disponíveis no MediaStore com ContentResolver. Se você deseja que o som repeta continuamente até que o usuário responda à notificação ou a notificação seja cancelada, adicione "FLAG_INSISTENT" para o campo de sinalizadores. Nota: Se o campo padrão inclui "DEFAULT_SOUND", então o som padrão substitui qualquer som definido pelo campo de som. Adicionando vibração Você pode alertar o usuário com o modelo padrão de vibração ou com um modelo de vibração definido pelo seu aplicativo. Para usar o modelo padrão, adicione "DEFAULT_VIBRATE" para o campo de padrões:
ANDROID, uma visão geral – Anderson Duarte de Amorim
184
notification.defaults |= Notification.DEFAULT_VIBRATE;
Para definir o seu padrão de vibração própria, passe uma matriz de valores longos para o campo vibrar: long[] vibrate notification.vibrate = vibrate;
=
{0,100,200,300};
O longo array define o padrão alternado para o comprimento de vibração e desligando (em milissegundos). O primeiro valor é o tempo de espera (desligado) antes de começar, o segundo valor é o comprimento da primeira vibração, o terceiro é o comprimento da próxima, e assim por diante. O padrão pode ser tão longo quanto queira, mas não pode ser configurado para repetir. Nota: Se o campo padrão inclui "DEFAULT_VIBRATE", então a vibração padrão substitui qualquer vibração definida pelo campo vibrate. Adicionando luzes a piscar Para alertar o usuário com luzes LED, você pode implementar o modelo de luz padrão (se disponível), ou definir sua própria cor e padrão para as luzes. Para usar a configuração padrão de luz, acrescentar "DEFAULT_LIGHTS" para o campo de padrões: notification.defaults |= Notification.DEFAULT_LIGHTS;
Para definir sua própria cor e padrão, definir um valor para o campo ledARGB (para a cor), o campo ledOffMS (período de tempo, em milésimos de segundo, para manter a luz apagada), o ledOnMS (período de tempo, em milissegundos, para manter a luz acesa), e também adicionar "FLAG_SHOW_LIGHTS" para o campo flags: notification.ledARGB = notification.ledOnMS = notification.ledOffMS = notification.flags |= Notification.FLAG_SHOW_LIGHTS;
0xff00ff00; 300; 1000;
Neste exemplo, a luz verde pisca várias vezes a cada 300 milésimos de segundo e desliga por um segundo. Nem todas as cores no espectro são suportadas pelo dispositivo de LEDs, e não é todo dispositivo que suporta as mesmas cores, então o hardware estima para o melhor de sua capacidade. Verde é a cor mais comum de notificação.
ANDROID, uma visão geral – Anderson Duarte de Amorim
185
Mais recursos Você pode adicionar várias características a mais às suas notificações usando campos de notificação e flags. Algumas características úteis incluem o seguinte: "FLAG_AUTO_CANCEL" Adicione isto ao campo de sinalizadores para cancelar automaticamente a notificação depois que ela é selecionada a partir da janela de notificações. "FLAG_INSISTENT" Adicione isto ao campo de sinalizadores para repetir o áudio até que o usuário responda. "FLAG_ONGOING_EVENT" Adicione isto ao campo de sinalizadores para agrupar notificações ao abrigo do título "em curso" da janela de notificações. Isso indica que o aplicativo está em curso - seus processos ainda estão em execução em segundo plano, mesmo quando o aplicativo não é visível (como a música ou uma chamada de telefone). "FLAG_NO_CLEAR" Adicione isto ao campo de sinalizadores para indicar que a notificação não deve ser limpa pelo botão "Limpar notificações". Isso é particularmente útil se a sua notificação está em curso. número Esse valor indica o número atual de eventos representados pela notificação. O número apropriado é sobreposto em cima do ícone da barra de status. Se você pretende usar esse campo, então você deve começar com "1" quando a notificação for criado pela primeira vez. (Se você alterar o valor de zero para qualquer coisa maior durante uma atualização, o número não é mostrado.)
ANDROID, uma visão geral – Anderson Duarte de Amorim
186
iconLevel Esse valor indica o nível atual de um LevelListDrawable que é usado para o ícone de notificação. Você pode animar o ícone na barra de status, alterando esse valor para correlacionar com os drawable é definido em um LevelListDrawable. Criar uma exibição personalizada expandida Por padrão, o modo expandido utilizado na janela "Notificações" inclui um título de base e mensagem de texto. Estes são definidos por parâmetros contentText e contentTitle do método setLatestEventInfo(). No entanto, você também pode definir um layout personalizado para o modo de exibição expandido usando RemoteViews. A imagem à direita mostra um exemplo de uma exibição personalizada alargada, que usa um ImageView e TextView em LinearLayout. Para definir seu próprio layout para a mensagem expandida, instancie um objeto RemoteViews e passe-o para o campo contentView da sua notificação. Passe o PendingIntent ao campo contentIntent. Criar uma exibição personalizada expandido é mais bem entendido com um exemplo: 1. Criar o layout XML para a exibição expandida. Por exemplo, crie um arquivo chamado layout custom_notification_layout.xml e construa-o assim: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="3dp" > <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_marginRight="10dp" /> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="fill_parent" android:textColor="#000" /> </LinearLayout>
ANDROID, uma visão geral – Anderson Duarte de Amorim
187
Este esquema é utilizado para a visualização expandida, mas o conteúdo do ImageView e TextView ainda precisam ser definidos pelo aplicativo. RemoteViews oferece alguns métodos adequados que lhe permitem definir o conteúdo... 2. No código do aplicativo, use os métodos RemoveViews para definir a imagem e texto. Em seguida, passe o objeto RemoteViews ao campo contentView da notificação, conforme mostrado neste exemplo: RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.custom_notification_layout); contentView.setImageViewResource(R.id.image, R.drawable.notification_image); contentView.setTextViewText(R.id.text, "Hello, this message is in a custom expanded view"); notification.contentView = contentView;
Como mostrado aqui, passe o nome do aplicativo do pacote e a identificação de recursos de layout para o construtor RemoteViews. Em seguida, defina o conteúdo para o ImageView e TextView, usando o setImageViewResource() e setTextViewText(). Em cada caso, passe o ID de referência do objeto View conveniente que você deseja definir, juntamente com o valor para essa visão. Finalmente, o objeto RemoteViews é passado para a notificação no âmbito contentView. 3. Porque você não precisa do método setLatestEventInfo() quando usando uma exibição personalizada, você deve definir as intenções para a notificação com o campo contentIntent, como neste exemplo: Intent notificationIntent = new Intent(this, MyClass.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.contentIntent = contentIntent;
4. A notificação pode ser enviada, como de costume: mNotificationManager.notify(CUSTOM_VIEW_ID, notification);
A classe RemoteViews também inclui métodos que você pode usar para facilmente adicionar um Chronometer ou ProgressBar na view expandida da notificação. Para obter mais informações sobre como criar layouts personalizados com RemoteViews, consulte o RemoteViews.
ANDROID, uma visão geral – Anderson Duarte de Amorim
188
Nota: Ao criar uma view expandida customizada, você deve tomar cuidado especial para garantir que as funções personalizadas de seu layout haja corretamente no dispositivo em orientações e resoluções diferentes. Enquanto este conselho se aplica a todos os layouts view criados no Android, é especialmente importante neste caso porque o seu layout de propriedade real é muito restrito. Portanto, não faça o seu layout personalizado demasiado complexo e não se esqueça de testá-lo em várias configurações.
Notificação de diálogo Um diálogo é geralmente uma pequena janela que aparece na frente da atividade atual. A atividade perde o foco e o diálogo aceita a interação do usuário. Os diálogos são normalmente utilizados para notificações e atividades curtas que se relacionem diretamente com a aplicação em curso. Você deve usar uma caixa de diálogo quando você precisa mostrar uma barra de progresso ou uma mensagem curta que requer a confirmação do usuário (como um alerta com "OK" e "Cancelar"). Você pode usar também as janelas como parte integrante na interface do aplicativo e para outros fins além de notificações. Para uma discussão completa sobre todos os tipos disponíveis de diálogos, incluindo seus usos para as notificações, consulte Criando caixas de diálogo.
ANDROID, uma visão geral – Anderson Duarte de Amorim
189
Aplicando estilos e temas Um estilo é um conjunto de propriedades que especificam o aspecto e formato de um View ou janela. Um estilo pode especificar propriedades como altura, padding, cor de fonte, tamanho de fonte, cor de fundo e muito mais. Um estilo é definido em um recurso de XML que é separado do XML que especifica o layout. Estilos em Android compartilham uma filosofia semelhante à de estilo em cascata em web-design que permitem a você separar o design do conteúdo. Por exemplo, usando um estilo, você pode ter este layout XML: <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:textColor="#00FF00" android:typeface="monospace" android:text="@string/hello" />
E transformar nisso: <TextView style="@style/CodeFont" android:text="@string/hello" />
Todos os atributos relacionados com o estilo foram removidos do layout XML e colocado em uma definição de estilo chamado CodeFont, e depois aplicado com o atributo style. Você verá a definição para esse estilo na seção seguinte. Um tema é um estilo aplicado a toda uma Activity ou aplicação, ao invés de um indivíduo View (como no exemplo acima). Quando um estilo é aplicado como um tema, a cada view da atividade ou da aplicação serão aplicados cada propriedade de estilo que ele suporta. Por exemplo, você pode aplicar o mesmo estilo CodeFont como um tema para uma atividade e, em seguida, todo o texto dentro dessa atividade deverá ter fonte monoespaçada verde.
Definição de estilos Para criar um conjunto de estilos, salve um arquivo XML no diretório res/values/ do seu projeto. O nome do arquivo XML é arbitrário, mas deve usar a extensão .xml e ser salvo na pasta res/values/. O nó raiz do arquivo XML deve ser <resources>. Para cada estilo que você quer criar, adicione um elemento <style> para o arquivo com um nome que identifica o estilo (este atributo é obrigatório). Em seguida, adicione um elemento <item> para cada propriedade desse estilo, com um nome que declara a ANDROID, uma visão geral – Anderson Duarte de Amorim
190
propriedade de estilo e um valor para ir com ele (este atributo é obrigatório). O valor para o <item> pode ser uma seqüência de palavras-chave, uma cor hexadecimal, uma referência a outro tipo de recurso, ou outro valor, dependendo da propriedade de estilo. Aqui está um exemplo de arquivo com um estilo único: <?xml version="1.0" encoding="utf-8"?> <resources> <style name="CodeFont" parent="@android:style/TextAppearance.Medium"> <item name="android:layout_width">fill_parent</item> <item name="android:layout_height">wrap_content</item> <item name="android:textColor">#00FF00</item> <item name="android:typeface">monospace</item> </style> </resources>
Cada filho do elemento <resources> é convertido em um objeto de recurso do aplicativo em tempo de compilação, que pode ser referenciado pelo valor do atributo name do elemento <style>. Este estilo de exemplo pode ser referenciado a partir de um layout XML como @style/CodeFont (como demonstrado na introdução acima). O atributo parent no elemento <style> é opcional e especifica o ID do recurso de um outro estilo a partir do qual este estilo deve herdar propriedades. Você pode, então, substituir as propriedades de estilo herdado se você quiser. Lembre-se, um estilo que você deseja usar como uma atividade ou tema de aplicação é definido em XML exatamente do mesmo jeito que um estilo para uma view. Um estilo, como o definido acima pode ser aplicado como um estilo para uma única view ou como um tema para uma atividade ou aplicação inteira. Como aplicar um estilo para uma visão única ou como um tema de aplicação é discutido mais tarde.
Herança O atributo parent no elemento <style> permite que você especifique um estilo a partir do qual o seu estilo deve herdar propriedades. Você pode usar isso para herdar propriedades de um estilo existente e, em seguida, definir apenas as propriedades que deseja alterar ou acrescentar. Você pode herdar de estilos que você criou para si mesmo ou de diferentes estilos que estão construídos na plataforma (Veja Usando estilos e temas da plataforma abaixo, para obter informações sobre herança de estilos definidos pela plataforma Android). Por exemplo, você pode herdar a aparência do texto padrão da plataforma Android e, em seguida, modificá-lo:
ANDROID, uma visão geral – Anderson Duarte de Amorim
191
<style name="GreenText" parent="@android:style/TextAppearance"> <item name="android:textColor">#00FF00</item> </style>
Se você quer herdar os estilos que você definiu para si mesmo, você não tem que usar o atributo parent. Em vez disso, apenas preceda o nome do estilo que você quer herdar ao nome do seu novo estilo, separados por um período. Por exemplo, para criar um novo estilo que herda o estilo CodeFont definido anteriormente, mas fazer a cor vermelha, você pode criar o novo estilo como este: <style name="CodeFont.Red"> <item name="android:textColor">#FF0000</item> </style>
Observe que não há atributo parent na tag <style>, mas porque o atributo name começa com a CodeFont nome do estilo (que é um estilo que você criou), este estilo herda todas as propriedades de estilo a partir desse estilo. Este estilo, em seguida, substitui a propriedade android:textColor para tornar o texto vermelho. Você pode fazer referência a este novo estilo como @style/CodeFont.Red. Você pode continuar herdando assim tantas vezes quanto quiser, encadeando os nomes com os períodos. Por exemplo, você pode estender CodeFont.Red ser maior, com: <style name="CodeFont.Red.Big"> <item name="android:textSize">30sp</item> </style>
Este herda de ambos os estilos CodeFont e CodeFont.Red, em seguida, adiciona a propriedade android:textSize. Nota: Essa técnica de herança por encadeando de nomes só funciona para estilos definidos por seus próprios recursos. Você não pode herdar estilos internos do Android desta forma. Para fazer referência a um estilo incorporado, como TextAppearance, você deve usar o atributo parent.
Propriedades do estilo Agora que você entende como um estilo é definido, é preciso saber que tipo de propriedades de estilo definidas pelo <item> estão disponíveis. Você provavelmente está familiarizado com alguns já, como layout_width e textColor. Claro, há muito mais propriedades de estilo que você pode usar.
ANDROID, uma visão geral – Anderson Duarte de Amorim
192
O melhor lugar para encontrar propriedades que se aplicam a um determinado View é a referência de classe correspondente, que lista todos os atributos XML que são suportados. Por exemplo, todos os atributos listados na tabela de atributos XML TextView podem ser usados em uma definição de estilo para um elemento TextView (ou uma de suas subclasses). Um dos atributos listados na referência é android:inputType, então onde você normalmente poderia colocar o atributo android:inputType em um elemento <EditText>, assim: <EditText android:inputType="number" ... />
Você pode em vez disso criar um estilo para o elemento EditText que inclua esta propriedade: <style name="Numbers"> <item name="android:inputType">number</item> ... </style>
Portanto, o seu XML para o esquema pode agora aplicar este estilo: <EditText style="@style/Numbers" ... />
Este exemplo simples pode parecer dar mais trabalho, mas quando você adiciona mais propriedades de estilo e fatores na possibilidade de voltar a usar o estilo em vários lugares, o custo-beneficio pode ser enorme. Para uma referência de todas as propriedades de estilo disponíveis, consulte a referência R.attr. Tenha em mente que todos os objetos view não aceitam todos os atributos de mesmo estilo, então você deve, normalmente, referenciar ao específico View para as propriedades de estilo suportadas. Entretanto, se você aplicar um estilo a uma exibição que não suporta todas as propriedades de estilo, o view irá aplicar apenas as propriedades que são suportadas e simplesmente ignorar os outros. Algumas propriedades de estilo, no entanto, não são suportadas por qualquer elemento View e só pode ser aplicado como um tema. Estas propriedades de estilo se aplicam a toda a janela e não a qualquer tipo de view. Por exemplo, propriedades de estilo para um tema podem ocultar o título do aplicativo, ocultar a barra de status, ou mudar o fundo da
ANDROID, uma visão geral – Anderson Duarte de Amorim
193
janela. Estes tipos de propriedades de estilo não pertencem a nenhum objeto View. Para descobrir essas propriedades de estilo theme-only, veja o R.attr de referência para os atributos
que
começam
com
window.
Por
exemplo,
windowNoTitle
e
windowBackground são propriedades de estilo que só são eficazes quando o estilo é aplicado como um tema para uma atividade ou aplicação. Consulte a próxima seção para informações sobre como aplicar um estilo como um tema. Nota: Não se esqueça de prefixo dos nomes das propriedades em cada elemento <item> com o android: namespace. Por exemplo: <item name="android:inputType">.
Aplicando estilos e temas para a interface do usuário Há duas maneiras de definir um estilo: Para uma view individual, adicione o atributo style a um elemento view no XML para seu layout. Ou, para uma atividade inteira ou uma aplicação, adicione o atributo android:theme ao elemento <activity> ou <application> no manifesto do Android. Quando você aplica um estilo a uma única View no layout, as propriedades definidas pelo estilo são aplicadas somente ao View. Se um estilo é aplicado a um ViewGroup, a criança do elemento View não herdará as propriedades de estilo - apenas o elemento ao qual se aplicam diretamente o estilo vai aplicar suas propriedades. No entanto, você pode aplicar um estilo para que se aplique a todos os elementos View, aplicando o estilo como um tema. Para aplicar uma definição de estilo como um tema, você deve aplicar o estilo para uma Activity ou aplicação no manifesto do Android. Quando você fizer isso, todos os View dentro da atividade ou da aplicação serão aplicáveis a cada propriedade que ele suporta. Por exemplo, se você aplicar o estilo CodeFont dos exemplos anteriores a uma atividade, então todos os elementos View que suportam as propriedades de estilo de texto irá aplicá-los. Qualquer visão que não suporta as propriedades vai ignorá-los. Se o view suporta apenas algumas das propriedades, então é só aplicar essas propriedades.
Aplicar um estilo a uma view Veja como definir um estilo para uma exibição no esquema XML: ANDROID, uma visão geral – Anderson Duarte de Amorim
194
<TextView style="@style/CodeFont" android:text="@string/hello" />
Agora este TextView será denominado como definido pelo estilo chamado CodeFont. (Veja o exemplo acima, em Definição de estilos). Nota: O atributo style não usa o android: namespace prefix.
Aplicar um tema a uma atividade ou aplicação Para definir um tema para todas as atividades de sua aplicação, abra o arquivo AndroidManifest.xml e edite o tag <application> para incluir o atributo android:theme com o nome do estilo. Por exemplo: <application android:theme="@style/CustomTheme">
Se você quer um tema aplicado a apenas uma atividade em seu aplicativo, então, adicione o atributo android:theme ao tag <activity> um de cada vez. Assim como o Android oferece outros recursos internos, há muitos temas pré-definidos que podem ser usados, para evitar escrevê-los sozinho. Por exemplo, você pode usar o tema Dialog e fazer a sua actividade parecer como uma caixa de diálogo: <activity android:theme="@android:style/Theme.Dialog">
Ou se você quiser que o fundo seja transparente, usar o tema translúcido: <activity android:theme="@android:style/Theme.Translucent">
Se você gosta de um tema, mas quer ajustá-lo, basta adicionar o tema como o parent do seu tema personalizado. Por exemplo, você pode modificar o tradicional light theme para usar a sua própria cor, como esta: <color name="custom_theme_color">#b0b0ff</color> <style name="CustomTheme" parent="android:Theme.Light"> <item name="android:windowBackground">@color/custom_theme_color</item> <item name="android:colorBackground">@color/custom_theme_color</item> </style>
(Note que a cor precisa ser fornecida como um recurso separado aqui, porque o atributo android:windowBackground suporta apenas uma referência a outro recurso, ao contrário do android:colorBackground, a ele pode não ser dada uma cor literal.) Agora use CustomTheme em vez de Theme.Light dentro do Manifesto Android: ANDROID, uma visão geral – Anderson Duarte de Amorim
195
<activity android:theme="@style/CustomTheme">
Selecione um tema baseado na versão de plataforma Novas versões do Android têm temas adicionais disponíveis para os aplicativos, e você pode querer usar estes durante a execução nessas plataformas, enquanto continuam sendo compatíveis com versões anteriores. Você pode fazer isso através de um tema personalizado que usa a seleção de recursos para alternar entre os temas pai diferentes, baseado na versão da plataforma. Por exemplo, aqui está a declaração de um tema personalizado que é simplesmente o modelo padrão das plataformas do tema light. Ele vai em um arquivo XML por res/values (tipicamente res/values/styles.xml ): <style name="LightThemeSelector" parent="android:Theme.Light"> ... </style>
Para este tema usar o novo tema holográfico quando o aplicativo está rodando o Android 3.0 (API Nível 11) ou superior, você pode colocar uma declaração alternativa para o tema em um arquivo XML em res/values-v11, mas fazer do tema mãe o tema holográfico: <style name="LightThemeSelector" parent="android:Theme.Holo.Light"> ... </style>
Agora, usar este tema como se fosse qualquer outro, e seu aplicativo passará automaticamente para o tema holográfico se em execução no Android 3.0 ou superior. Uma lista de atributos padrão que você pode usar em temas podem ser encontrados em R.styleable.Theme. Para obter mais informações sobre o fornecimento de recursos alternativos, como os temas e layouts, com base na versão de plataforma ou configurações de outro dispositivo, consulte o documento Fornecimento de Recursos.
Usando estilos e temas da plataforma A plataforma Android oferece uma grande coleção de estilos e temas que você pode usar em seus aplicativos. Você pode encontrar uma referência de todos os estilos disponíveis na classe R.style. Para usar os estilos listados aqui, substituir todos os ANDROID, uma visão geral – Anderson Duarte de Amorim
196
sublinhados no nome do estilo, com um período. Por exemplo, você pode aplicar o Theme_NoTitleBar tema com "@android:style/Theme.NoTitleBar". O R.style, entretanto, não é bem documentado e não descreve minuciosamente os estilos, assim, ver o código fonte para estes estilos e temas lhe dará uma compreensão melhor do que as propriedades de estilo de cada um oferece. Para uma melhor referência para estilos e temas do Android, consulte os seguintes códigos fonte: Android Styles (styles.xml) Android Temas (themes.xml) Esses arquivos vão te ajudar a aprender através do exemplo. Por exemplo, no código fonte
de
temas
Android,
você
encontrará
uma
declaração
de
<style
name="Theme.Dialog">. Nesta definição, você verá todas as propriedades que são usadas para estilo de diálogos que são usadas pelo framework Android. Para uma referência de atributos de estilo disponíveis que você pode usar para definir um estilo ou tema (por exemplo, "windowBackground" ou "textAppearance"), ver R.attr ou a classe View para o qual você está criando um estilo.
ANDROID, uma visão geral – Anderson Duarte de Amorim
197
Recursos de aplicação Você deve sempre externar recursos como imagens e seqüências de seu código de aplicação, para que você possa mantê-las de forma independente. Externalizar seus recursos também permite que você forneça recursos alternativos que oferecem suporte a configurações de dispositivos específicos, como línguas ou tamanhos de tela diferentes, o que se torna cada vez mais importante quanto mais dispositivos com Android tornamse disponíveis com configurações diferentes. A fim de proporcionar compatibilidade com diferentes configurações, você deve organizar os recursos em seu diretório do projeto res/, usando vários sub-diretórios que agrupam os recursos por tipo e configuração.
Figura 1. Dois dispositivos diferentes, ambos os usando recursos padrão.
Figura 2. Dois dispositivos diferentes, uma usando recursos alternativos.
Para qualquer tipo de recurso, você pode especificar padrão e vários recursos alternativos para a sua aplicação: Os recursos padrão são aqueles que devem ser utilizados independentemente da configuração do aparelho ou quando não existem recursos alternativos que correspondam à configuração atual. Recursos alternativos são aqueles que você já projetou para uso com uma configuração específica. Para especificar que um grupo de recursos são de uma configuração específica, acrescente um qualificador de configuração adequada ao nome do diretório. ANDROID, uma visão geral – Anderson Duarte de Amorim
198
Por exemplo, enquanto o seu layout padrão de interface do usuário é salvo no diretório res/layout/, você pode especificar um layout de interface diferente para ser usado quando a tela está na orientação paisagem, salvando-o no diretório res/layout-land/. Android aplica automaticamente os recursos apropriados por correspondência à configuração atual do dispositivo para os nomes do diretório de recursos. A figura 1 demonstra como um conjunto de recursos padrão de um aplicativo são aplicados a dois dispositivos diferentes, quando não há recursos alternativos disponíveis. A Figura 2 mostra o mesmo aplicativo com um conjunto de recursos alternativos que se qualificam para uma das configurações do dispositivo, assim, os dois dispositivos usam recursos diferentes. A informação acima é apenas uma introdução sobre como trabalhar os recursos do aplicativo no Android. Os documentos a seguir fornecem um guia completo de como você pode organizar seus recursos de aplicação, especificar os recursos alternativos, acessá-los em seu aplicativo, e mais: Fornecendo recursos Que tipos de recursos você pode oferecer em seu aplicativo, onde guardá-los, e como criar recursos alternativos para configurações de dispositivo específico. Acessando recursos Como utilizar os recursos que você forneceu, seja por referenciá-los a partir do seu código de aplicativo ou de outros recursos XML. Tratando alterações em runtime Como gerenciar as alterações de configuração que ocorrem quando sua atividade está em execução. Localização Um guia de baixo para cima para localizar seu aplicativo usando recursos alternativos. Enquanto este é apenas um uso específico de recursos alternativos, é muito importante para alcançar mais usuários.
ANDROID, uma visão geral – Anderson Duarte de Amorim
199
Tipos de recursos Uma referência de vários tipos de recursos que você pode fornecer, descrevendo seus elementos XML, atributos e sintaxe. Por exemplo, esta referência mostra como criar um recurso para os menus do aplicativo, os desenhos, animações e muito mais.
ANDROID, uma visão geral – Anderson Duarte de Amorim
200
Armazenamento de dados Android oferece várias opções para você guardar dados da aplicação persistente. A solução que você escolher depende de suas necessidades específicas, tais como se os dados devem ser privados da sua aplicação ou acessíveis para outras aplicações (e do usuário) e quanto espaço seus dados requerem. Suas opções de armazenamento de dados são os seguintes: Preferências compartilhadas Armazenar dados privados primitivos em pares chave-valor. Armazenamento interno Armazenar dados privados sobre a memória do dispositivo. Armazenamento externo Armazenar dados públicos sobre o armazenamento compartilhado externo. Bancos de dados SQLite Armazenar dados estruturados em um banco privado. Conexão de rede Armazenar dados na web com seu servidor de rede própria. Android fornece uma maneira para que você exponha seus dados pessoais, mesmo para outras aplicações - com um provedor de conteúdo. Um provedor de conteúdo é um componente opcional que expõe acesso de leitura/gravação à sua aplicação de dados, sujeito a qualquer restrição que você pretende impor. Para obter mais informações sobre como usar provedores de conteúdo, consulte a documentação de provedores de conteúdo.
Utilizando preferências compartilhadas A classe SharedPreferences fornece um framework geral que permite salvar e recuperar pares chave-valor persistente de tipos de dados primitivos. Você pode usar SharedPreferences para salvar os dados primitivos: booleans, floats, inteiros, longos, e
ANDROID, uma visão geral – Anderson Duarte de Amorim
201
strings. Estes dados vão persistir nas sessões de usuário (mesmo se sua aplicação é morta). Para obter um objeto SharedPreferences para sua aplicação, use um dos dois
Preferências do usuário Preferências
métodos:
compartilhadas
não
são
estritamente para o salvar "as preferências do
getSharedPreferences() - Use esta
usuário", como o toque de um usuário
opção se você precisa de arquivos
escolheu. Se você está interessado em criar
de
preferências
preferências do usuário para seu aplicativo,
identificados pelo nome, que você
consulte PreferenceActivity , que estabelece
especifica
um quadro de atividades para você criar as
múltiplas
com
o
parâmetro
primeiro.
preferências
getPreferences() - Use esta opção
automaticamente
se você só precisa de um arquivo de
do
usuário, persistido
o
qual
será
(usando
as
preferências compartilhadas).
preferências para a sua actividade. Porque este será o único arquivo de preferências para sua atividade, você não fornece um nome. Para escrever os valores: 1. Chame edit() para obter uma SharedPreferences.Editor. 2. Adicione valores com métodos como putBoolean() e putString(). 3. Empregue os novos valores com commit(). Para ler os valores, use métodos SharedPreferences como getBoolean() e getString(). Aqui está um exemplo que salva uma preferência para o modo silencioso keypress em uma calculadora: public class Calc extends Activity { public static final String PREFS_NAME = "MyPrefsFile"; @Override protected void onCreate(Bundle state){ super.onCreate(state); ... // Restore preferences SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0); boolean silent = settings.getBoolean("silentMode", false); setSilent(silent); }
ANDROID, uma visão geral – Anderson Duarte de Amorim
202
@Override protected void onStop(){ super.onStop(); // We need an Editor object to make preference changes. // All objects are from android.context.Context SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0); SharedPreferences.Editor editor = settings.edit(); editor.putBoolean("silentMode", mSilentMode); // Commit the edits! editor.commit(); } }
Usando o armazenamento interno Você pode salvar arquivos diretamente na memória interna do dispositivo. Por padrão, os arquivos salvos no armazenamento interno são privados para sua aplicação e outras aplicações não podem acessá-los (nem mesmo o usuário). Quando o usuário desinstala o aplicativo, esses arquivos são removidos. Para criar e gravar um arquivo privado para o armazenamento interno: 1. Chame openFileOutput() com o nome do arquivo e o modo de funcionamento. Isso retorna um FileOutputStream. 2. Escreva no arquivo com o write(). 3. Feche o fluxo com close(). Por exemplo: String FILENAME = "hello_file"; String string = "hello world!"; FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE); fos.write(string.getBytes()); fos.close();
MODE_PRIVATE irá criar o arquivo (ou substituir um arquivo de mesmo nome) e torná-lo
privado
para
sua
aplicação.
Outras
modalidades
disponíveis
são:
MODE_APPEND, MODE_WORLD_READABLE e MODE_WORLD_WRITEABLE.
ANDROID, uma visão geral – Anderson Duarte de Amorim
203
Para ler um arquivo de armazenamento interno: 1. Chame openFileInput() e passe o nome do arquivo a ser lido. Isso retorna um FileInputStream. 2. Leia os bytes do arquivo com a read(). 3. Em seguida, feche o fluxo com close(). Dica: Se você quiser salvar um arquivo estático em seu aplicativo em tempo de compilação, salve o arquivo no diretório de res/raw/ do seu projeto. Você pode abri-lo com openRawResource(), passando a identificação de recurso R.raw.<filename>. Esse método retorna um InputStream que você pode usar para ler o arquivo (mas você não pode escrever no arquivo original).
Salvando os arquivos de cache Se você gostaria de gravar alguns dados em cache, ao invés de armazená-lo persistentemente, você deve usar getCacheDir() para abrir um arquivo que representa o diretório interno onde a sua aplicação deve salvar os arquivos de cache temporário. Quando o dispositivo está com pouco espaço de armazenamento interno, o Android pode excluir esses arquivos de cache para recuperar espaço. No entanto, você não deve confiar no sistema para limpar esses arquivos para você. Você deve sempre manter os arquivos de cache e manter dentro de um limite razoável de espaço consumido, como 1MB. Quando o usuário desinstala o aplicativo, esses arquivos são removidos. Outros métodos úteis: getFilesDir() Obtém o caminho absoluto para o diretório de arquivos onde os arquivos internos são salvos. getDir() Cria (ou abre um existente) diretório dentro de seu espaço de armazenamento interno. deleteFile() Exclui um arquivo salvo na memória interna.
ANDROID, uma visão geral – Anderson Duarte de Amorim
204
fileList() Retorna uma matriz de arquivos atualmente salvos pela sua aplicação.
Usando o armazenamento externo Cada dispositivo compatível com o Android suporta um "armazenamento externo" compartilhado que você pode usar para salvar arquivos. Pode ser uma mídia de armazenamento removível (como um cartão SD) ou uma memória interna (não removível). Os arquivos salvos no armazenamento externos são de leitura e podem ser modificados pelo usuário quando permitem armazenamento em massa USB para transferir arquivos de um computador. Atenção: os arquivos externos podem desaparecer se o usuário monta o armazenamento externo em um computador ou remove a mídia, e não há nenhuma segurança aplicada sobre os arquivos que você salva para o armazenamento externo. Todas as aplicações podem ler e gravar arquivos colocados no armazenamento externo e o usuário pode removê-los.
Verificar a disponibilidade dos meios Antes de fazer qualquer trabalho com o armazenamento externo, você deve sempre chamar getExternalStorageState() para verificar se os meios de comunicação estão disponíveis. A mídia pode ser montada a um computador, faltando, somente leitura, ou em algum outro estado. Por exemplo, aqui está como você pode verificar a disponibilidade: boolean mExternalStorageAvailable = false; boolean mExternalStorageWriteable = false; String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { // We can read and write the media mExternalStorageAvailable = mExternalStorageWriteable = true; } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { // We can only read the media mExternalStorageAvailable = true; mExternalStorageWriteable = false; } else { // Something else is wrong. It may be one of many other states, but all we need // to know is we can neither read nor write mExternalStorageAvailable = mExternalStorageWriteable = false; }
ANDROID, uma visão geral – Anderson Duarte de Amorim
205
Este exemplo verifica se o armazenamento externo está disponível para ler e escrever. O método getExternalStorageState() retorna outros estados que você pode querer verificar, como se a mídia está sendo compartilhada (conectada a um computador), está totalmente ausente, foi mal removida, etc. Você pode usá-los para notificar o usuário com mais informações quando o aplicativo precisa de acesso à mídia.
Acessando arquivos em armazenamento externo Se você estiver usando a API de nível 8 ou superior, use getExternalFilesDir() para abrir um arquivo que representa o diretório de armazenamento externo onde você deve salvar seus arquivos. Este método utiliza um parâmetro type que especifica o tipo de subdiretório
que
você
deseja,
como
DIRECTORY_MUSIC
e
DIRECTORY_RINGTONES (passe null para receber a raiz do diretório do arquivo da aplicativo). Este método irá criar o diretório apropriado, se necessário. Ao especificar o tipo de diretório, você garante que a mídia scanner do Android irá categorizar corretamente seus arquivos no sistema (por exemplo, os ringtones são identificados como toques, e não música). Se o usuário desinstala o aplicativo, este diretório e todo seu conteúdo serão apagados. Se você estiver usando a API de nível 7 ou inferior, use getExternalStorageDirectory() para abrir um File que representa a raiz de armazenamento externo. Você deve então escrever os seus dados no seguinte diretório: /Android/data/<package_name>/files/ O <package_name> é o seu nome de estilo do pacote Java, tal como "com.example.android.app". Se o dispositivo do usuário está executando API nível 8 ou superior e desinstalar o aplicativo, este diretório e todo seu conteúdo serão apagados.
Como salvar arquivos que Escondendo seus arquivos a partir da devem ser compartilhados Media Scanner Se você deseja salvar os arquivos que
Inclua um arquivo vazio chamado .nomedia em seu
não são específicos para a sua
diretório de arquivos externos (note o ponto prefixo
aplicação e que não devem ser
ao nome do arquivo). Isto irá prevenir o media
excluídos quando o aplicativo é
scanner do Android de ler arquivos de mídia e
desinstalado, salva-os em um dos
incluí-los em aplicativos como Galley ou Music.
ANDROID, uma visão geral – Anderson Duarte de Amorim
206
diretórios públicos de armazenamento externo. Esses diretórios estão na origem do armazenamento externo, como Music/, Pictures/, Ringtones/ e outros. Na API nível 8 ou superior, use getExternalStoragePublicDirectory(),passando para ele o tipo de diretório público que deseja, tais como DIRECTORY_MUSIC, DIRECTORY_PICTURES, DIRECTORY_RINGTONES ou outros. Este método irá criar o diretório apropriado, se necessário. Se você estiver usando a API de nível 7 ou inferior, use getExternalStorageDirectory() para abrir um File que representa a raiz do armazenamento externo, em seguida, salve seus arquivos compartilhados em um dos seguintes diretórios: Music/ - o scanner media classifica todas as mídias encontradas aqui como a música do usuário. Podcasts/ - scanner media classifica todas as mídias encontradas aqui como um podcast. Ringtones/ - scanner media classifica todas as mídias encontradas aqui como um ringtone. Alarms/ - scanner media classifica todas as mídias encontradas aqui como um som de alarme. Notifications/ - scanner media classifica todas as mídias encontradas aqui como um som de notificação. Pictures/ - todas as fotos (excluindo as tiradas com a câmera). Movies/ - todos os filmes (excluindo aqueles filmados com a câmera de vídeo). Download/ - downloads diversos.
Salvando os arquivos de cache Se você estiver usando a API de nível ou superior, use 8 getExternalCacheDir() para abrir um File que representa o diretório de armazenamento externo, onde você deve salvar os arquivos de cache. Se o usuário desinstala o aplicativo, esses arquivos serão automaticamente excluídos. No entanto, durante a vida do seu aplicativo, você deve gerenciar esses arquivos de cache e eliminar os que não são necessários, a fim de preservar o espaço do arquivo.
ANDROID, uma visão geral – Anderson Duarte de Amorim
207
Se você estiver usando a API de nível 7 ou inferior, use getExternalStorageDirectory() para abrir um File que representa a raiz do armazenamento externo, em seguida, escreva o seu cache de dados no seguinte diretório: /Android/data/<package_name>/cache/ O
<package_name>
é
o
seu
estilo
nome
do
pacote
Java,
tal
qual
"com.example.android.app".
Utilizando bancos de dados Android oferece suporte completo para bancos de dados SQLite. Qualquer banco de dados que você criar serão acessíveis pelo nome de qualquer classe na aplicação, mas não fora da aplicação. O método recomendado para criar um novo banco de dados SQLite é criar uma subclasse de SQLiteOpenHelper e substituir o método onCreate(), no qual você pode executar um comando SQLite para criar tabelas no banco de dados. Por exemplo: public class DictionaryOpenHelper extends SQLiteOpenHelper { private static final int DATABASE_VERSION = 2; private static final String DICTIONARY_TABLE_NAME = "dictionary"; private static final String DICTIONARY_TABLE_CREATE = "CREATE TABLE " + DICTIONARY_TABLE_NAME + " (" + KEY_WORD + " TEXT, " + KEY_DEFINITION + " TEXT);"; DictionaryOpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(DICTIONARY_TABLE_CREATE); } }
Você pode obter uma instância de sua implementação SQLiteOpenHelper usando o construtor que você definiu. Para escrever e ler a partir do banco de dados, chame getWritableDatabase() e getReadableDatabase(), respectivamente. Estes devolvem um objeto SQLiteDatabase que representa o banco de dados e fornece métodos para operações de SQLite.
ANDROID, uma visão geral – Anderson Duarte de Amorim
208
Você pode executar consultas SQLite usando
Android
métodos query() SQLiteDatabase, que aceitam
limitação para além dos conceitos-
parâmetros de consulta diversos, tais como a tabela a
padrão
ser
recomendamos incluir um valor de
consultada,
a
projeção,
seleção,
colunas,
agrupamento e outros.
não
impõe
qualquer
SQLite.
Nós
campo autoincrementado como chave que pode ser usado como
Cada consulta SQLite irá retornar um Cursor que
uma
aponta para todos os registros encontrados pela
localizar rapidamente um registro.
consulta. O Cursor é sempre o mecanismo com o
Isso não é necessária para dados
qual você pode navegar pelos resultados de uma
privados,
consulta de banco de dados e ler linhas e colunas.
identificação
implementar
mas um
única
para
se
você
provedor
de
conteúdo, você deve incluir uma
Para aplicativos de exemplo que demonstram como
identificação exclusiva com a
usar banco de dados SQLite no Android, consulte as
constante BaseColumns._ID.
aplicações Note Pad e Dicionário pesquisável.
Banco de dados de depuração O Android SDK inclui uma ferramenta sqlite3 de banco de dados que permite que você navegue sobre o conteúdo da tabela, execute comandos SQL e execute outras funções úteis em bancos de dados SQLite.
Usando uma conexão de rede Você pode usar a rede (quando disponível) para armazenar e recuperar dados sobre os seus próprios serviços baseados na web. Para fazer operações de rede, use as classes dos seguintes pacotes: java.net.* android.net.*
ANDROID, uma visão geral – Anderson Duarte de Amorim
209
Artigos Acessibilidade Linguagens e recursos Text-to-Speech TTS: também conhecido como speech synthesis (síntese de fala, em português) é um recurso disponibilizado a partir de Android 1.6 (API Level 4) que possibilita ao dispositivo „ler‟ textos em diversas linguagens. O mecanismo TTS embarcado no Android suporta inglês (britânico ou americano), francês, alemão, italiano e espanhol e precisa saber qual idioma pronunciar para adequar a voz, afinal, uma mesma palavra possui pronuncias diferentes dependendo da língua. Uma checagem da disponibilidade do recurso se faz necessário tendo em vista que, apesar de todos os dispositivos com Android terem a funcionalidade embarcada, alguns possuem armazenamento limitado e pode ser que faltem arquivos de recursos específicos do idioma, sendo assim, o seguinte código verifica a presença dos recursos TTS. Intent checkIntent = new Intent(); checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); startActivityForResult(checkIntent, MY_DATA_CHECK_CODE);
Uma checagem que retorna sucesso é marcada por CHECK_VOICE_DATA_PASS indicando que o dispositivo está pronto para „falar‟, depois da criação do objeto TextToSpeech. Em caso negativo, o dispositivo irá utilizar o ACTION_INSTALL_TTS_DATA que dispara uma ação levando o usuário a fazer a instalação manualmente acessando o Android Market; A instalação é feita automaticamente após a conclusão do download. Uma implementação da verificação do resultado da checagem está abaixo: private TextToSpeech mTts; protected void onActivityResult( int requestCode, int resultCode, Intent data) { if (requestCode == MY_DATA_CHECK_CODE) { if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) { // success, create the TTS instance mTts = new TextToSpeech(this, this); } else { // missing data, install it Intent installIntent = new Intent(); installIntent.setAction( TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA); startActivity(installIntent);
ANDROID, uma visão geral – Anderson Duarte de Amorim
210
} }}
No construtor da instância TextToSpeech nós passamos uma referência ao Contexto a ser usado (neste caso a atividade atual), e um OnInitListener (aqui a nossa atividade também). Dessa forma é habilitado para que a aplicação seja notificada quando o TextTo-Speech é totalmente carregado, então podemos começar a configurar-lo e usá-lo.
Linguagens e localidade No Google I/O 2009 foi mostrado uma utilização do TTS para falar o resultado de uma tradução de e para uma das línguas disponíveis. Um exemplo de chamada é como o abaixo: mTts.setLanguage(Locale.US);
Ou para verificar se uma linguagem está disponível, basta usar o trecho abaixo que retornará TextToSpeech.LANG_COUNTRY_AVAILABLE para indicar que o idioma e o país, como descrito pelo parâmetro de localidade, são suportados (e os dados estão corretamente
instalados),
como
também
pode
retornar
TextToSpeech.LANG_AVAILABLE indicando que a língua está disponível ou o oposto TextToSpeech.LANG_MISSING_DATA. mTts.isLanguageAvailable(Locale.UK)) mTts.isLanguageAvailable(Locale.FRANCE)) mTts.isLanguageAvailable(new Locale("spa", "ESP")))
Obs.: para usar o código Locale.getDefault(), deve-se certificar primeiramente se o idioma padrão é suportado.
Fazendo o dispositivo ‘falar’ A maneira mais simples de fazer isso é usando o método speak() como: String myText1 = "Did you sleep well?"; String myText2 = "I hope so, because it's time to wake up."; mTts.speak(myText1, TextToSpeech.QUEUE_FLUSH, null); mTts.speak(myText2, TextToSpeech.QUEUE_ADD, null);
O mecanismo TTS gerencia uma fila global de todas as entradas para sintetizar, que também são conhecidos como "expressões". Cada TextToSpeech pode gerir sua própria fila a fim de controlar o que vai interromper a emissão atual e que é simplesmente uma fila de espera.
ANDROID, uma visão geral – Anderson Duarte de Amorim
211
No Android, cada stream de áudio que é reproduzido está associado a um tipo de fluxo, tal como definido no android.media.AudioManager . Para um despertador falando, o texto a ser reproduzido pertence ao tipo de fluxo AudioManager.STREAM_ALARM, para que ele respeite as definições de alarme que o usuário escolheu no dispositivo. O último parâmetro do método speak() permite que você passe para os parâmetros do TTS, especificado como pares chave / valor em um HashMap, tal qual: HashMap<String, String> myHashAlarm = new HashMap(); myHashAlarm.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_ALARM)); mTts.speak(myText1, TextToSpeech.QUEUE_FLUSH, myHashAlarm); mTts.speak(myText2, TextToSpeech.QUEUE_ADD, myHashAlarm);
Como as chamadas são assíncronas, pode ser necessário identificar se uma síntese foi concluída, isso pode ser feito da seguinte forma: mTts.setOnUtteranceCompletedListener(this); myHashAlarm.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_ALARM)); mTts.speak(myText1, TextToSpeech.QUEUE_FLUSH, myHashAlarm); myHashAlarm.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "end of wakeup message ID"); // myHashAlarm now contains two optional parameters mTts.speak(myText2, TextToSpeech.QUEUE_ADD, myHashAlarm);
E a atividade é notificada pelo método, repare que a mensagem foi usada para ser identificada no método: public void onUtteranceCompleted(String uttId) { if (uttId == "end of wakeup message ID") { playAnnoyingMusic(); } }
Como o recurso de fala exige bastante processamento, numa situação em que um determinado texto será lido diversas vezes é mais interessante gravar o áudio para ser reproduzido posteriormente. HashMap<String, String> myHashRender = new HashMap(); String wakeUpText = "Are you up yet?"; String destFileName = "/sdcard/myAppCache/wakeUp.wav"; myHashRender.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, wakeUpText); mTts.synthesizeToFile(wakuUpText, myHashRender, destFileName);
A funcionalidade de text-to-speech depende de um serviço dedicado compartilhado entre todos os aplicativos que usam esse recurso. Quando você terminar de usar o TTS, use a instrução mTts.shutdown() dentro do método onDestroy(), por exemplo. ANDROID, uma visão geral – Anderson Duarte de Amorim
212
Interface Toque O modo de toque é um estado da hierarquia de vista que depende unicamente da interação do usuário com o telefone. Por si só, o modo de tocar é algo muito fácil de entender, pois ele simplesmente indica se a interação do usuário passado foi realizada com a tela sensível ao toque. Por exemplo, se você estiver usando um dispositivo com Android, a seleção de um widget com o trackball vai levá-lo sair do modo de tocar, no entanto, se você toca um botão na tela com seu dedo, você entrará no modo de tocar. Quando o usuário não estiver em modo de tocar, nós falamos sobre o modo de trackball, o modo de navegação ou a navegação por teclado, por isso não se surpreenda se você encontrar esses termos. Existe apenas uma API diretamente relacionada com o modo de toque, View.isInTouchMode(). Curiosamente, o modo de tocar é enganosamente simples e as conseqüências de entrar no modo de tocar é muito maior do que você imagina. Vejamos algumas das razões. Toque em modo de seleção e foco Criar um conjunto de ferramentas UI para dispositivos móveis é difícil porque são vários os mecanismos de interação. Alguns dispositivos oferecem apenas 12 teclas, algumas têm uma tela sensível ao toque, alguns exigem uma caneta, alguns têm ambos um ecrã táctil e um teclado. Com base nos recursos de hardware do usuário, ele pode interagir com seu aplicativo usando mecanismos diferentes, então tivemos que pensar muito sobre todos os possíveis problemas que possam surgir. Uma questão nos levou a criar o modo de toque. Imagine um aplicativo simples, ApiDemos por exemplo, que mostra uma lista de itens de texto. O usuário pode navegar livremente pela lista usando a trackball, mas também, em alternativa, deslocar e arremessar a lista usando a tela sensível ao toque. O problema neste cenário é como lidar com a seleção corretamente quando o usuário manipula a lista através da tela sensível ao toque. Neste caso, se o usuário selecionar um item no topo da lista e então arremessa a lista para o fundo, o que deve acontecer com a seleção? E se ele permanecer no item e rolar
ANDROID, uma visão geral – Anderson Duarte de Amorim
213
para fora da tela? O que deve acontecer se o usuário decidiu então mover a seleção com o trackball? Ou pior, o que deve acontecer se o usuário pressiona o trackball para agir de acordo com o item selecionado, que não é mostrado na tela mais? Após cuidadosa consideração, decidimos remover por completo a seleção, quando o usuário manipula a interface do usuário através da tela sensível ao toque. No modo de toque, não há foco e não há seleção. Qualquer item selecionado em uma lista de em uma grade fica desmarcada, logo que o usuário entra no modo de tocar. Da mesma forma, quaisquer widgets ficaram desfocados quando o usuário entra no modo de tocar. A imagem abaixo ilustra o que acontece quando o usuário toca uma lista depois de selecionar um item com o trackball.
Para tornar as coisas mais naturais para o usuário, o quadro sabe como ressuscitar a seleção / foco sempre que o usuário sai do modo de tocar. Por exemplo, se o usuário fosse usar o trackball novamente, a seleção iria reaparecer no item previamente selecionado. É por isso que alguns desenvolvedores estão confusos quando se criar uma exibição personalizada e começar a receber os principais eventos só depois de mover o trackball uma vez: a sua aplicação está no modo de tocar, e eles precisam usar o trackball para sair do modo de tocar e ressuscitar o foco.
ANDROID, uma visão geral – Anderson Duarte de Amorim
214
A relação entre o modo de toque, seleção e foco significa que você não deve confiar na seleção e/ou foco existir em sua aplicação. Um problema muito comum com o Android para novos desenvolvedores é contar com ListView.getSelectedItemPosition(). No modo de toque, este método retornará INVALID_POSITION. Focando no modo Touch Em geral, o foco não existe no modo de tocar. No entanto, o foco pode existir no modo de tocar em uma maneira muito especial chamado focusable. Este modo especial foi criado para widgets que recebem a entrada de texto, como EditText ou ListView. O modo focusable é o que permite ao usuário inserir texto dentro de um campo de texto na tela, sem primeiro selecioná-la com a bola ou o dedo. Quando um usuário toca a tela, o aplicativo irá entrar no modo de toque se ele não estava no modo de tocar já. O que acontece durante a transição para o modo de tocar depende do que o usuário tocou, e que atualmente tem foco. Se o usuário toca um widget que é focusable no modo de tocar, o widget irá receber o foco. Caso contrário, qualquer elemento ao qual se dedica atualmente não vai manter o foco a menos que seja focusable no modo de tocar. Por exemplo, na figura abaixo, quando o usuário toca a tela, o campo de texto recebe o foco. Focusable no modo de é uma propriedade que você pode definir a si mesmo, seja de código ou de XML. No entanto, você deve usá-lo com parcimônia e só em situações muito específicas, porque quebra a coerência com o comportamento normal da interface do Android. Um jogo é um bom exemplo de uma aplicação que pode fazer bom uso do focusable na propriedade de modo sensível ao toque. MapView, se usada em tela cheia como no Google Maps, é outro bom exemplo de onde você
pode
usar
focusable
no
modo
de
tocar
corretamente. Abaixo está um exemplo de um widget focusable em modo de tocar. Quando o usuário bate um AutoCompleteTextView com o dedo, o foco permanece no campo de texto de entrada:
ANDROID, uma visão geral – Anderson Duarte de Amorim
215
Novos desenvolvedores para o Android, muitas vezes pensam que focusable no modo de tocar é a solução que eles precisam para "consertar" o problema do "desaparecimento" da seleção/foco. Nós encorajamos você a pensar muito antes de utilizá-lo. Se usada incorretamente, pode fazer seu aplicativo se comportar de maneira diferente do resto do sistema e simplesmente jogar fora os hábitos do usuário. O quadro Android contém todas as ferramentas necessárias para lidar com as interações do usuário sem usar focusable no modo de tocar. Por exemplo, em vez de tentar fazer ListView sempre manter a sua seleção, basta usar o modo de escolha apropriada, como mostrado na setChoiceMode(int) .
Gestos As telas de toque são uma ótima maneira de interagir com aplicações em dispositivos móveis.
Com uma tela de toque, os usuários podem facilmente tocar, arrastar,
arremessar ou deslizar rapidamente e realizar ações em seus aplicativos favoritos. Para os desenvolvedores de aplicativos o Android faz com que seja fácil reconhecer ações simples, mas tem sido mais difícil de lidar com gestos complicados, às vezes exigindo que os desenvolvedores escrevam um monte de código. É por isso que foi introduzida uma nova API de gestos no Android 1.6. Esta API, localizada no novo pacote android.gesture , permite armazenar, carregar, desenhar e reconhecer gestos. Clique para baixar o código fonte dos exemplos
Método de entrada de dados A partir do Android 1.5, a plataforma Android oferece um Input Method Framework (FMI) que permite a criação de métodos de entrada na tela, como teclados virtuais. Este artigo fornece uma visão geral de editores de método de Android de entrada (IME) e o que um aplicativo precisa fazer para trabalhar bem com eles. O FMI é concebido para
ANDROID, uma visão geral – Anderson Duarte de Amorim
216
apoiar novas classes de dispositivos Android, como os teclados sem hardware, por isso é importante que o aplicativo funcione bem com o FMI e oferece uma ótima experiência para os usuários. O que é um método de entrada? O FMI Android foi concebido para suportar uma variedade de IMEs, incluindo o teclado virtual, reconhecedores de mão-escrita, e tradutores teclado duro. Nosso foco, no entanto, será em teclados virtuais, já que este é o tipo de método de entrada que atualmente faz parte da plataforma. Um usuário normalmente irá acessar o IME atual, tocando em uma exibição de texto para editar, conforme mostrado na tela inicial:
O teclado virtual é posicionado na parte inferior da tela sobre a janela do aplicativo. Para organizar o espaço disponível entre a aplicação e o IME, usamos algumas abordagens, a que é mostrada aqui é chamado de pan e digitalizar, e simplesmente envolve a rolagem da janela do aplicativo em torno de modo que a visão focada atualmente é visível. Este é o modo padrão, pois é mais seguro para as aplicações existentes.
ANDROID, uma visão geral – Anderson Duarte de Amorim
217
Na maioria das vezes o layout da tela é um resize, onde a janela da aplicação é redimensionada para ser totalmente visível. Um exemplo é mostrado aqui, quando escrevo uma mensagem de e-mail:
O tamanho da janela do aplicativo é alterada para que nenhum deles seja escondido pelo IME, permitindo acesso total ao aplicativo e IME. Isto, obviamente, só funciona para aplicativos que têm uma área redimensionável que pode ser reduzida para dar espaço suficiente, mas o espaço vertical neste modo é realmente nada menos do que o que está disponível na orientação paisagem. O principal modo final é fullscreen ou modo de extrato. Isso é usado quando o IME é muito grande para o espaço compartilhar com a aplicação de base. Com o IME padrão, você só vai encontrar essa situação quando a tela está em uma orientação horizontal, embora IMEs sejam livres para usá-lo sempre que desejarem. Neste caso, a janela da aplicação é deixada como está, e simplesmente o IME exibe a tela inteira em cima dela, como mostrado aqui:
ANDROID, uma visão geral – Anderson Duarte de Amorim
218
Porque o IME está cobrindo o aplicativo, ele tem a sua área de edição própria, o que mostra o texto, na verdade contida na petição inicial. Existem também algumas oportunidades limitadas à aplicação que tem que personalizar as partes do IME para melhorar a experiência do usuário. Atributos básicos XML para controlar IMEs Há uma série de coisas que o sistema faz para tentar ajudar de trabalho existente com as aplicações IMEs tão bem quanto possível, tais como: Use pan e scan por padrão, a menos que possa razoavelmente supor que o modo de redimensionar vai trabalhar pela existência de listas, percorrer as exibições, etc. Analisar no TextView vários atributos existentes para adivinhar o tipo de conteúdo (números, texto simples, senha, etc.) para ajudar o teclado a exibir um esquema de chave apropriado. Atribuir algumas ações padrão para o IME fullscreen, como "campo próximo" e "feito". Há também algumas coisas simples que você pode fazer na sua aplicação que, muitas vezes, melhoram significativamente sua experiência de usuário. Exceto quando mencionado explicitamente, estes irão trabalhar em qualquer versão da plataforma Android, mesmo os anteriores para o Android 1.5 (uma vez que irá simplesmente ignorar essas novas opções). Especificando cada tipo de controle de entrada EditText A coisa mais importante para um pedido a fazer é usar o novo atributo android:inputType em cada EditText. O atributo fornece informação muito mais rica
ANDROID, uma visão geral – Anderson Duarte de Amorim
219
sobre o conteúdo do texto. Este atributo realmente substitui muitos atributos existentes ( android: password , android: singleLine , android: numeric , android: phoneNumber , android: capitalize , android: autoText e android: editable). Se você especificar os atributos mais velhos e os novos android:inputType, o sistema usa android:inputType e ignora as outras. O android:inputType atributo tem três partes: A classe é a interpretação geral de caracteres. As classes atualmente suportadas são text (plain text), number (número decimal), phone (número de telefone), e datetime (uma data ou hora). A variação é um refinamento da classe. No atributo, normalmente você vai especificar a classe e variante juntamente com a classe como um prefixo. Por exemplo, textEmailAddress é um campo de texto onde o usuário irá inserir algo que é um endereço de correio (foo@bar.com) para a disposição das teclas terá um @ "caráter" de acesso fácil, e numberSigned é um campo numérico com um sinal. Se somente a classe é especificado, então você obtém o padrão / variante genérica. Sinalizadores adicionais podem ser especificados de abastecimento de refinamento. Esses sinalizadores são específicos para uma classe. Por exemplo, alguns sinalizadores para o text de classe são textCapSentences, textAutoCorrect e textMultiline. Como exemplo, aqui é o EditText novo para a aplicação do IM ver texto da mensagem: <EditText android:id="@+id/edtInput" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:inputType="textShortMessage|textAutoCorrect|textCapSentences|textMultiLine" android:imeOptions="actionSend|flagNoEnterAction" android:maxLines="4" android:maxLength="2000" android:hint="@string/compose_hint"/>
Ativar o modo de redimensionamento e funcionalidades outra janela A segunda coisa mais importante para sua aplicação a fazer é especificar o comportamento global da sua janela em relação ao método de entrada. O aspecto mais
ANDROID, uma visão geral – Anderson Duarte de Amorim
220
visível disto é o controle redimensionar VS. pan e scan mode, mas existem outras coisas que você pode fazer bem para melhorar sua experiência de usuário. Você
normalmente
irá
controlar
esse
comportamento
através
do
android:windowSoftInputMode em cada <activity> em sua AndroidManifest.xml. Como o tipo de entrada, há um par de peças diferentes de dados que pode ser especificado aqui, combinando-as entre si: O modo de ajuste da janela é especificado com adjustResize ou adjustPan . É altamente recomendado que você especifique sempre um ou outro. Você ainda pode controlar se o IME será exibido automaticamente quando sua atividade é exibida e outras situações onde o usuário move a ele. O sistema não mostrará automaticamente um IME, por padrão, mas em alguns casos pode ser conveniente para o usuário se uma aplicação permite esse comportamento. Você pode solicitar este com stateVisible . Há também um número de opções de outro estado para o controle mais detalhado que você pode encontrar na documentação. Um exemplo típico desse campo pode ser visto na atividade de edição do contato, o que garante que ele é redimensionado e exibe automaticamente o IME para o usuário: <activity name="EditContactActivity" android:windowSoftInputMode="stateVisible|adjustResize"> ... </activity>
Controlando os botões de ação Para a personalização final, vamos olhar para a "ação" de botões no IME. Existem atualmente dois tipos de ações: A tecla enter em um teclado virtual é normalmente ligada a uma ação quando não estiverem operando em um multi-line de edição de texto. Quando em modo de tela cheia, um IME pode também colocar um botão de ação adicional à direita do texto que está sendo editado, dando ao usuário o acesso rápido a uma operação de aplicativos comuns.
ANDROID, uma visão geral – Anderson Duarte de Amorim
221
Estas opções são controladas com o atributo android:imeOptions em TextView . O valor que você fornecer aqui pode ser qualquer combinação de: Uma das ações pré-definidas constantes ( actionGo , actionSearch , actionSend , actionNext , actionDone ). Se nenhum desses for especificado, o sistema irá inferir qualquer actionNext ou actionDone dependendo se há um campo focusable após este, você pode explicitamente forçar a nenhuma ação com actionNone. O flagNoEnterAction informa o IME que a ação não deve estar disponível na tecla enter, mesmo que o texto em si não é multi-linha. Isso evita ter ações irrecuperáveis (enviar) que pode ser tocado acidentalmente pelo usuário durante a digitação. O flagNoAccessoryAction remove o botão de ação da área de texto, deixando mais espaço para o texto. O flagNoExtractUi remove completamente a área de texto, permitindo que o aplicativo possa ser visto por trás dele. A anterior mensagem de aplicação MI também fornece um exemplo de um uso interessante da imeOptions, para especificar a ação de envio, mas não que seja mostrado a tecla Enter: android:imeOptions="actionSend|flagNoEnterAction"
APIs para controlar IMEs Para um controle mais avançado sobre o IME, há uma variedade de novas APIs que você pode usar. A menos que tenha um cuidado especial (como por meio de reflexão), utilizando essas APIs fará com que seu aplicativo seja incompatível com as versões anteriores do Android, e você deve se certificar de que você especifique android:minSdkVersion="3" em seu manifesto. A API principal é o novo android.view.inputmethod.InputMethodManager, que você pode recuperar com Context.getSystemService(). Ele permite que você interaja com o estado global do método de entrada, como explicitamente esconder ou mostrar a área atual do IME de entrada.
ANDROID, uma visão geral – Anderson Duarte de Amorim
222
Há também novas bandeiras controlando a interação do método de entrada, que você pode
controlar
através
Window.setSoftInputMode().
da A
existente classe
Window.addFlags()
PopupWindow
acrescentou
e
novos métodos
correspondentes para controlar essas opções em sua janela. Uma coisa em particular a ter
em
conta
é
o
novo
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, que é usado para controlar se uma janela está em cima ou atrás do IME atual. A maior parte da interação entre um IME ativos e a aplicação é feita através do android.view.inputmethod.InputConnection. Esta é a API implementada em uma aplicação, que um IME chama para realizar as operações de edição apropriada sobre o pedido. Você não vai precisar se preocupar com isso, pois TextView oferece sua própria implementação para si mesmo. Há também um punhado de novas View às APIs, a mais importante delas sendo onCreateInputConnection() que cria um novo InputConnection de um IME (e preenche um android.view.inputmethod.EditorInfo com o seu tipo de entrada, as opções IME, e outros dados), novamente, a maioria dos desenvolvedores não precisarão se preocupar com isso, pois TextView cuida disso para você.
Criando um método de entrada de dados Para criar um método de entrada (IME) para inserir texto em campos de texto e outras exibições, você precisa estender a classe InputMethodService. Essa classe fornece grande parte da implementação de base para um método de entrada, em termos de administrar o estado e a visibilidade do método de entrada e se comunicar com a atividade visível no momento. O típico ciclo de vida de um InputMethodService:
ANDROID, uma visão geral – Anderson Duarte de Amorim
223
ANDROID, uma visão geral – Anderson Duarte de Amorim
224
Campos de um aplicativo de texto podem ter diferentes tipos de entrada especificada sobre eles, como o texto de forma livre, numérico, URL, endereço de e-mail e busca. Quando você implementa um novo método de entrada, você precisa estar ciente dos tipos de entrada diferentes. Os métodos de entrada não são automaticamente comutados para diferentes tipos de entrada e por isso é necessário para suportar todos os tipos no IME. No entanto, o IME não é responsável por validar a entrada enviada para o aplicativo. Essa é a responsabilidade da aplicação. Por exemplo, o LatinIME equipados com a plataforma Android oferece layouts diferentes para o texto e entrada de número de telefone:
Preste atenção específica ao enviar o texto para campos de senha. Certifique-se que a senha não é visível na sua interface do usuário - nem no modo de exibição de entrada ou o ponto de vista dos candidatos. Além disso, não salvar a senha em qualquer lugar sem explicitamente informar o utilizador. A interface do usuário deve ser capaz de escala entre as orientações retrato e paisagem. No modo IME não fullscreen, deixar espaço suficiente para a aplicação para mostrar o campo de texto e qualquer contexto associado. De preferência, não mais que metade da tela deve ser ocupada pelo IME. Existem duas maneiras de enviar mensagens de texto para o aplicativo. Você pode enviar individuais eventos-chave ou você pode editar o texto ao redor do cursor no campo a aplicação do texto. Para enviar um evento-chave, você pode simplesmente construir objetos KeyEvent e chamar InputConnection.sendKeyEvent(). Aqui estão alguns exemplos: InputConnection ic = getCurrentInputConnection(); long eventTime = SystemClock.uptimeMillis(); ic.sendKeyEvent(new KeyEvent(eventTime, eventTime,
ANDROID, uma visão geral – Anderson Duarte de Amorim
225
KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)); ic.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, KeyEvent.ACTION_UP, keyEventCode, 0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
Ou então use o seguinte comando: InputMethodService.sendDownUpKeyEvents(keyEventCode);
Ao editar texto em um campo de texto, alguns dos métodos mais úteis na android.view.inputmethod.InputConnection são: getTextBeforeCursor() getTextAfterCursor() deleteSurroundingText() commitText()
Ações de desenhos Drawable Mutations é uma classe para oferecer ao programador “algo que pode ser desenhado” (Drawable Mutations). Por exemplo, um BitmapDrawable é usado para exibir imagens, um ShapeDrawable para desenhar formas e gradientes e assim por diante. Você pode até mesmo combinálas para criar renderizações complexas. Por exemplo, toda vez que você criar um Button, uma nova drawable é carregada a partir do quadro de recursos ( android.R.drawable.btn_default ). Isto significa que todos os botões em todos os aplicativos usam uma instância diferente drawable como pano de fundo.
No entanto, todas estas partes drawables possuem um estado comum, o
chamado "estado constante". O conteúdo deste estado varia de acordo com o tipo de drawable você está usando, mas, geralmente, contém todas as propriedades que podem ser definidos por um recurso. No caso de um botão, o constante estado contém uma imagem bitmap. Desta forma, todos os botões em todos os aplicativos compartilham o mesmo bitmap, o que economiza um monte de memória. O diagrama abaixo mostra como as entidades são criadas quando você atribuir o recurso de mesma imagem como fundo de dois pontos de vista diferentes. Como você
ANDROID, uma visão geral – Anderson Duarte de Amorim
226
pode ver dois drawables são criadas, mas que ambos compartilham o mesmo estado constante, portanto, a mesma bitmap:
Esse recurso de compartilhamento de estado é de grande valia para evitar o desperdício de memória, mas pode causar problemas quando você tentar modificar as propriedades de um drawable. Imagine uma aplicação com uma lista de livros. Cada livro tem uma estrela ao lado de seu nome, totalmente opaco quando o usuário marca o livro como um dos favoritos, e translúcido quando o livro não é um favorito. Para conseguir esse efeito, você provavelmente vai escrever o seguinte código em seu método adaptador de lista do getView(): Book = ...; TextView Item_da_lista = ...; listItem.setText (book.getTitle ()); estrela Drawable = context.getResources () getDrawable (R.drawable.star).; if (book.isFavorite ()) { star.setAlpha (255); / opaca / Else {} star.setAlpha (70); / translúcido }
ANDROID, uma visão geral – Anderson Duarte de Amorim
227
Infelizmente, este pedaço de código gera um resultado um pouco estranho: todas as drawables têm a mesma opacidade. Esse resultado é explicado pela constante estado. Mesmo que nós estamos começando uma nova instância drawable para cada item da lista, o constante estado permanece o mesmo e, no caso de BitmapDrawable, a opacidade é parte da constante estado. Assim, mudando a opacidade de uma instância muda drawable a opacidade de todas as outras instâncias. Android 1.5 e superior oferecem uma maneira muito fácil de resolver esse problema com a nova mutate() método. Quando você chamar esse método em um drawable, a constante estado de drawable é duplicada para permitir a você alterar qualquer propriedade, sem afetar outros drawables. Note-se que os bitmaps são ainda comuns, mesmo depois de uma mutação drawable. O diagrama abaixo mostra o que acontece quando você chama mutate() em um drawable:
ANDROID, uma visão geral – Anderson Duarte de Amorim
228
Agora, o código a ser escrito é parecido com o abaixo. Drawable star = context.getResources().getDrawable(R.drawable.star); if (book.isFavorite()) { star.mutate().setAlpha(255); // opaque } else { star. mutate().setAlpha(70); // translucent }
Assim, um exemplo se uso, como o citado acerca dos livros, fica como a figura abaixo.
Truques de layout: criando layouts eficientes O Android UI Toolkit oferece diversos gerenciadores de layout que são bastante fáceis de usar e, na maioria das vezes, você só precisa das características básicas destes gerenciadores de layout para implementar uma interface de usuário. Ater-se às características básicas infelizmente não é a forma mais eficiente para criar interfaces de usuário. Um exemplo comum é o abuso de LinearLayout , o que leva a uma proliferação de pontos de vista e hierarquia de vista. Cada ponto de vista - ou pior, cada gerente de layout - que você adicionar à sua aplicação tem um custo: layout de inicialização, e o desenho se torna mais lento. A passagem de layout pode ser muito
ANDROID, uma visão geral – Anderson Duarte de Amorim
229
cara principalmente quando você aninhar diversos LinearLayout que utilizam o weight do parâmetro, que requer que o nodo filho seja medido duas vezes. Consideremos um exemplo muito simples e comum de um esquema: um item da lista com um ícone no lado esquerdo, um título em cima e uma descrição opcional abaixo do título.
Uma ImageView e dois TextView são posicionados em relação uns aos outros, aqui é o wireframe do layout como capturado pelos HierarchyViewer :
A implementação desta estrutura é simples com LinearLayout. O item em si é uma LinearLayout horizontal com um ImageView e um LinearLayout vertical, que contém as duas TextView. Aqui está o código fonte deste esquema: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="?android:attr/listPreferredItemHeight" android:padding="6dip"> <ImageView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_marginRight="6dip" android:src="@drawable/icon" /> <LinearLayout android:orientation="vertical" android:layout_width="0dip" android:layout_weight="1" android:layout_height="fill_parent"> <TextView android:layout_width="fill_parent" android:layout_height="0dip"
ANDROID, uma visão geral – Anderson Duarte de Amorim
230
android:layout_weight="1" android:gravity="center_vertical" android:text="My Application" /> <TextView android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:singleLine="true" android:ellipsize="marquee" android:text="Simple application that shows how to use RelativeLayout" /> </LinearLayout> </LinearLayout>
Este esquema acima funciona, mas pode ser prejudicial se você instanciá-la para cada item da lista de um ListView . O mesmo esquema pode ser reescrito utilizando um único RelativeLayout , salvando assim um ponto de vista, e melhor ainda, um nível na hierarquia do ponto de vista, por item da lista. A implementação do layout com um RelativeLayout é simples: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="?android:attr/listPreferredItemHeight" android:padding="6dip"> <ImageView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_alignParentTop="true" android:layout_alignParentBottom="true" android:layout_marginRight="6dip" android:src="@drawable/icon" /> <TextView android:id="@+id/secondLine" android:layout_width="fill_parent" android:layout_height="26dip" android:layout_toRightOf="@id/icon" android:layout_alignParentBottom="true" android:layout_alignParentRight="true"
ANDROID, uma visão geral – Anderson Duarte de Amorim
231
android:singleLine="true" android:ellipsize="marquee" android:text="Simple application that shows how to use RelativeLayout" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_toRightOf="@id/icon" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:layout_above="@id/secondLine" android:layout_alignWithParentIfMissing="true" android:gravity="center_vertical" android:text="My Application" /> </RelativeLayout>
Esta nova aplicação se comporta exatamente da mesma forma que a implementação anterior, exceto em um caso: o item da lista que se pretende apresentar tem duas linhas de texto: o título e uma descrição opcional. Quando uma descrição não está disponível para um determinado item da lista o aplicativo simplesmente define a visibilidade do segundo TextView como GONE. Isso funciona perfeitamente com o LinearLayout, mas não com o RelativeLayout:
Em um RelativeLayout, as vistas são alinhadas com a sua mãe, com o RelativeLayout
em si, ou com outras visões. Por exemplo, nós declaramos que a
descrição está alinhado com a parte inferior da RelativeLayout e que o título é colocado acima da descrição e ancorado a mãe da top. Com a descrição GONE, o RelativeLayout não sabe a posição da margem inferior do título. Para resolver esse problema,
você
pode
usar
um
parâmetro
muito
especial
chamado
layout_alignWithParentIfMissing. Este parâmetro boolean simplesmente diz ao RelativeLayout a utilizar os seus próprios limites como âncoras quando um alvo está faltando. Por exemplo, se você posicionar uma view à direita de uma GONE e definir alignWithParentIfMissing como true,
ANDROID, uma visão geral – Anderson Duarte de Amorim
232
RelativeLayout
vez vai ancorar o fim de sua borda esquerda. No nosso caso, usando
alignWithParentIfMissing
fará RelativeLayout alinhar a parte inferior do título
com si mesmo. O resultado é o seguinte:
O comportamento do nosso layout está perfeito, mesmo quando a descrição é GONE. Ainda melhor, a hierarquia é mais simples porque não estamos usando pesos LinearLayout's é também mais eficiente. A diferença entre as duas implementações torna-se evidente quando se comparam as hierarquias em HierarchyViewer:
ANDROID, uma visão geral – Anderson Duarte de Amorim
233
Truques de layout: usando ViewStub Compartilhamento e reutilização de componentes de interface do usuário são muito fáceis com o Android, graças à tag <include/>. Às vezes é tão fácil criar complexas construções UI que UI termina com um grande número de pontos de vista, algumas das quais raramente são utilizados. Felizmente, o Android oferece um widget muito especial chamado ViewStub , que traz todos os benefícios da <include /> sem poluir a interface do usuário com views raramente utilizadas. A ViewStub é uma visão leve. Não tem dimensão, não tira nada e não participa no layout de qualquer forma. Isso significa que uma ViewStub é muito barata para inflar e muito barata para se manter em uma hierarquia de vista. A ViewStub pode ser melhor descrita como um preguiçoso <include />. O esquema referenciado por um ViewStub é inflado e adicionado à interface do usuário somente quando assim o decidir. A figura a seguir vem da aplicação Prateleira.
O principal objetivo da atividade
mostrado na imagem é apresentar ao usuário uma lista pesquisável dos livros:
ANDROID, uma visão geral – Anderson Duarte de Amorim
234
A atividade também é usada quando o usuário adiciona ou importa novos livros. Durante essa operação, a Prateleira mostra bits adicionais de interface do usuário. A imagem abaixo mostra a barra de progresso e um botão cancelar que aparecerá na parte inferior da tela durante uma importação:
Como a importação de livros não é uma operação comum, pelo menos quando comparado à visita a lista de livros, o painel de importação é representado inicialmente por um ViewStub :
ANDROID, uma visão geral – Anderson Duarte de Amorim
235
Quando o usuário inicia o processo de importação, o ViewStub é inflado e passa a ter o conteúdo do arquivo de layout que faz referência:
ANDROID, uma visão geral – Anderson Duarte de Amorim
236
Para usar um ViewStub , tudo que você precisa é especificar um atributo android:id, para depois inflar, e um atributo android:layout para fazer referência ao arquivo de layout para incluir e inflar. Um stub permite que você use um terceiro atributo, android:inflatedId , que pode ser usado para substituir o id da raiz do arquivo incluído. Finalmente, os parâmetros de layout especificados no topo serão aplicados para a raiz do layout incluído. Aqui está um exemplo: <ViewStub android:id="@+id/stub_import" android:inflatedId="@+id/panel_import" android:layout="@layout/progress_overlay" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" />
Quando estiver pronto para inflar o stub, basta invocar o método inflate(). Você também pode simplesmente alterar a visibilidade do stub para VISIBLE ou INVISIBLE e o stub irá inflar. Note, no entanto que o método inflate() tem a vantagem de retornar a raiz View ao inflar o layout: ((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE); // or View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
É muito importante lembrar que após o stub ser inflado, o topo é removido da hierarquia de vista. Como tal, é necessário manter uma referência de vida longa, por exemplo, em um campo de instância de classe, a uma ViewStub . Um ViewStub é um grande compromisso entre a facilidade de programação e de eficiência. Em vez de encher vistas manualmente e adicioná-los em tempo de execução de sua hierarquia de vista, basta usar um ViewStub. É barato e fácil. A única desvantagem de ViewStub é que atualmente não suporta a tag <merge />.
Truques de layout: mesclando layouts A tag <merge /> foi criada com a finalidade de otimizar layouts Android, reduzindo o número de níveis em árvores de vista. É mais fácil entender o problema que esta tag resolve olhando um exemplo. O esquema XML a seguir declara um layout que mostra uma imagem com o título em cima dela.
A estrutura é bastante simples, uma
FrameLayout é usada para empilhar um TextView em cima de um ImageView : ANDROID, uma visão geral – Anderson Duarte de Amorim
237
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/golden_gate" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dip" android:layout_gravity="center_horizontal|bottom" android:padding="12dip" android:background="#AA000000" android:textColor="#ffffffff" android:text="Golden Gate" /> </FrameLayout>
Isso torna o layout bem e nada parece errado com ele:
ANDROID, uma visĂŁo geral â&#x20AC;&#x201C; Anderson Duarte de Amorim
238
As coisas ficam mais interessantes quando você inspecionar o resultado com HierarchyViewer. Se você olhar atentamente para a árvore resultante, você vai notar que o FrameLayout definida no arquivo XML (destacada em azul abaixo) é o filho único de outro FrameLayout:
Só fizemos a interface mais complexa, sem qualquer razão. Mas como poderíamos nos livrar do presente FrameLayout? Afinal de contas, documentos XML requerem uma marca de raiz e tags em esquemas XML sempre representam instâncias. É aí que o <merge /> vem a calhar. Quando o LayoutInflater encontra essa marca, ele ignora-o e adiciona o <merge /> dos nodos ao <merge /> pai. Confuso? Vamos reescrever o nosso layout XML anterior, substituindo o FrameLayout com <merge />: ANDROID, uma visão geral – Anderson Duarte de Amorim
239
<merge xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/golden_gate" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dip" android:layout_gravity="center_horizontal|bottom" android:padding="12dip" android:background="#AA000000" android:textColor="#ffffffff" android:text="Golden Gate" /> </merge>
Com esta nova versão, tanto o TextView quanto o ImageView serão adicionados diretamente ao nível superior. O resultado será o mesmo, mas visualmente a hierarquia do ponto de vista é simples:
ANDROID, uma visão geral – Anderson Duarte de Amorim
240
Obviamente, o uso do <merge /> funciona neste caso porque o nodo pai é sempre um FrameLayout. Você não pode aplicar este truque se o layout estava usando um LinearLayout como sua marca raiz, por exemplo. O <merge /> pode ser útil em outras situações, no entanto. Por exemplo, ele funciona perfeitamente quando combinado com o <include />. Você também pode usar <merge /> quando você cria um composto de exibição personalizado. Vamos ver como podemos usar essa tag para criar uma nova visão chamada OkCancelBar que simplesmente mostra dois botões com rótulos personalizados. <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:okCancelBar="http://schemas.android.com/apk/res/com.example.android.merge"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/golden_gate" /> <com.example.android.merge.OkCancelBar android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:paddingTop="8dip" android:gravity="center_horizontal" android:background="#AA000000" okCancelBar:okLabel="Save" okCancelBar:cancelLabel="Don't save" /> </merge>
ANDROID, uma visão geral – Anderson Duarte de Amorim
241
O código fonte do OkCancelBar é muito simples, porque os dois botões são definidos em um arquivo XML externo, carregado com um LayoutInflate. Como você pode ver no trecho a seguir, o esquema XML R.layout.okcancelbar é inflado com o OkCancelBar como o pai: public class OkCancelBar extends LinearLayout { public OkCancelBar(Context context, AttributeSet attrs) { super(context, attrs); setOrientation(HORIZONTAL); setGravity(Gravity.CENTER); setWeightSum(1.0f); LayoutInflater.from(context).inflate(R.layout.okcancelbar, this, true); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.OkCancelBar, 0, 0); String text = array.getString(R.styleable.OkCancelBar_okLabel); if (text == null) text = "Ok"; ((Button) findViewById(R.id.okcancelbar_ok)).setText(text); text = array.getString(R.styleable.OkCancelBar_cancelLabel); if (text == null) text = "Cancel"; ((Button) findViewById(R.id.okcancelbar_cancel)).setText(text);
ANDROID, uma visão geral – Anderson Duarte de Amorim
242
array.recycle(); } }
Os dois botões são definidos no esquema XML a seguir. Como você pode ver, usamos o <merge /> para adicionar os dois botões diretamente ao OkCancelBar . Cada botão está incluído a partir do arquivo XML externo mesmo layout para torná-los mais fáceis de manter, nós simplesmente substituimos o seu id: <merge xmlns:android="http://schemas.android.com/apk/res/android"> <include layout="@layout/okcancelbar_button" android:id="@+id/okcancelbar_ok" /> <include layout="@layout/okcancelbar_button" android:id="@+id/okcancelbar_cancel" /> </merge>
Nós criamos uma forma flexível e fácil de manter a exibição personalizada que gera uma hierarquia de vista eficiente:
ANDROID, uma visão geral – Anderson Duarte de Amorim
243
O <merge /> é extremamente útil e pode fazer maravilhas em seu código. No entanto, ele sofre de um par de limitações: <merge /> só pode ser usado como a tag raiz de um esquema XML Ao inflar um layout começando com uma <merge /> você deve especificar um pai ViewGroup e você deve definir attachToRoot como true
ListView, uma otimização ListView é um dos widgets mais amplamente utilizados no Android. É bastante fácil de usar, muito flexível e incrivelmente poderoso. Um dos problemas mais comuns com ListView acontece quando você tenta usar um plano personalizado. Por padrão, como muitos widgets do Android, o ListView tem um fundo transparente, o que significa que você pode ver através do padrão da janela do fundo. Além disso, ListView permite as margens de desvanecimento por padrão, como você pode ver no topo da tela a seguir - o texto do primeiro item desvanece-se gradualmente para preto. Esta técnica é utilizada em todo o sistema para indicar que o contêiner pode ser rolado.
ANDROID, uma visão geral – Anderson Duarte de Amorim
244
O efeito de fade é implementado usando uma combinação de Canvas.saveLayerAlpha() e Porter-Duff Destination Out blending mode. Infelizmente, as coisas começam a ficarem feias quando você tenta usar um background personalizado no ListView ou quando você muda a janela de fundo. A seguir duas imagens mostram o que acontece em um aplicativo quando você mudar o fundo da janela. A imagem da esquerda mostra como a lista se parece por padrão e a imagem da direita mostra o como a lista se parece durante um deslocamento iniciado com um gesto de tocar:
Este problema de processamento é causada por uma otimização do quadro Android ativada por padrão em todas as instâncias do ListView. Esta aplicação funciona muito bem, mas infelizmente é muito caro e pode trazer para baixo o desempenho de desenho, um pouco como ele necessita para capturar uma parcela da prestação em um bitmap fora da tela e, em seguida, requer a mistura extra (o que implica readbacks da memória). ListView‟s são, na maioria das vezes exibidos em uma base sólida, não há nenhuma razão para enveredar por esse caminho caro. É por isso que nós introduzimos uma otimização chamada de “pitada de cor cache”. A dica de cor cache é uma cor RGB definido por padrão com a cor de fundo da janela, que é #191919 no tema escuro do
ANDROID, uma visão geral – Anderson Duarte de Amorim
245
Android. Quando esta dica é definida, ListView (na verdade, sua classe base View) sabe que vai recorrer a um fundo sólido e substitui, portanto, a cara renderização saveLayerAlpha()/Porter-Duff por um gradiente simples. Este gradiente vai desde totalmente transparente para o valor de cor cache e é exatamente isso que você vê na imagem acima, com o gradiente escuro na parte inferior da lista. No entanto, isso ainda não explica por que a lista inteira fica em preto durante um pergaminho. Como mencionado anteriormente, ListView tem um fundo transparente/translúcido por padrão, assim como todos os widgets padrão na caixa de ferramentas UI Android. Isto implica que, quando ListView redesenha seus filhos, tem que misturar as crianças com janela de fundo. Mais uma vez, isto requer readbacks caros de memória que são particularmente dolorosos durante um deslocamento ou uma aventura, quando acontece o desenho dezenas de vezes por segundo. Para melhorar o desempenho de desenho durante as operações de rolagem, o quadro Android reutiliza a dica de cor cache. Para corrigir esse problema, tudo que você tem que fazer é desativar o cache de otimização de cor, se você usar uma cor de fundo não-contínua, ou definir a dica para o valor de cor sólido adequado. Você pode fazer isso a partir do código ou, de preferência, a partir de XML, usando o android:cacheColorHint. Para desabilitar a otimização, basta usar a cor transparente #00000000. A figura abaixo mostra uma lista com android:cacheColorHint="#00000000" definido no arquivo de layout XML:
ANDROID, uma visão geral – Anderson Duarte de Amorim
246
Como você pode ver, o fade funciona perfeitamente contra o fundo personalizado em madeira. O recurso de cache de sugestão de cor é interessante porque mostra como as otimizações podem tornar sua vida mais difícil em algumas situações. Neste caso específico, porém, o benefício do comportamento padrão compensa a maior complexidade.
Live folders Live Folders, introduzida no Android 1.5 API (Nível 3), permitem a exibição de qualquer fonte de dados na tela inicial, sem forçar o usuário a lançar uma aplicação. Uma live folder é simplesmente uma visão em tempo real de um ContentProvider. Como tal, uma live folder pode ser usada para exibir todos os contatos do usuário ou bookmarks, e-mail, listas de reprodução, um feed RSS, e assim por diante.
As
possibilidades são infinitas! A plataforma inclui várias pastas padrão para a exibição de contatos. Por exemplo, a imagem abaixo mostra o conteúdo das pastas ao vivo que mostra todos os contatos com um número de telefone:
ANDROID, uma visão geral – Anderson Duarte de Amorim
247
Se a sincronização de contatos acontece em segundo plano enquanto o usuário está visitando esta pasta ao vivo, o usuário verá a mudança acontecer em tempo real. Live folders não são apenas úteis, mas elas também são fáceis de adicionar ao seu aplicativo. Este artigo mostra como adicionar uma live folder para uma aplicação, como por exemplo as chamadas Prateleiras. Para entender melhor como trabalham as pastas, você pode baixar o código fonte da aplicação e modificá-lo seguindo as instruções abaixo. Para dar ao usuário a opção de criar uma nova pasta para um aplicativo, você primeiro precisa criar uma nova atividade com a intenção de filtro cuja ação é android.intent.action.CREATE_LIVE_FOLDER.
Para
isso,
basta
abrir
AndroidManifest.xml e adicionar algo semelhante a isto: <activity android:name=".activity.BookShelfLiveFolder" android:label="BookShelf"> <intent-filter> <action android:name="android.intent.action.CREATE_LIVE_FOLDER" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
O rótulo e o ícone desta atividade são o que o usuário verá na tela inicial quando se escolhe uma pasta:
ANDROID, uma visão geral – Anderson Duarte de Amorim
248
Uma vez que você só precisa de uma intenção de filtro, é possível e, por vezes aconselhável, a reutilização de uma atividade existente. No caso de Prateleiras, vamos criar
uma
nova
org.curiouscreature.android.shelves.activity.BookShelfLiveFolder.
atividade, O
papel
desta
atividade é enviar um resultado Intent para Página contendo a descrição da live folder: o seu nome, ícone, modo de apresentação e conteúdo URI. O URI conteúdo é muito importante, pois descreve o que ContentProvider será usado para preencher a live folder. O código da atividade é muito simples, como você pode ver aqui: public class BookShelfLiveFolder extends Activity { public static final Uri CONTENT_URI = Uri.parse("content://shelves/live_folders/books"); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Intent intent = getIntent(); final String action = intent.getAction(); if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) { setResult(RESULT_OK, createLiveFolder(this, CONTENT_URI, "Books", R.drawable.ic_live_folder)); } else { setResult(RESULT_CANCELED); } finish(); } private static Intent createLiveFolder(Context context, Uri uri, String name, int icon) { final Intent intent = new Intent(); intent.setData(uri); intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, name); intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON, Intent.ShortcutIconResource.fromContext(context, icon)); intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE, LiveFolders.DISPLAY_MODE_LIST); return intent; } }
Esta atividade, quando invocada com a ACTION_CREATE_LIVE_FOLDER, retorna com a intenção de um URI, content://shelves/live_folders/books , e três extras para descrever a pasta ao vivo. Existem outros extras e constantes que você pode usar e você deve consultar a documentação do android.provider.LiveFolders para mais detalhes.
ANDROID, uma visão geral – Anderson Duarte de Amorim
249
Quando a Home recebe esta ação, uma nova live folder é criada no desktop do usuário, com o nome e o ícone que você forneceu. Então, quando o usuário clica na pasta para abri-la ao vivo, a Home consulta o provedor de conteúdo referenciado pelo URI fornecido. Prestadores de pastas Live devem obedecer a regras específicas de nomeação. O Cursor retornado pelo método query() deve ter pelo menos duas colunas chamadas LiveFolders._ID e LiveFolders.NAME . O primeiro é o identificador único de cada item na live folder e o segundo é o nome do item. Há nomes de coluna que você pode usar para especificar um ícone, uma descrição, a intenção de associar ao item (acionado quando o usuário clicar nesse item), etc. Novamente, consulte a documentação do android.provider.LiveFolders para mais detalhes . No nosso exemplo, tudo o que precisamos fazer é modificar o provedor existente nas prateleiras Primeiro,
chamado precisamos
org.curiouscreature.android.shelves.provider.BooksProvider. modificar
o
URI_MATCHER
para
reconhecer
nosso
content://shelves/live_folders/books URI de conteúdo: private static final int LIVE_FOLDER_BOOKS = 4; // ... URI_MATCHER.addURI(AUTHORITY, "live_folders/books", LIVE_FOLDER_BOOKS);
Então, precisamos criar um mapa de nova projeção para o cursor.
Um mapa de
projeção pode ser usado para "renomear" colunas. No nosso caso, vamos substituir BooksStore.Book._ID , BooksStore.Book.TITLE e BooksStore.Book.AUTHORS com LiveFolders._ID , LiveFolders.TITLE e LiveFolders.DESCRIPTION: private static final HashMap<string, string=""> LIVE_FOLDER_PROJECTION_MAP; static { LIVE_FOLDER_PROJECTION_MAP = new HashMap<string, string="">(); LIVE_FOLDER_PROJECTION_MAP.put(LiveFolders._ID, BooksStore.Book._ID + " AS " + LiveFolders._ID); LIVE_FOLDER_PROJECTION_MAP.put(LiveFolders.NAME, BooksStore.Book.TITLE + " AS " + LiveFolders.NAME); LIVE_FOLDER_PROJECTION_MAP.put(LiveFolders.DESCRIPTION, BooksStore.Book.AUTHORS + " AS " + LiveFolders.DESCRIPTION); }
Porque estamos a dar um título e uma descrição para cada linha, Home irá exibir automaticamente cada item da live folder com duas linhas de texto. Finalmente, vamos
ANDROID, uma visão geral – Anderson Duarte de Amorim
250
implementar a query(), fornecendo o nosso mapa de projeção para o construtor de consultas SQL: public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); switch (URI_MATCHER.match(uri)) { // ... case LIVE_FOLDER_BOOKS: qb.setTables("books"); qb.setProjectionMap(LIVE_FOLDER_PROJECTION_MAP); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } SQLiteDatabase db = mOpenHelper.getReadableDatabase(); Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, BooksStore.Book.DEFAULT_SORT_ORDER); c.setNotificationUri(getContext().getContentResolver(), uri); return c; }
Agora você pode compilar e implantar o aplicativo, vá para a tela inicial e tente adicionar uma live folder. Você pode adicionar uma pasta para os livros na sua tela inicial e quando você abri-lo, veja a lista de todos os seus livros, com seus títulos e autores, e bastou algumas linhas de código:
ANDROID, uma visão geral – Anderson Duarte de Amorim
251
A API de live folders é extremamente simples e depende apenas de intenções e URI. Se você quiser ver mais exemplos de aplicação das pastas, você pode ler o código-fonte do aplicativo de contatos e do provedor de Contatos.
Live Wallpapers Começando com o Android 2.1 (API Nível 7), os utilizadores podem agora desfrutar de papéis de parede ao vivo - mais ricos, animados, cenários interativos - em suas telas iniciais. Um papel de parede ao vivo é muito semelhante a uma aplicação Android normal e tem acesso a todas as facilidades da plataforma: SGL (desenho 2D), OpenGL (desenho 3D), GPS, acelerômetro, acesso à rede, etc. Os papéis de parede ao vivo, incluído no Nexus One, demonstram o uso de algumas dessas APIs para criar experiências divertidas e interessantes. Por exemplo, o papel de parede da grama usa a localização do telefone para calcular o nascer e o por do sol, a fim de exibir o céu adequado.
Criar o seu próprio papel de parede ao vivo é fácil, especialmente se você teve experiência anterior com SurfaceView ou Canvas. Para saber como criar um papel de parede ao vivo, você deve verificar se o código de exemplo CubeLiveWallpaper . Em termos de execução, um papel de parede ao vivo é muito similar a um Service. A única diferença é a adição de um novo método, onCreateEngine(), cujo objetivo é criar um WallpaperService.Engine. O dispositivo é responsável pela gestão do ciclo de vida e desenho de um papel de parede. O sistema fornece uma superfície sobre a qual você pode desenhar, assim como você faria com um SurfaceView. Desenho de um papel de parede pode ser muito caro por isso você deve otimizar o código, tanto quanto possível para evitar o uso excessivo de CPU, não só para a vida da bateria, mas também para evitar a abrandar o resto do sistema. É também por isso que a parte mais importante do ciclo de vida de um papel de parede é quando se torna visível, como indicado por uma ANDROID, uma visão geral – Anderson Duarte de Amorim
252
chamada para onVisibilityChanged(). Quando invisíveis, como quando o usuário inicia um aplicativo que cobre a tela inicial, o papel de parede tem que parar todas as atividades. O dispositivo também pode implementar vários métodos para interagir com o usuário ou o aplicativo de origem. Por exemplo, para reagir ao toque, basta implementar onTouchEvent(). Finalmente, os aplicativos podem enviar comandos arbitrários para o papel de parede ao vivo. Atualmente, apenas o pedido inicial padrão envia comandos para o onCommand() do papel de parede ao vivo: android.wallpaper.tap: Quando o usuário bate um espaço vazio na área de
1.
trabalho. Este comando é interpretado pelo dispositivo para fazer o papel de parede reagir à interação do usuário. android.home.drop: Quando o usuário solta um ícone ou um widget no espaço
2.
de trabalho. Se você está desenvolvendo um papel de parede ao vivo, lembre-se que o recurso só é suportado no Android 2.1 (API nível 7) e versões superiores da plataforma. Para garantir que seu pedido só pode ser instalado em dispositivos que suportam wallpapers, lembre-se de acrescentar o seguinte trecho de código antes de publicar no Android Market: <uses-sdk android:minSdkVersion="7" />, que indica à Android Market e à plataforma que seu aplicativo requer Android 2.1 ou superior. <uses-feature android:name="android.software.live_wallpaper" />, que informa à Android Market que seu aplicativo inclui um papel de parede ao vivo. Android Market usa esse recurso como um filtro, ao apresentar listas de usuários de aplicações disponíveis. Quando você declarar esse recurso, o Android Market exibe sua aplicação apenas aos usuários cujas dispositivos suportam wallpapers ao vivo, enquanto oculta de outros dispositivos sobre os quais não seria capaz de executar.
ANDROID, uma visão geral – Anderson Duarte de Amorim
253
Usando webViews Um pequeno aplicativo chamado WebViewDemo mostra como você pode adicionar conteúdo da Web em seu aplicativo. Você pode encontrá-la no projeto de aplicativos para Android. Esta aplicação demonstra como você pode incorporar um WebView em uma atividade e também como você pode ter comunicação bidirecional entre o aplicativo e o conteúdo da web. Um WebView utiliza o mesmo processamento e motor de JavaScript, o navegador, mas ele é executado sob o controle de sua aplicação. O WebView podem ser em tela cheia ou você pode misturá-la com outras visões. O WebView pode baixar conteúdo da web, ou pode vir a partir de arquivos locais armazenados em seu diretório de ativos. O conteúdo pode até ser gerado dinamicamente pelo código do aplicativo. Este aplicativo não faz muita coisa: quando você clica sobre o Android, ele levanta o braço.
Isso poderia, naturalmente, ser facilmente conseguido com um pouco de JavaScript. Em vez disso, porém, WebViewDemo toma um caminho um pouco mais complicado para ilustrar duas características muito poderosa de WebView. Primeiro, o JavaScript em execução dentro do WebView pode se ligar com o código em sua atividade. Você pode usar isso para fazer suas ações disparar JavaScript como começar uma nova atividade, ou pode ser usada para buscar dados de um banco de dados ou ContentProvider . A API para isso é muito simples: basta conectar com o addJavascriptInterface()em sua WebView. Você passa um objeto cujos métodos você
ANDROID, uma visão geral – Anderson Duarte de Amorim
254
deseja expor ao JavaScript e o nome a ser usado para fazer chamadas. Você pode ver a sintaxe exata em WebViewDemo.java. Aqui nós estamos fazendo o nosso objeto DemoJavascriptInterface disponível em JavaScript para onde ele vai ser chamado de "window.demo". Em segundo lugar, sua atividade pode invocar métodos JavaScript. Tudo que você tem a fazer é chamar o método loadUrl com a chamada de JavaScript apropriada: mWebView.loadUrl("javascript:wave()");
Nosso WebViewDemo utiliza duas técnicas: quando você clica sobre o Android, que chama a atividade, que então se vira e chama de volta para o JavaScript. WebViews são muito poderosos e podem ser uma ferramenta valiosa para ajudar a construir a sua aplicação - especialmente se você já tem um monte de conteúdo HTML. Quando isso acontece, usamos exatamente essa abordagem em algumas das aplicações que nós escrevemos.
Funcionalidades Caixa de pesquisa Começando com o Android 1.6, a plataforma inclui suporte para caixa de pesquisa rápida (CPR ou QSB Quick Search Box), uma poderosa estrutura de pesquisa de todo o sistema. A caixa de pesquisa rápida permite que os usuários rapidamente e facilmente encontrem o que procuram, tanto em seus dispositivos quanto na web. Ele sugere conteúdo no seu dispositivo enquanto você digita como aplicativos, contatos, histórico do navegador, e música. Também oferece resultados das sugestões de pesquisa na web, anúncios de empresas locais e outras informações do Google, tais como cotações da bolsa, previsão do tempo e status de vôo. Tudo isso está disponível logo na tela inicial, tocando na caixa de pesquisa rápida. Seus aplicativos podem fornecer sugestões de pesquisa que surgirão a usuários de CPR junto com outros resultados de pesquisa e sugestões. Isso torna possível para os usuários acessem o conteúdo do seu aplicativo de fora da sua aplicação, por exemplo, a partir da tela inicial.
ANDROID, uma visão geral – Anderson Duarte de Amorim
255
Nota: Os fragmentos de código deste documento estão relacionados a um aplicativo de exemplo chamado Dicionário pesquisável. O aplicativo está disponível para o Android 1.6 e plataformas posteriores. Plataforma de lançamentos de versões anteriores para o Android 1.6 já previam um mecanismo que permite que você exponha pesquisa e sugestões de busca na sua aplicação, conforme descrito na documentação para SearchManager. Esse mecanismo não mudou e exige as seguintes coisas em sua AndroidManifest.xml : 1.
Em sua <activity>, a intenção do filtro, e uma referência a um searchable.xml arquivo (descritas abaixo): <intent-filter> <action android:name="android.intent.action.SEARCH" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
2.
Um provedor de conteúdo que pode oferecer sugestões de pesquisa de acordo com as URIs e formatos de coluna especificada pelo Sugestões de Pesquisa seção do docs SearchManager. <!-- Provides search suggestions for words and their definitions. --> <provider android:name="DictionaryProvider" android:authorities="dictionary" android:syncable="false" />
No searchable.xml, você especifica algumas coisas sobre como você deseja que o sistema de busca apresente a pesquisa para a sua aplicação, incluindo a autoridade do provedor de conteúdo que oferece sugestões para o usuário enquanto digitam. Aqui está um exemplo do searchable.xml de um aplicativo do Android que fornece sugestões de pesquisa dentro de suas próprias atividades: <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/search_label" android:searchSuggestAuthority="dictionary" android:searchSuggestIntentAction="android.intent.action.VIEW"> </searchable>
ANDROID, uma visão geral – Anderson Duarte de Amorim
256
No Android 1.6, que adicionou um novo atributo para os metadados conhecido como: android:includeInGlobalSearch. Especificando-a como "true" no seu searchable.xml, você permite que QSB a pegar sua pesquisa provedor de conteúdo e dê sugestão de inclusão (se o usuário permite que suas sugestões a partir das definições de pesquisa do sistema). Você
também
deve
especificar
um
valor
de
cadeia
para
android:searchSettingsDescription, que descreve aos usuários que tipo de sugestões o seu aplicativo fornece nas configurações do sistema para a pesquisa. <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/search_label" android:searchSettingsDescription="@string/settings_description" android:includeInGlobalSearch="true" android:searchSuggestAuthority="dictionary" android:searchSuggestIntentAction="android.intent.action.VIEW"> </searchable>
O mais importante e primeira coisa a notar é que quando um usuário instala um aplicativo com um provedor de sugestão que participam no CPR, esta nova aplicação não será ativado por padrão para o CPR. O usuário pode optar por ativar as fontes de sugestão especial, as configurações do sistema para a pesquisa (clicando em "Pesquisar" > "itens pesquisáveis" nas configurações). Você deve pensar em como lidar com isso em sua aplicação. Talvez mostrar um aviso de que instrui o usuário a visitar as configurações do sistema e permitir as sugestões do seu aplicativo. Quando o usuário habilita o item pesquisado, sugestões do aplicativo terão a chance de aparecer na QSB. Como sugestões de sua aplicação são escolhidas com mais freqüência, eles podem se mover para cima na lista.
ANDROID, uma visão geral – Anderson Duarte de Amorim
257
Um dos nossos objetivos com o CPR é torná-lo mais rápido para que os usuários acessem as coisas com mais freqüência. Uma maneira que fizemos isso é 'shortcutting' algumas das sugestões de pesquisa previamente escolhido, então eles serão mostrados imediatamente quando o usuário começa a digitar, ao invés de esperar para consultar os fornecedores de conteúdos. Sugestões de sua aplicação podem ser escolhidas como atalhos quando o usuário clica neles. Para sugestões de dinâmicas que podem querer alterar o seu conteúdo (ou se tornar inválido), no futuro, você pode fornecer um 'id atalho‟.
Sistema Alocação de memória Escrever eficazes aplicações móveis nem sempre é fácil. Em particular, aplicações Android dependem de gerenciamento automático de memória manipulado pelo coletor de lixo Dalvik, que por vezes pode causar problemas de desempenho se você não for cuidadoso com as alocações de memória. Em um caminho de código de desempenho sensíveis, tais como o método de layout ou desenho de uma vista ou o código da lógica de um jogo, qualquer atribuição tem um preço. Depois de muitas atribuições, o coletor de lixo vai começar e parar o aplicativo para deixá-lo liberar memória. Na maioria das vezes, as coletas de lixo acontecem rápidas o suficiente para você não perceber. No entanto, se uma coleção acontece enquanto você estiver percorrendo uma lista de itens ou enquanto você está tentando
ANDROID, uma visão geral – Anderson Duarte de Amorim
258
derrotar um inimigo em um jogo, você pode de repente ver uma queda no desempenho/ capacidade de resposta do aplicativo. Não é incomum para uma coleta de lixo levar de 100 a 200 ms. Para comparação, uma animação suave precisa desenhar cada quadro em 16 a 33 ms. Se a animação é subitamente interrompida por 10 quadros, você pode estar certo que os usuários irão notar. Na maioria das vezes, a coleta de lixo ocorre por causa de toneladas de objetos pequenos, de curta duração e alguns coletores de lixo, como catadores de lixo de gerações, que podem otimizar a coleta desses objetos para que o aplicativo não se interrompa com muita freqüência. O coletor de lixo Android infelizmente não é capaz de realizar tais otimizações e na criação de objetos de curta duração em caminhos de código crítico de desempenho é, portanto, muito caro para sua aplicação. Para ajudar a evitar freqüentes coletas de lixo, o SDK do Android é embarcado com uma ferramenta muito útil chamado allocation tracker. Esta ferramenta é parte do DDMS, que você já deve ter usado para fins de depuração. Para começar a usar o tracker de atribuição, primeiro você deve lançar a versão autônoma do DDMS, que pode ser encontrado na tools/ do SDK. A versão do DDMS incluído no Eclipse plugin não oferece capacidade de usar o tracker de atribuição ainda. Depois DDMS estar funcionando, basta selecionar o seu processo de candidatura e, em seguida, clique na guia Atribuição Tracker. Na nova visão, clique em Iniciar monitoramento e então usar o aplicativo para torná-lo executável para os caminhos de código que você deseja analisar. Quando estiver pronto, clique em Obter atribuições. Uma lista de objetos alocados será mostrada no primeiro quadro. Ao clicar em uma linha você pode ver, na segunda tabela, o rastreamento de pilha que levaram à atribuição. Não somente você saberá que tipo de objeto foi alocado, mas também em qual segmento, em que classe, em qual arquivo e em qual linha. A figura abaixo mostra as atribuições realizadas por prateleiras enquanto estiver rolando um ListView.
ANDROID, uma visão geral – Anderson Duarte de Amorim
259
O tracker de atribuição ajudará você a identificar questões importantes em seu código. Por exemplo, um erro comum que tenho visto em muitas aplicações é a criação de uma novo objeto Paint em cada sorteio. Mover a pintura em um campo de instância é uma solução simples que ajuda no desempenho de um lote. Eu altamente encorajo-vos a examinar o código fonte do Android para ver como podemos reduzir dotações em caminhos de código crítico de desempenho. Você também vai descobrir o Android, assim, oferecer APIs para ajudar você a reutilizar objetos.
Zipaling oferece uma otimização fácil O Android SDK inclui uma ferramenta chamada zipalign que otimiza a forma como um aplicativo é empacotado. Rodando Zipalign em seu aplicativo permite que o Android possa interagir de forma mais eficiente em tempo de execução e, portanto, tem o potencial para fazê-lo executar o sistema global mais rápido. No Android, arquivos de dados armazenados em APK de cada aplicativo são acessados por vários processos: o instalador lê o manifesto para lidar com as permissões associadas com o pedido; a aplicação inicial lê recursos para obter o nome do aplicativo
ANDROID, uma visão geral – Anderson Duarte de Amorim
260
e um ícone; o sistema servidor lê os recursos para uma variedade de razões (por exemplo, para exibir as notificações que aplicativo), e por último mas não menos importante, os arquivos de recurso são, obviamente, utilizado pelo próprio aplicativo. Os códigos de manipulação de recursos no Android podem acessar os recursos de forma eficiente quando estão alinhados em limites de 4 bytes de memória. Mas, para recursos que não estão alinhados (ou seja, quando zipalign não tenha sido executado em um APK), ele tem que cair de volta a expressamente e lê-los - o que é mais lento e consome memória adicional. Para um desenvolvedor de aplicativos, este mecanismo de retorno é muito conveniente. Ele oferece uma grande flexibilidade, permitindo o desenvolvimento de vários métodos diferentes, incluindo aqueles que não incluem a agregação de recursos como parte de seu fluxo normal. Infelizmente, para os usuários a situação é inversa - os recursos de leitura de APKs desalinhados são lentos e exigem muito da memória. No melhor dos casos, o único resultado visível é que tanto a aplicação inicial quanto o lançamento de aplicações desalinhadas são mais lentos do que deveria. No pior dos casos, a instalação de várias aplicações com recursos de memória não alinhadas aumentam a pressão, causando assim problemas para o sistema por ter que constantemente iniciar e matar processos. O usuário acaba com um dispositivo lento, com uma duração de bateria fraca. Felizmente, é muito fácil para o alinhar recursos em sua aplicação: Usando ADT: o
A ADT, plugin para o Eclipse (a partir da versão 0.9.3), alinhará automaticamente os pacotes de aplicativos libertados se a assistente de exportação é usado para criá-las.
Usando Ant: o
O script de construção Ant (a partir do Android 1.6) pode alinhar os pacotes de aplicativos. Metas para versões mais antigas da plataforma Android não são alinhados pelo script de construção Ant e precisam ser manualmente alinhados.
ANDROID, uma visão geral – Anderson Duarte de Amorim
261
o
A partir dos 1,6 Android SDK, Ant alinha pacotes de sinais automaticamente, quando está construindo o modo de depuração.
o
No modo de lançamento, Ant alinha as embalagens se tiver informação suficiente para assinar os pacotes, uma vez que o alinhamento deve acontecer após a assinatura. A fim de poder assinar os pacotes, e, portanto, para alinhá-los, Ant precisa saber a localização do armazenamento de chaves e o nome da chave na build.properties. O nome das propriedades são key.store e key.alias. Se as propriedades estiverem ausentes, o pacote de lançamento não será assinado e, portanto, não vai se alinhar também.
Manualmente: o
A fim de alinhar manualmente um pacote, zipalign está no tools/ do Android SDK 1.6 e posteriores. Você pode usá-lo para alinhar os pacotes de aplicativos voltados para qualquer versão do Android. Você deve executá-lo somente após a assinatura do arquivo APK, usando o seguinte comando: zipalign -v 4 source.apk destination.apk
Verificando o alinhamento: o
O
comando
a
seguir
verifica
se
um
pacote
está
alinhado:
zipalign -c -v 4 application.apk
ANDROID, uma visão geral – Anderson Duarte de Amorim
262
Nota Todo o conteúdo foi retirado de <http://android.com> e correlatos sendo disponibilizado através da licença Apache 2.0 e traduzido com auxílio de <http://translate.google.com.br> com posterior revisão.
Fontes About the Android Open Source Project. Disponível em: <http://source.android.com/about/index.html>. Acesso em: 06 abril 2011. Activities. Disponível em: <http://developer.android.com/guide/topics/fundamentals/activities.html>. Acesso em: 22 março 2011. Application Resources. Disponível em: <http://developer.android.com/guide/topics/resources/index.html>. Acesso em: 05 abril 2011. Applying Styles and Themes. Disponível em: <http://developer.android.com/guide/topics/ui/themes.html>. Acesso em: 04 abril 2011. Bound Services. Disponível em: <http://developer.android.com/guide/topics/fundamentals/bound-services.html>. Acesso em: 25 março 2011. Creating Dialogs. Disponível em: <http://developer.android.com/guide/topics/ui/dialogs.html>. Acesso em: 01 abril 2011. Creating Menus. Disponível em: <http://developer.android.com/guide/topics/ui/menus.html>. Acesso em: 30 março 2011. Creating Status Bar Notifications. Disponível em: <http://developer.android.com/guide/topics/ui/notifiers/notifications.html>. Acesso em: 04 abril 2011.
ANDROID, uma visão geral – Anderson Duarte de Amorim
263
Creating Toast Notifications. Disponível em: <http://developer.android.com/guide/topics/ui/notifiers/toasts.html>. Acesso em: 04 abril 2011. Data Backup. Disponível em: <http://developer.android.com/guide/topics/data/backup.html>. Acesso em: 06 abril 2011. Data Storage. Disponível em: <http://developer.android.com/guide/topics/data/data-storage.html>. Acesso em: 05 abril 2011. Declaring Layout. Disponível em: <http://developer.android.com/guide/topics/ui/declaring-layout.html>. Acesso em: 29 março 2011. Fragments. Disponível em: <http://developer.android.com/guide/topics/fundamentals/fragments.html>. Acesso em: 22 março 2011. Handling UI Events. Disponível em: <http://developer.android.com/guide/topics/ui/ui-events.html>. Acesso em: 04 abril 2011. How Android Draws Views. Disponível em: <http://developer.android.com/guide/topics/ui/how-android-draws.html>. Acesso em: 04 abril 2011. Loaders. Disponível em: <http://developer.android.com/guide/topics/fundamentals/loaders.html>. Acesso em: 23 março 2011. Notifying the User. Disponível em: <http://developer.android.com/guide/topics/ui/notifiers/index.html>. Acesso em: 04 abril 2011. Processes and Threads. Disponível em:
ANDROID, uma visão geral – Anderson Duarte de Amorim
264
<http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html>. Acesso em: 25 março 2011.
ANDROID, uma visão geral – Anderson Duarte de Amorim
265
Services. Disponível em: <http://developer.android.com/guide/topics/fundamentals/services.html>. Acesso em: 25 março 2011. Tasks and Back Stack. Disponível em: <http://developer.android.com/guide/topics/fundamentals/tasks-and-back-stack.html>. Acesso em: 24 março 2011. User Interface. Disponível em: <http://developer.android.com/guide/topics/ui/index.html>. Acesso em: 29 março 2011. Using the Action Bar. Disponível em: <http://developer.android.com/guide/topics/ui/actionbar.html>. Acesso em: 30 março 2011.
Fontes das imagens Androids. Disponível em: <http://www.android.com/media/wallpaper/gif/androids.gif>. Acesso em: 06 abril 2011. Untitled. Disponível em: <http://t3.gstatic.com/images?q=tbn:ANd9GcSknkUiTjwIBqS6dGp8k5IR2ihXI1ipyrols 9PHvLL2nq9GLVd0lg&t=1>. Acesso em: 04 abril 2011.
Fontes dos artigos Accessibility Service. Disponível em: <http://developer.android.com/resources/samples/AccessibilityService/index.html>. Acesso em: 18 março 2011. Drawable Mutations. Disponível em: <http://developer.android.com/resources/articles/drawable-mutations.html>. Acesso em: 18 março 2011. Gestures. Disponível em: <http://developer.android.com/resources/articles/gestures.html>. Acesso em: 18 março 2011.
ANDROID, uma visão geral – Anderson Duarte de Amorim
266
Layout Tricks: Creating Efficient Layouts. Disponível em: <http://developer.android.com/resources/articles/layout-tricks-efficiency.html>. Acesso em: 21 março 2011. Layout Tricks: Merging Layouts. Disponível em: <http://developer.android.com/resources/articles/layout-tricks-merge.html>. Acesso em: 21 março 2011. Layout Tricks: Using ViewStubs. Disponível em: <http://developer.android.com/resources/articles/layout-tricks-stubs.html>. Acesso em: 21 março 2011. Live Folders. Disponível em: <http http://developer.android.com/resources/articles/live-folders.html>. Acesso em: 21 março 2011. Live Wallpapers. Disponível em: <http://developer.android.com/resources/articles/live-wallpapers.html>. Acesso em: 21 março 2011. ListView Backgrounds: An Optimization. Disponível em: <http://developer.android.com/resources/articles/listview-backgrounds.html>.
Acesso
em: 21 março 2011. Touch Mode. Disponível em: <http://developer.android.com/resources/articles/touch-mode.html>. Acesso em: 21 março 2011. Using Text-to-Speech. Disponível em: <http://developer.android.com/resources/articles/tts.html>. Acesso em: 18 março 2011. Zipalign: an Easy Optimization. Disponível em: <http://developer.android.com/resources/articles/zipalign.html>. Acesso em: 21 março 2011.
ANDROID, uma visão geral – Anderson Duarte de Amorim
267