Caso prefira, você encontrará todo esse material, em inglês, no site do Developer Android. A tradução e comentários dos materiais eu faço livremente para ajudar a comunidade que fala português.

quarta-feira, 23 de março de 2011

0 comentários

Atividades - Gerenciando o ciclo de vida da atividade

Gerenciar o ciclo de vida de suas atividades implementando métodos callback é crucial para desenvolver uma aplicação forte e flexível. O ciclo de vida de uma atividade é diretamente afetada por suas associações com outras atividades, suas tarefas e seu back stack.

Uma atividade pode existir em três estados essencialmente:

Resumed
A atividade está sendo executada na tela e o usuário tem o foco. Esse estado também é referido como "rodando"

Paused
Outra atividade está sendo executada na tela e tem o foco, mas a atividade pausada ainda está visível. Ou seja, outra atividade está visível mas parcialmente transparente e não cobre a tela inteira. Uma atividade pausada está completamente viva (o objeto Activity continua na memória e seu estado é mantido juntamente com as informações e membros e mantêm-se anexada ao gerenciador de janelas) mas pode ser morto pelo sistema em situações de memória extremamente baixas.

Stopped
A atividade está completamente obscurescida por outra atividade (a atividade agora está em modo "background"). Uma atividade parada também está viva (o objeto Activity continua na memória e seu estado é mantido juntamente com as informações e membros e NÃO mantêm-se anexada ao gerenciador de janelas). Contudo, ela não está mais visível ao usuário e pode ser morta pelo sistema caso memória seja requerida em outro processo.

Mas tudo isso já foi coberto no post do Ciclo de Vida da aplicação.

Implementando os callbacks do ciclo de vida

Quando uma transação de atividade entre em um dos estados descritos acima, ele é notificado através de vários métodos de callback. Todos os callbacks podem ser sobrescritos para fazer o trabalho apropriado quando seu estado de atividade muda. O esqueleto de atividades abaixo inclui cada um dos métodos fundamentais do ciclo de vida:

public class ExampleActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // A atividade está sendo criada
    }
    @Override
    protected void onStart() {
        super.onStart();
        // A atividade está para se tornar visível
    }
    @Override
    protected void onResume() {
        super.onResume();
        // A atividade se tornou visível (está em modo "resumed").
    }
    @Override
    protected void onPause() {
        super.onPause();
        // Outra atividade está ganhando o foco (está em modo "paused").
    }
    @Override
    protected void onStop() {
        super.onStop();
        //  A atividade não está mais visível (está "stopped")
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // A atividade está para ser destruída.
    }
}
Mais sobre os métodos em Ciclo de Vida da aplicação. Não deixe de ir lá pois há informações realmente válidas que ajudarão no entendimento desse tópico.

Salvando o estado da atividade


A introdução ao ciclo de vida da atividade rapidamente menciona que quando uma atividade está pausada ou parada, o estado da atividade é guardado. Isso é verdade pois o objeto de Activity é guardado na memória quando está pausado ou parado - todas as informações sobre seus membros e estado atual são mantidos vivos. Por isso, quaisquer alterações feitas pelo usuário dentro da atividade são mantidos em memória para que quando a atividade retorne para a tela (quando estiver em modo "resumed"), essas mudanças ainda estejam por lá.

Contudo, quando o sistema destroi uma atividade para recuperar memória, o objeto de Activity é destruído, então o sistema não pode simplesmente retornar ao estado anterior de maneira intacta. Ao invés disso, o sistema deve recriar o objeto se o usuário navegar de volta a ele. Ainda mais, o usuário não tem qualquer conhecimento de que o sistema destruiu a atividade e recriou-a e, na verdade, ele espera que a atividade esteja exatamente como estava anteriormente. Nessa situação, você pode assegurar que informações importantes sobre o estado de atividade estejam preservado implementando um método callback adicional que permite que você salve a informação sobre o estado da atividade e depois restaure-a quando o sistema precisar recriá-la.

O método callback que pode salvar informações sobre o estado atual de uma atividade é o onSaveInstanceState(). O sistema chama esse método antes de fazer com que a atividade esteja apta a ser destruída e passa esses dados para um objeto Bundle. O Bundle é onde você pode guardar informações de estado sobre a atividade como pares nome-valor, usando métodos como putString(). Então, se o sistema matar o processo de sua atividade e o usuário navegar de volta a ela, o sistema passa o Bundle para onCreate() para que quaisquer informações da atividade guardadas durante o uso de onSaveInstanceState() possam ser restauradas. Se não há nenhuma informação a ser restaurada, então o Bundle passado para o onCreate() é nulo (null).

Nota: Não há garantia que o onSaveInstanceState() seja chamado antes da atividade ser destruída, já que existem casos nos quais não será necessário salvar o estado (como quando um usuário deixa a atividade usando o botão de BACK em uma situação onde o usuário está explicitamente fechando a atividade). Se o método é chamado, ele deve ser chamado sempre antes de onStop() e possivelmente antes de onPause().

Contudo, mesmo quando você não faz nada ou não implementa o onSaveInstanceState(), algum dos estados de atividade são restaurados pela implementação padrão do onSaveInstanceState(). Especificamente há uma implementação padrão de onSaveInstanceState() para cada View do seu layout, que permite a cada View prover informações sobre si mesmo que devem ser salvas. Quase todo widget no framework Android implementa esse método como apropriado, tal como mudanças visíveis na interface que são automaticamente salvas e restauradas quando sua atividade é recriada. Por exemplo, o widget EditText salva qualquer texto que o usuário digitou dentro deste e o CheckBox salva a condição de estar ou não marcado. O único trabalho que você precisa ter é o de prover um ID único (com o atributo android:id) para cada widget que você quer ter o estado salvo. Se um widget não tem um ID, ele não terá seu estado salvo.

Apesar da implementação padrão de onSaveInstanceState() salvar dados úteis sobre a interface de uma atividade, você possivelmente ainda terá de sobrescrevê-lo para salvar informações adicionais. Por exemplo, você pode querer salvar valores que foram mudados durante o ciclo de vida da atividade.

Como a implementação padrão de onSaveInstanceState() ajuda a salvar o estado da interface, se você fizer a sobrescrita do método para salvar informações adicionais, você deve sempre chamar a implementação da supreclasse de onSaveInstanceState() antes de fazer qualquer trabalho adicional.

Nota: Como onSaveInstanceState() não é garantido de ser chamado, você deve usá-lo apenas para guardar o estado transiente da atividade - você NÃO deve usá-lo para guardar dados que precisam ser persistidos. Ao invés disso, você deve usar o onPause() para guardar dados que precisam ser persistidos (como dados que devem ser guardados em banco de dados).


Uma boa forma de testar a habilidade da aplicação para restaurar seu estado é simplesmente girar o seu dispositivo para que a orientação da tela mude. Quando a orientação muda, o sistema destrói e recria a atividade para que possa aplicar recursos alternativos que podem estar disponíveis na nova orientação. Por essa razão, é MUITO importante que sua atividade restaure completamente seu estado quando recriada, já que o usuário regularmente rotaciona a tela enquanto usa as aplicações.

segunda-feira, 21 de março de 2011

0 comentários

Atividades

Uma atividade é um componente de aplicação que provê uma tela onde o usuário poderá interagir para fazer alguma coisa como discar um número, bater uma foto, enviar um email ou ver um mapa. A cada atividade é dada uma janela na qual é desenhada a interface de usuário. A janela normalmente preenche toda a tela mas pode ser menor que a tela e flutuar sobre outras janelas.

Uma aplicação usualmente consiste de multiplas atividades que são pouco relacionadas a outras. Tipicamente, uma atividade é uma especificada como a principal (main), que é apresentada ao usuário quando ele chama a aplicação pela primeira vez. Cada atividade pode começar outra atividade para realizar diferentes ações. A cada vez que uma nova atividade se inicia, a atividade anterior é parada, mas o sistema preserva a atividade numa pilha (a chamada "back stack"). Quando uma nova atividade se inicia, ela é colocada na back stack e ganha o foco. O back stack usa o conceito de fila "last in, first out", então quando o usuário terminou a operação e pressiona o botão BACK, a atividade corrente é retirada do topo da pilha (e destruída) e a atividade anterior reinicia.

Quando uma atividade é parada em razão de outra atividade se iniciando, ela é notificada da mudança do seu estado através dos métodos de ciclo de vida da atividade. Existem diversos outros métodos de callback que a atividade pode receber, de acordo com a mudança de seu estado - seja quando o sistema esteja criando a aplicação ou parando-a, reiniciando-a ou a destruindo - e cada callback dá a você a oportunidade de realizar trabalhos específicos que são apropriados para a mudança de estado. Por exemplo, quando parado, a atividade deve liberar objetos de recursos como conexões de rede e bancos de dados. Quando uma atividade se reinicia, você pode capturar os recursos necessários e continuar a ação que foi interrompida. Essas transições de estado são parte do ciclo de vida da atividade.

Criando uma atividade

Para criar uma atividade, você deve criar uma subclasse de Activity (ou de uma subclasse existente). Na sua subclasse, você precisará implementar métodos callback que o sistema chama quando a atividade faz a transição entre os vários estados de seu ciclo de vida, como quando a atividade é criada, parada, reiniciada ou destruída. Os dois métodos callback mais importantes são:

onCreate()
Você deve implementar esse método. O sistema chama esse método quando está criando a atividade. Dentro de sua implementação, você deve inicializar os componentes essenciais para sua atividade. Mais importante ainda, é aqui que você vai chamar setContentView() para definir qual layout será a interface inicial de sua atividade (i.e. aplicação).

onPause()
O sistema chama esse método como a primeira indicação que o usuário está deixando sua atividade (apesar disso não significar que a atividade está sendo destruída). É aqui que você usualmente deverá fazer o commit para quaisquer mudanças que devam ser persistidas.

Existem outros métodos callback dentro do ciclo de vida e que você deverá usar para provêr ao usuário uma experiência fluída entre as atividades e gerenciar interrupções inexperadas que causar a parada ou destruíção de sua atividade. Os métodos de callback foram discutidos em post anterior.

Implementando a interface de usuário

A interface de usuário para uma atividade é provida por uma hierarquia de views - objetos derivados da classe View. Cada view controla um espaço retangular particular dentro da janela onde a atividade está presente e pode responder a interações do usuário. Por exemplo, uma view pode ser um botão que inicia uma ação quando o usuário toca nele.

O Android provê um número de views já prontas que você poderá usar para criar o seu design e organizar o seu layout. Mas isso você já sabe, já que falamos extensivamente de interface. Essas views são os widgets, que provêem elementos visuais e os layouts, que são views derivadas das ViewGroups.

Como sabem também, a forma mais comum de definir layouts é através de arquivos XML salvos como recursos da aplicação.

Declarando a atividade no manifesto

Você deve declarar sua atividade no arquivo de manifesto para que ele possa ser acessível no sistema. Para declarar sua atividade, abra o arquivo de manifesto e adicione um elemento de <activity> como filho do elemento <application>. Por exemplo:

<manifest ... >
  <application ... >
      <activity android:name=".ExampleActivity" />
      ...
  </application ... >
  ...
</manifest >

Existem diversos outros atributos que você pode incluir nesse elemento para definir as propriedades como label para uma atividade, um ícone para uma atividade ou mesmo um tema de estilos para a interface da atividade.

Usando intent filters (filtros de intenção)

Um elemento <activity> pode também especificar vários intent filters - usando o elemento <intent-filter> - para declarar como outros componentes de aplicação devem ser ativados.

Quando você cria uma nova aplicação usando as ferramentas SDK do Android, a atividade básica criada para você automaticamente inclui um intent filter que declara como a atividade deve responder à ação principal (main) e que deveria ser colocada na categoria de launcher. O intent filter se parece como abaixo:

<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 ele é o ponto principal e inicial da aplicação. O elemento <category> especifica que essa atividade deve ser listada na aplicação launcher do sistema (para permitir aos usuários lançar a atividade).

Se você tem a intenção que sua aplicação seja, por assim dizer, privada e que não permita que outras aplicações ativem uma de suas atividades, então você não precisará de nenhum outro intent filter. Apenas uma atividade deve ser a ação principal e estar na categoria launcher, como mostrado no código acima. Atividades que você não quer que sejam disponíveis para outras aplicações não devem ter nenhum intent filter e você pode inicia-las você mesmo usando explicit intents (que serão discutidos em breve).

Contudo, se você quer que sua atividade responda a implicit intents que são entregues a partir de outras aplicações (e suas próprias), então você deve definir intent filters adicionais para sua atividade. Praa cada tipo de intent ao qual você quer responder, você deve incluir o elemento <intent-filter> que inclui um elemento <action> e, opcionalmente, um elemento <category> e/ou elemento <data>. Esses elementos especificam o tipo de intent ao qual você quer que sua atividade possa responder.

Falaremos sobre intents em breve, num post só para ele.

Iniciando uma atividade

Você pode começar uma outra atividade chamando startActivity(), passando para ele um Intent que descreve a atividade que você quer iniciar. O intent especifica ou a atividade exata que você quer iniciar ou descreve o tipo de ação que você quer executar (e o sistema seleciona a atividade apropriada para você, que pode inclusive ser uma aplicação diferente). Um intent pode também trazer consigo pequenas quantidades de dados a serem usados pela atividade que está sendo iniciada.

Quando trabalhando na sua própria aplicação, você também vai normalmente precisar lançar uma atividade conhecida. Você pode fazê-lo criando um intent que explicitamente define a aplicação que você quer iniciar, usando o nome de classe. Por exemplo, aqui está como uma atividade inicia outra atividade chamada SignInActivity:
Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

Contudo, sua aplicação deve também querer executar alguma ação como enviar um email, mensagem de texto ou fazer o update no status ou mesmo usar dados de sua atividade. Nesse caso, sua aplicação pode não ter, dentro de suas atividades, uma que realize a ação esperada. Então, ao invés de criar uma atividade apenas para aquela ação, você poderá usar outras atividades providas por outras aplicações dentro do seu dispositivo. É aqui que os intents são realmente valiosos - você pode criar um intent que descreve uma ação que você quer realizar e o sistema lança a atividade apropriada a partir de outra aplicação.

Se existem multiplas atividades que podem realizar o intent, então o usuário poderá selecionar qual quer usar. É como quando você instala um app que realiza a tarefa de mostrar uma galeria de imagens. Caso você tenha dois ou mais apps que fazem a mesma função, ao tentar abrir uma imagem a partir de um app que não tenha essa funcionalidade, o Android perguntará na tela com qual app ele deverá realizar a tarefa. No exemplo abaixo, se você quer permitir que o usuário envie uma mensagem de email, você pode criar o intent seguinte:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
O EXTRA_EMAIL extra adicionado no intent é uma array de string de endereços de email aos quais o email pode ser enviado. Quando uma aplicação de email responde a esse intent, ele lê a array de string provida e coloca no campo "para" do formulário de composição do email. Nessa situação, a aplicação de email inicia e quando o usuário tiver terminado de enviar o email, a atividade anterior reinicia.

Iniciando uma atividade para um resultado

Algumas vezes, você pode querer receber um resultado a partir de uma atividade que você iniciou. Nesse caso, inicie a atividade chamando startActivityForResult() (ao invés de startActivity()). Para então receber o resultado de uma atividade subsequente, implemente o método callback onActivityResult(). Quando a atividade subsequente estiver finalizada, ele retorna um resultado para um Intent dentro do seu onActivityResult().

Por exemplo, talvez você queira que o usuário selecione um dos seus contatos para que a atividade possa fazer alguma coisa com a informação que está em seu contato. Aqui abaixo está como você pode criar tal intent e como manusear o resultado:

private void pickContact() {
    // Cria um intent para "pegar" um contato como definido pelo 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) {
    // Se a requisição foi bem sucedida (OK) e a requisição foi PICK_CONTACT_REQUEST
    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
        // Faz um query no content provider dos contatos para recuperar o nome do contato
        Cursor cursor = getContentResolver().query(data.getData(),
        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
        if (cursor.moveToFirst()) { // Verdadeiro se o cursor não for vazio
            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
            String name = cursor.getString(columnIndex);
            // Faz algo com o nome do contato selecionado...
        }
    }
}
Esse exemplo mostra a lógica básica que você deverá usar em seu método onActivityResult() para que possamos manusear corretamente o resultado da atividade. A primeira condição checa se a requisição foi bem sucedida - e se foi, então o resultCode retornará RESULT_OK - e se a requisição para a qual o resultado está sendo retornado é conhecido - nesse caso, o requestCode bate com o segundo parâmetro enviado para startActivityForResult(). A partir dai, o código manuseia o resultado da atividade fazendo a leitura do dado retornado em um intent (a partir do parâmetro data).

O que acontece é que o ContentResolver realiza uma query no content provider que retorna um Cursor que permite que os dados retornados possam ser lidos. Mas vamos falar de content provider com mais propriedade também em um post só para ele.

Encerrando uma atividade

Você pode encerrar uma atividade chamando o método finish(). Você também pode encerrar uma atividade separada que você iniciou anteriormente chamando finishActivity().

Nota: Em muitos casos, você não deve explicitamente finalizar uma atividade usando esses métodos. O ideal é deixar o sistema Android gerenciar a vida da atividade para você. Chamar esses métodos podem afetar negativamente a experiência do usuário e deverá apenas ser usando quando absolutamente necessário.

Interface de Usuários - 100%. E agora?

Ok. A interface eu já sei como fazer? E o restante? Como juntar isso tudo? Vamos então falar, a partir de agora, de atividades. Sei que pode parecer que estou me repetindo. Afinal, já falei disso anteriormente. Só que eu falei rapidamente e em apenas um único post (Ciclo de Vida de uma aplicação)

Voltarei com uma abordagem melhor e mais específica. E, com os conhecimentos que já temos a respeito da interface, tenho certeza que ficará mais fácil para todos entender o que são as atividades dentro do contexto da interface do usuário.

Volto em breve.

domingo, 20 de março de 2011

2 comentários

Interface de Usuários - Fazendo o bind de dados com AdapterView

Pessoal, desculpe pelo meu sumiço. Eu achava que estava bem e resolvi que podia fazer tudo como se estivesse 100%. Não estava e tive uma recaída. Com dengue não se brinca. Ela se parece com uma gripe comum (mais forte, no entanto) mas não é. Se tiverem a infelicidade de pegar dengue, fiquem de repouso mesmo e não vacilem como eu, ok?

--

O AdapterView é uma subclasse do tipo ViewGroup cujas View filhas são determinadas por um Adapter que faz o bind de dados de algum repositório. O AdapterView é útil onde quer que você precise mostrar dados que estão guardados em bancos de dados dentro da sua interface.

Gallery, ListView e Spinner são exemplos de subclasses AdapterView que você pode usar para fazer o bind a partir de dados.

Os objetos AdapterView têm duas responsabilidades principais:

  • Preencher o layout com dados
  • Gerenciar as seleções de usuário
Preencher o layout com dados

Inserir dados em um layout é tipicamente alcançado fazendo o binding de uma classe AdapterView a um Adapter, que retorna dados de uma fonte externa (talvez uma lista de fornecedores ou o resultado de uma query a partir do banco de dados do dispositivo).

O código abaixo faz o seguinte:
  1. Cria um Spinner com uma View existente e faz o bind para um novo ArrayAdapter que lê uma array de cores do recurso local.
  2. Cria um outro Spinner a partir de uma View e faz o bind para um novo SimpleCursosAdapter que vai ler os nomes das pessoas dos contatos do dispositivo (ver depois Contacts.People).
// Pega o Spinner e faz o bind para um ArrayAdapter
// que referencia uma array de String

Spinner s1 = (Spinner) findViewById(R.id.spinner1);
ArrayAdapter adapter = ArrayAdapter.createFromResource(
    this, R.array.colors, android.R.layout.simple_spinner_item);
 
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
s1.setAdapter(adapter);
 
// Carrega um Spinner e faz o bind para uma query
private static String[] PROJECTION = new String[] {
        People._ID, People.NAME
    };
 
Spinner s2 = (Spinner) findViewById(R.id.spinner2);
Cursor cur = managedQuery(People.CONTENT_URI, PROJECTION, null, null);
   
SimpleCursorAdapter adapter2 = new SimpleCursorAdapter(this,
    android.R.layout.simple_spinner_item, // Use um template
                                                                   // que mostra um
                                                                   // text view

    cur, // Dá ao cursos ao list adapter
    new String[] {People.NAME}, // Mapeia a coluna NAME do banco
    new int[] {android.R.id.text1}); // A view "text1" definida no XML
                                       
adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
s2.setAdapter(adapter2);
Note que é necessário ter uma coluna People._ID em projeção usada no CursorAdapter ou você terá uma exceção.

Se, durante o curso da vida da aplicação, você mudar os dados que foi lido pelo seu Adapter, você deverá chamar notifyDataSetChanged(). Isso vai notificar as Views anexadas que os dados foram modificados e que elas deverão se atualizar.

Gerenciar as seleções de usuário

Você gerencia a seleção do usuário setando o membro AdapterView.OnItemClickListener para um listener e capturando as mudanças de seleção.
// Cria uma mensagem de handle para uma classe anônima
private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
    public void onItemClick(AdapterView parent, View v, int position, long id)
    {
        // Mostra uma messageBox.
        Toast.makeText(mContext,"You've got an event",Toast.LENGTH_SHORT).show();
    }
};
// Agora pega o seu objeto e seta o membro onItemClickListener
// para que nossa classe possa gerenciar o objeto
mHistoryView = (ListView)findViewById(R.id.history);
mHistoryView.setOnItemClickListener(mMessageClickedHandler); 

quarta-feira, 16 de março de 2011

0 comentários

Interface de Usuários - Controles Compostos e como modificar um View Type existente

Se você não quer criar um componente completamente customizado mas está pensando em criar um componente reusável que consistiria em um grupo de componentes já existentes, então criar um controle composto é a solução. De maneira simples, isso consiste em colocar juntos controles (ou Views) dentro de um grupo lógico de itens que podem ser tratados como uma coisa única. Por exemplo, um combobox pode ser pensado como uma combinação de uma linha única de EditText com um botão adjacente com um PopupList anexado a ele. Se você pressionar o botão e selecionar alguma coisa da lista, ele popula o campo EditText mas o usuário também pode escrever algo diretamente no EditText se ele preferir.

No Android existem, na verdade, duas outras Views que estão disponíveis para fazer a função do combobox: Spinner a AutoCompleteTextView. Mesmo assim, o exemplo do Combobox é um exemplo fácil de se entender.

Para criar um componente composto:
  1. O ponto inicial usual é um layout de algum tipo, então crie uma classe que extenda um layout. Talvez no caso de um Combobox seja melhor usar um layout do tipo LinearLayout com orientação horizontal. Lembre-se que outros layouts podem ser aninhados e dessa maneira o componente composto pode ser complexo e estruturado. Note que assim como toda atividade, você pode usar ou a abordagem declaratica (usando um arquivo XML) ou então você pode aninhar os componentes programaticamente.
  2. No construtor da sua nova classe receba quaisquer parâmetros que a superclasse espera e passe-as para o construtor da superclasse primeiramente. Só então você poderá inserir outras Views a serem usadas no seu novo componente; ou seja, é aqui que você deve criar seu EditText e PopupList. Note que você também pode inserir seus próprios atributos e parâmetros dentro do seu XML que pode ser usado no seu construtor.
  3. Crie os listeners para os eventos que as Views que estão dentro de seu Layout devem requerer como, por exemplo, um método listener para um Click do List Item para fazer o update do conteúdo do EditText se uma seleção de lista é feita.
  4. Crie suas próprias propriedades com acessores e modificadores, por exemplo, para permitir que o valor do EditText seja inicialmente preenchido com um valor que desejar.
  5. No acso de estar extendendo um layout, você não precisa fazer o override dos métodos onDraw() ou do onMeasure() já que o layout terá os comportamentos padrões que vão funcionar. Contudo, você pode fazer o override se achar que deve.
  6. Faça o override em métodos on... que achar necessário, como onKeyDown.
Para sumarizar, o uso de um layout como base para um controle customizável tem vantagens, incluindo:
  • Você pode especificar o layout usando um arquivo XML declarativo assim como com numa tela de atividade ou você pode criar as Views programaticamenet e aninhá-los dentro do layout a partir de seu código.
  • Os métodos onDraw() e onMeasure() vão, na maioria dos casos, ter um comportamento já descrito e você talvez não precise fazer o override deles.
  • No final das contas, você poderá construir controles compostos complexos rapidamente e reutilizá-los como se eles fossem componentes simples.
Modificando um View Type existente

Existe, ainda, uma maneira ainda mais simples de se criar um componente customizado em certas circunstâncias. Se existe um componente que já tem um comportamento similar ao que você deseja mas você quer apenas extender algumas características, você pode simplesmente fazer o override de algum comportamento e pronto. 

Interface de Usuários - Construindo Componentes Customizados

O Android oferece um sofisticado e poderoso modelo componentizado para construir sua interface de usuário, baseado nas classes fundamentais de layout: View e ViewGroup. Para começar, a plataforma inclui uma variedade de Views e ViewGroup chamadas widgets e layouts, respectivamente, que você pode usar para construir sua interface.

Uma lista parcial dos widgets disponíveis incluem Button, TextView, EditText, ListView, CheckBox, RadioButton, Gallery, Spinner e alguns com propostas mais específicas, como AutoCompleteTextView, ImageSwitcher e TextSwitcher.

Entre os layouts disponíveis estão o LinearLayout, FrameLayout, RelativeLayout e outros. Para outros exemplos, veja Common Layout Objects.

Se nenhum dos widgets padrão ou layouts atendem suas necessidades, você pode criar sua própria subclasse View. Você precisará fazer apenas pequenos ajustes no widget ou layout e você pode simplesmente criar uma subclasse do widget ou layout e fazer o override de seus métodos.

Criar sua própria subclasse View dá o controle preciso sobre a aparência e função de um elemento de tela. Para dar uma idéia do controle que você tem com uma view customizada, aqui vão alguns dos exemplos do que você pode fazer com eles:
  • Você pode criar um tipo View completamente customizado como, por exemplo, um controle de volume renderizado usando gráficos 2D que se assemelhe ao controle analógico real.
  • Você pode combinar um grupo de componentes View em um único componente, talvez para fazer algo como uma combobox (uma combinação de lista em popup e um campo text), um controle dual-pane (com paineis do lado esquerdo e direito onde você pode assinalar de qual lista um item faz parte) e assim por diante.
  • Você pode fazer o override na forma como um EditText é renderizado na tela.
  • Você pode capturar outros eventos como keypress e retornar alguma interação de acordo com a maneira como desejar (como em um jogo).

Abordagem básica

Aqui está um overview do que é necessário para começar a criar seus próprios componentes View customizados:
  1. Extenda uma classe View existente ou subclasse em sua própria classe.
  2. Faça o override de alguns dos métodos da superclasse. Os métodos da superclasse a serem feitos override começam com 'on'. Por exemplo, onDraw(), onMeasure(), on KeyDown(). 
  3. Use sua nova classe de extensão. Uma vez completada, a nova classe pode ser usada no lugar da classe na qual ela é baseada.
Dica: Classes de extensão podem ser definidas como classes internas às atividades que as usam. Isso é particularmente útil já que o controle de acesso à classe é feito automaticamente.

Componentes Customizados Completos

Componentes customizados completos podem ser usados para criar componentes gráficos da maneira como desejar. Talvez um medidor gráfico que se pareça com um medidor analógico ou qualquer outra coisa que você tenha em mente. De qualquer forma, você vai querer algo que os componentes padrão ainda não façam, não importa a maneira como você os combine.

Felizmente, você pode facilmente criar componentes que tem o visual e se comportam da maneira que você quer limitado, talvez, apenas pela sua imaginação, o tamanho da tela e o poder de processamento disponível (lembre-se que sua aplicação vai rodar em um dispositivo que terá muito menos poder de processamento que seu desktop onde está criando o app).

Para criar um componente customizado completo:
  1. A View mais genérica da qual você pode extender é, sem surpresa nenhuma, a View. Então, você vai começar, normalmente, extendendo seu código a partir dessa classe e criar seu novo super componente a partir dela.
  2. Você pode suprir um construtor que poderá receber atributos e parâmetros a partir de um arquivo XML.
  3. Você vai querer, provavelmente, criar seus próprios event listeners, property accessors e modifiers e possivelmente comportamentos mais sofisticados dentro da sua classe de componente.
  4. Você vai certamente querer fazer o override do método onMeasure() e também de onDraw() se você quiser que seu componente mostre alguma coisa. Mesmo tendo comportamentos padrão, o onDraw() padrão não faz nada e o onMeasure() padrão vai sempre colocar o tamanho como 100x100.
  5. Outros métodos on... deverão ser overriden.
Extendendo onDraw() e onMeasure()

O método onDraw() cria um Canvas sobre o qual você pode implementar qualquer coisa que queira: gráficos 2D, outros componentes padrão ou customizados, texto estilizado ou qualquer coisa que você possa imaginar. Imagine um pintor que vai criar sua obra. Ele pega as tintas e a tela onde vai pintar. Canvas é a tela onde você poderá criar o que imaginar. Em tecnologia, o termo canvas significa isso: o local onde você poderá criar o que deseja.

Nota: O canvas não se aplica a gráficos 3D. Se quiser usar gráficos dessa natureza, você deve extender SurfaceView ao invés da View, e desenhar o gráfico em uma thread separada. Você pode checar por GLSurfaceViewActivity para mais detalhes.

Já o método onMeasure() é um pouco mais complexo. onMeasure() é uma peça critica para o contrato de renderização do seu componente e o seu container. onMeasure() deve ser overriden para eficientemente e corretamente reportar as medidas de seu container para que a renderização seja perfeita na tela. O onMeasure() foi feito para ser mais complexo por conta dos requerimentos de limites dos componentes pais onde eles estão posicionados e pelo requerimento de chamar o método setMeasuredDimension() com a medida em largura e altura que foi calculada. Se você falhar em chamar esse método de um onMeasure() que foi overriden, o resultado é que uma exceção será chamada.

Implementar o onMeasure() seria algo assim:
  1. O método onMeasure() que foi overriden é chamado com as especificações de largura e altura (parâmetros widthMeasureSpec e heightMeasureSpec, ambos com valores em inteiro representando as dimensões do componente pai onde ele está sendo criado) que deve ser tratado como um requerimento para as restrições de largura e altura a serem produzidas. 
  2. O método onMeasure() do componente calcula as medidas de largura e altura necessárias para renderizar o componente. Ele vai tentar se manter dentro das especificações enviadas.
  3. Uma vez que a largura e altura foram calculadas, o método setMeasuredDimension(int width, int height) deve ser chamado com as medidas calculadas. Se houver uma falha em conseguir um resultado e uma exceção será lançada.
Aqui está um sumário de alguns dos outros métodos padrões que a framework chama nas views:

CategoryMethodsDescription
CreationConstrutoresExiste uma forma do construtor que é chamado quando a View é criada no código ou quando a View é inflada a partir de um arquivo de layout. A segunda forma passa quaisquer atributos definidos no arquivo de layout.
onFinishInflate()Chamado após a view e todos os itens filhos serem inflados da XML.
LayoutonMeasure(int, int)Chamado para determinar os requerimentos de tamanho para  a View e todos os seus filhos.
onLayout(boolean, int, int, int, int)Chamado quando a View deve assinalar o tamanho e posição de todos os seus filhos.
onSizeChanged(int, int, int, int)Chamado quando o tamanho da View foi mudado.
DrawingonDraw(Canvas)Chamado quando uma View deve renderizar seu conteúdo.
Event processingonKeyDown(int, KeyEvent)Chamado quando um novo evento de tecla ocorre.
onKeyUp(int, KeyEvent)Chamado quando um novo evento key up ocorre.
onTrackballEvent(MotionEvent)Chamado quando um evento de movimentação do trackball ocorre.
onTouchEvent(MotionEvent)Chamado quando um evento touch screen ocorre.
FocusonFocusChanged(boolean, int, Rect)Chamado quando uma View ganha ou perde o foco.
onWindowFocusChanged(boolean)Chamado quando uma janela contendo views ganha ou perde foco.
AttachingonAttachedToWindow()Chamado quando uma View é anexada a uma janela.
onDetachedFromWindow()Chamado quando uma View é desanexada de uma janela.
onWindowVisibilityChanged(int)Chamado quando a visibiliadde da janela contendo a View é modificada.
Na página de Demonstração de APIs existem vários exemplos de componentes customizados. Dê uma olhada lá para ver como é o código.

No próximo post, Controles Compostos e como modificar um View Type existente 

Interface de Usuários - Aplicando estilos e Temas na interface

É isso ai pessoal, mesmo com dengue, cá estou eu não deixando-os na mão. Pelo menos a febre baixou, mas o corpo ainda está todo dolorido. Nada que me impedisse de vir aqui escrever mais esse post.

-----

Existem duas maneiras de se aplicar um estilo:

  • Aplicando o estilo a uma View individual, onde você adiciona o atributo style no elemento View dentro da XML responsável pelo seu layout ou;
  • Aplicando um estilo para uma atividade ou aplicação, onde você adiciona o atributo android:theme em <activity> ou <application> dentro do manifesto do Android.
Quando você aplica um estilo a uma View em particular dentro do layout, as propriedades definidas pelo estilo são aplicadas apenas aquela View. Se um estilo é aplicado a um ViewGroup, os elementos View filhos NÃO vão herdar as propriedades de estilo - apenas o elemento no qual é aplicado diretamente o estilo terá as propriedades mostradas. Contudo, se você quer que um estilo se aplique a todos os elementos View, você terá de usar um tema.

Para aplicar uma definição de estilo como um tema, você deve aplicá-lo à atividade ou aplicação dentro do manifesto do Android. Quando você faz dessa maneira, cada View dentro da atividade ou aplicação vai aplicar as propriedades que suportadas. Por exemplo, se você aplicar o estilo CodeFont do post anterior a uma atividade, então todas os elementos View que suportam o estilo de texto descrito dentro do arquivo de estilo vão aplicá-lo. Quaisquer Views que não suportem alguma das propriedades não a terão aplicada. Se uma View suporte apenas uma das propriedades, apenas essa propriedade será aplicada.

Aplicando um estilo a uma View

Aqui está como aplicar um estilo para uma View no layout XML:
<TextView
    style="@style/CodeFont"
    android:text="@string/hello" />
Agora esse TextView terá um estilo definido pelo estilo de nome CodeFont.

Nota: O atributo style NÃO usa o prefixo android: como visto em android:text.

Aplicando um tema a uma atividade ou aplicação

Para aplicar um tema para todas as atividades de sua aplicação, abra o arquivo AndroidManifest.xml e edite a tag <application> inserindo o atributo android:theme com o nome do estilo. Por exemplo:
<application android:theme="@style/CustomTheme">
Se você quer um tema aplicado apenas a uma atividade de sua aplicação, então adicione o atributo android:theme à tag <activity>.

Assim como o Android provê outros recursos já built-in (ou, de fábrica, por assim dizer), existem outros temas pré-definidos que você pode usar para evitar escrever tudo você mesmo. Por exemplo, você pode usar o tema Dialog e fazer com que uma atividade fique com a cara de uma caixa de diálogo:
<activity android:theme="@android:style/Theme.Dialog">
Ou, se você pode querer que o fundo seja transparente, você pode usar o tema Translucent:
<activity android:theme="@android:style/Theme.Translucent">
Se você gostou de algum tema, você ainda pode modificá-lo e, por que não, melhorá-lo. Para tal, adicione o tema como parent de seu tema customizado. Por exemplo, você pode modificar o tema e inserir suas próprias cores:
<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 suprida como um recurso separado aqui pois o atributo android:windowBackground apenas suporta a referência para outro recurso, diferente de android:colorBackground, que a ele pode ser dado um literal representando uma cor.

Agora, para usar o tema customizado ao invés de Theme.Light dentro do manifesto Android, basta fazer isso:
<activity android:theme="@style/CustomTheme">
Selecione o tema baseado na versão de plataforma

Novas versões do Android tem temas adicionais aplicáveis às aplicações e você pode querer usá-los quando estiver rodando suas aplicações nessas plataformas enquanto elas se mantém, mesmo assim, compatíveis com versões anteriores. Você pode conseguir isso através de um tema customizado que usa uma seleção de recursos para definir diferentes temas pais, baseados na versão da plataforma.

Por exemplo, aqui está uma declaração de um tema customizável que é simplesmente a versão padrão Theme.Light que se encontraria dentro de um arquivo XML na pasta res/values (tipicamente res/values/styles.xml):
<style name="LightThemeSelector" parent="android:Theme.Light">    ...</style>
Para que esse tema use o tema holográfico do Android 3.0 (API 11), você pode colocar uma declaração alternativa para o tema dentro de um arquivo XML em res/values-v11, mudando apenas o valor de parent:
<style name="LightThemeSelector" parent="android:Theme.Holo.Light">    ...</style>
Assim, ele saberá qual tema pai usar baseado na versão da API. Caso seja a API 11 ou maior, usará o android:Theme.Holo.Light. Caso seja menor, usará android:Theme.Light.

Uma lista de atributos padrão que você pode usar em temas pode ser encontrado em R.stylable.Theme.

Usando uma plataforma de estilos e temas

A plataforma Android provê uma larga coleção de estilos e temas que você pode usar em suas aplicações. Você poderá encontrar referências para estilos disponíveis na classe R.style. Para usar os estilos listados dentro de R.style, substitua todos os caracteres underscore _ com um ponto. Por exemplo, você pode aplicar o tema Theme_NoTitleBar com "@android:style/Theme.NoTitleBar".

No entanto, a referência contida em R.style não é bem documentada e não descreve no todo os estilos, então ver o código desses estilos dá um melhor entendimento de como ele é. Para uma melhor referência dos estilos e temas do Android, veja esses códigos fontes:

Intervalo para cuidar de mim

Pessoal, se não houver posts por esses dias, a razão é que estou com dengue. Mas assim que tiver condições de escrever, eu posto mais material.

segunda-feira, 14 de março de 2011

0 comentários

Interface de Usuários - Estilos e Temas

Um estilo é uma coleção de propriedades que especificam o visual e formato de uma View ou janela. Um estilo por especificar propriedades como altura, padding, cor de fonte, tamanho da fonte, cor de fundo e muito mais. Um estilo é definido em um recurso XML que é reparado do XML que especifica o layout.

Estilos em Android compartilham uma filosofia similar ao que é o CSS para web design - eles permitem a você separar o design do conteúdo.

Por exemplo, usando um estilo, você pode pegar esse XML de layout...

<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textColor="#00FF00"
    android:typeface="monospace"
    android:text="@string/hello" />

...e torná-lo em algo como isso:

<TextView
    style="@style/CodeFont"
    android:text="@string/hello" />
Todos os atributos relacionados a estilo foram removidos do XML de layout e colocados em um estilo definido pelo nome CodeFont, que é então aplicado com o atributo style.

Um tema é um estilo aplicado em toda uma atividade ou aplicação, ao invés de apenas em uma View individual (como no exemplo acima). Quando um estilo é aplicado como um tema, cada View na atividade vai ter o estilo aplicado onde for suportao. Por exemplo, você pode aplicar o mesmo estilo CodeFont como um tema para uma atividade e então todo o texto dentro da atividade vai ter uma fonte mono espaçada de cor verde.

Definindo Estilos

Para criar um conjunto de estilos, salve um arquivo XML na pasta res/values dentro do seu projeto. O nome do arquivo XML é arbitrário, mas você deverá usar a extensão .xml e salvá-lo na pasta indicada acima.

O nó raiz do arquivo XML de estilo deverá ser <resources>.

Para cada estilo que você queira criar adicione um elemento <style> ao arquivo com um name que identifique unicamente o estilo. Então adicione elementos <item> para cada propriedade do estilo, com o name que declare a propriedade de estilo e o valor a ser usado. O valor para o <item> pode ser uma string, um código hexadecimal para cor, uma referência para outro recurso ou outro valor dependendo das propriedades do estilo. Aqui está um exemplo de um estilo:

<?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 da aplicação em tempo de compilação, que pode ser referenciada pelo valor do atributo name dentro do elemento <style>. Esse estilo de exemplo pode ser referenciado a partir de um XML de layout usando @style/CodeFont.


O atributo parent dentro do elemento <style> é opcional e especifica o ID de recurso de um outro estilo a partir do qual esse estilo deverá herdar propriedades. Você pode, se quiser, fazer o override de propriedades de estilo.

Lembre-se, um estilo que você quer usar como aplicação ou tema de aplicação é definido no XML da mesma maneira que um estilo para uma View. Um estilo como o que definimos acima pode ser aplicado como estilo para uma View única ou como um tema para uma atividade ou aplicação.

Herança

O atributo parent dentro do elemento <style> permite a você especificar de qual outro estilo seu estilo deverá herdar propriedades. Você pode usar esse atributo para herdar propriedades de um estilo existente e então definir suas próprias propriedades que você quer mudar ou adicionar. Você pode herdar de estilos que você mesmo criou ou de estilos que são definidos na plataforma. Por exemplo, você pode herdar o estilo de aparência padrão do Android para textos e depois modificá-lo:

<style name="GreenText" parent="@android:style/TextAppearance">
<item name="android:textColor">#00FF00</item>
</style>
Se você quer herdar de estilos que você definiu pessoalmente, você não precisa usar o atributo parent. Ao invés disso, apenas use o prefixo do nome do estilo de onde deseja que haja a herança, separada por pontos. Por exemplo, para criar um novo estilo que herda do estilo CodeFont mas deseja que a cor das letras sejam vermelhas, você pode criar um novo estilo como abaixo:

<style name="CodeFont.Red">
        <item name="android:textColor">#FF0000</item>
</style>
Note que não existe o atributo parent dentro da tag <style>, mas como o atributo name começa com a palavra CodeFont e este é o nome de um estilo, esse novo estilo herda as propriedades de CodeFont. Então o novo estilo faz um override nas características do texto fazendo-o ficar na cor vermelha. Você pode então referenciar esse novo estilo como @style/CodeFonte.Red.

Você pode continuar fazendo heranças quantas vezes entender ser necessário. Por exemplo, você pode extender CodeFont.Red para ser do format Bigger.

<style name="CodeFont.Red.Big">
        <item name="android:textSize">30sp</item>
</style>
Nota: Esse formato de herança em que vamos separando os nomes por pontos só funciona com seus próprios estilos. Não é possível fazer dessa maneira com os estilos padrão do Android.

Propriedades de Estilo

Agora que você entendeu como um estilo é definido, você precisará aprender que tipos de propriedades de estilo - definidas no elemento <item> - estão disponíveis.

O melhor local para encontrar propriedades para aplicar a uma View é a classe de referência da View em específico, que lista todos os atributos XML suportados. Por exemplo, todos os atributos listados na tabela dos atributos de TextView podem ser usados em um estilo de definição para o elemento TextView (ou qualquer de suas subclasses). Como existe um atributo chamado android:inputType na classe de referência, você poderá inserir o atributo android:inputTyle dentro do elemento <EditText>, que é subclasse de TextView:

<EditText
    android:inputType="number"
    ... />
Você pode ao invés disso criar um estilo para o EditText:

<style name="Numbers">
  <item name="android:inputType">number</item>
  ...
</style>
E então seu XML para o layout precisará apenas implementar o estilo como abaixo:

<EditText
    style="@style/Numbers"
    ... />
Esse exemplo acima pode parecer que dá mais trabalho, mas verá que, com o tempo, a reutilização de tais estilos dará maior controle sob o estilo de sua aplicação.

Para uma referência de todas as propriedades de estilo disponíveis, vá na referência de R.attr. Tenha em mente que nem todos os objetos View aceitam todos os mesmos atributos de estilo, então você deve sempre observar os estilos de propriedades suportados pela View. Contudo, se você aplicar um estilo a uma View que não a suporta, a View apenas usará os estilos suportados e ignorará todos os outros.

Algumas propriedades de estilo, contudo, não são suportadas por nenhum elemento View e pode ser aplicado apenas como um tema. Essas propriedades de estilo são aplicadas para toda a janela e não para um tipo de View. Por exemplo, propriedades de estilo para um tema podem esconder o título da aplicação, esconder o status bar ou mudar o fundo da janela. Esses tipos de propriedades de estilo não pertencem a nenhum objeto View. Para descobrir mais sobre essas propriedades que são suportadas apenas nos temas, procure pelos atributos que começam com window dentro de R.attr.

No próximo post, falarei sobre como aplicar estilos e temas para a interface.

Interface de Usuários - Notificando o usuário

Vários tipos de situação podem surgir que necessitarão que você notifique o usuário sobre eventos que ocorrem em sua aplicação. Alguns desses eventos vão requerir que o usuário responda à notificação e outros não. Por exemplo:
  • Quando um evento como salvar um arquivo foi completado, uma mensagem poderá aparecer confirmando que o arquivo foi salvo com sucesso.
  • Se sua aplicação está rodando em background e precisa da atenção do usuário, a aplicação poderá criar uma notificação que permita ao usuário responder convenientemente.
  • Se sua aplicação está efetuando uma ação na qual o usuário deverá aguardar ser completada, a aplicação poderá mostrar uma barra de progresso ou roda de progresso.
Cada uma dessas tarefas de notificação podem ser alcançadas usando uma técnica diferente:
  • Um Toast Notification, para mensagens rápidas.
  • Um Status Bar Notification, para lembretes persistentes que requerem a resposta do usuário.
  • Um Dialog Notification, para notificações relacionadas à atividade.
Toast Notification

Um toast notification (Notificação Torrada. Aliás, é um nome terrível se a gente parar para pensar nele) é uma mensagem que aparece na superfície da janela. É apenas ocupa o espaço necessário para que seja exibida a mensagem. A notificação automaticamente desaparece e não recebe nenhum evento de interação. Como um toast pode ser criado a partir de um serviço rodando em background, ele aparece mesmo que a aplicação não esteja visível.

Um toast é melhor usado para mensagens curtas, tal como "Arquivo salvo com sucesso" quando você tem quase certeza que o usuário está dando atenção à tela.

Status Bar Notification

Um Status Bar Notification adiciona um ícone ao status bar do sistema (com uma mensagem de horário opcional) e uma mensagem expandida na tela de notificação. Quando o usuário seleciona a mensagem expandida, o Android abre um intent que é definido pelo usuário (usualmente quando lança uma atividade). Você também pode configurar a notificação para alertar o usuário com um som, uma vibração ou fazer com que luzes do dispositivo sejam piscadas.

Esse tipo de notificação é ideal quando sua aplicação está trabalhando como serviço em background e precisa notificar o usuário sobre um evento. Se você precisar alertar o usuário para um evento que ocorrer enquanto uma atividade ainda está em foco, considere o uso do Dialog Notification.

Dialog Notification

Um diálogo é usualmente uma janela pequena que aparece na frente da atividade corrente. A atividade perde o foco e o diálogo aceita todas as interações do usuário. Diálogos são normalmente usados para notificações e atividades curtas que diretamente são relacionadas à aplicação em progresso.

Já falei desse tópico aqui. Para maior referência, basta acessar a parte 1 e parte 2 sobre esse tipo de notificações.

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.
Related Posts Plugin for WordPress, Blogger...