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.

terça-feira, 5 de abril de 2011

0 comentários

Loaders

Introduzido no Android 3.0, os loaders fazem ser mais fáceis o carregamento de dados de maneira assincrona nas atividades e fragmentos. Loaders tem as seguintes características:
  • Eles estão disponíveis em cada atividade e fragmento.
  • Eles provêem carregamento de dados de maneira assincrona.
  • Eles monitoram a fonte de seus dados e entregam novos resultados quando o conteúdo é modificado no back end.
  • Eles automaticamente reconectam para o último cursor do loader quando estão sendo recriados após uma mudança de configuração. Ou seja, eles não precisam fazer a chamada para recuperar os dados novamente (i.e. não é necessário passar novamente a query para o provider).
Sumário da API do Loader

Essas são as múltiplas classes e interfaces que estão involvidas no uso dos loaders em uma aplicação. Eles são listados nessa tabela:

Load manager
Uma classe abstrata associada com uma atividade ou fragmento para gerenciar uma ou mais intâncias de Loader. Isso ajuda uma aplicação no gerenciamento de operações que consomem tempo em conjunção com o ciclo de vida da atividade ou fragmento; o uso mais comum disso é o CursorLoader, contudo aplicações são livres para escrever seus proprios loaders para carregamento de outros tipos de dados.

Existe apenas um LoaderManager por atividade ou fragmento. Mas um LoaderManager pode ter múltiplos loaders.

LoaderManager.LoaderCallbacks
Uma interface de callback para um cliente para interagir com o LoaderManager. Por exemplo, você usa o método onCreateLoader() para criar um novo loader.

Loader
Uma classe abstrata que realiza o carregamento assincrono de dados. Ele é a classe base para o loader. Você tipicamente usará o CursorLoader mas você pode implementar sua própria subclasse. Enquanto os loaders estão ativos eles devem monitorar a fonte de seus dados e entregar novos resultados quando o conteúdo é mudado no back end.

AsyncTaskLoader
Loader abstrato que prove um AsyncTask para fazer o trabalho.

CursorLoader
Uma subclasse de AsyncTaskLoader que envia o query para o ContentResolver e retorna um Cursor. Essa classe implementa o protocolo Loader em uma forma padrão para fazer o envio de queries em cursores, contruindo o AsyncTaskLoader para realizar o query do cursor num thread de background para que ele não bloqueie a interface. Usando esse loader é a melhor maneira de capturar dados de maneira assincrona de um ContentProvider, ao invés de realizar um query gerenciado através do fragmento ou API da atividade.

As classes e interfaces acima listadas são componentes essenciais que você usará para implementar um loader para sua aplicação. Você não precisará de todos eles para cada loader que você criar mas você sempre necessitará de uma referência para o LoaderManager para inicializar o load e implementar uma classe Loader como CursorLoader.

Usando Loaders em sua aplicação

Uma aplicação que usa loaders tipicamente incluirá o seguinte:
  • Uma atividade ou fragmento
  • Uma instância do LoaderManager
  • Um CursorLoader para carregar dados guardados por um ContentProvider. Alternativamente, você pode implementar sua própria subclasse de Loader ou AsyncTaskLoader para carregar dados de outra fonte.
  • Uma implementação de LoaderManager.LoaderCallbacks. Isso é necessário para que seja criado novos loaders e gerenciar suas referências para loaders existentes.
  • Uma forma de mostrar os dados do loader, como um SimpleCursorAdapter.
  • Um data source, como um ContentProvider quando usando o CursorLoader.
Inicializando o Loader

O LoaderManager gerencia um ou mais instâncias de Loader dentro da atividade ou fragmento. Existe apenas um LoaderManager por atividade ou fragmento.

Você tipicamente inicializará o Loader dentro do onCreate() da atividade ou dentro do onActivityCreated() dentro do fragmento. Você faz isso da seguinte maneira:

// Prepare the loader.  Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);

O initLoader() recebe os seguintes parâmetros:
  • Um ID único que identifica o loader. No exemplo, o ID é 0.
  • Argumentos opcionais para suprir o loader na construção deste (null é um exemplo).
  • Uma implementação de LoaderManager.LoaderCallbacks à qual o LoaderManager chamará para reportar eventos de loader. No exemplo, a classe local implementa a interface LoaderManager.LoaderCallbacks e passa a referência de si mesmo, a palavra chave this.
O chamada ao initLoader assegura que o loader está inicializado e ativo. São duas as respostas a essa chamada:
  • Se o loader especificado já existe, o último loader criado será reutilizado.
  • Se o loader especificado não existe, o initLoader inicia o método onCreateLoader dentro de LoaderManager.LoaderCallbacks. Aqui é onde você implementa código para instanciar e retornar um novo loader.
Em ambos os casos, a implementação dada de LoaderManager.LoaderCallbacks é associada ao loader e será chamado quando o estado do loader for mudado. Se em algum ponto da chamada o método que faz a chamada está em estado inicializado e o loader requisitado já existe e tem dados, então o sistema chama onLoadFinished() imediatamente (duranto o initLoader()) portanto, esteja preparado para tal cenário.

Note que o método initLoader retornar o Loader que é criado mas não necessita capturar uma referência para ele. O LoaderManager gerencia a vida do loader automaticamente. O LoaderManager inicia e pára o carregamento quando necessário e mantém o estado do loader e de seus conteúdos associados. Como se pode supor, você raramente interagirá com os loaders diretamente. Você vai, mais comumente, usar os métodos dentro de LoaderManager.LoaderCallbacks para intervir em um processo de carregamento quando um evento particular ocorrer.

Reiniciando um Loader

Quando você usa o initLoader(), como mostrado acima, ele usa um loader existente com o ID especificado nele. Se não existe o ID, ele o cria. Mas algumas vezes você poderá querer descartar seus dados antigos e começar tudo de novo.

Para descartar seus dados antigos, use restartLoader(). Por exemplo, essa implementação de SearchView.OnQueryTextListener reinicia o loader quando a query do usuário é modificada. O loader precisa ser reiniciado para que possa ser usado para recriar os dados já que o filtro foi modificado.

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 os callbacks do LoaderManager

O LoaderManager.LoaderCallbacks é uma interface de callback que permite ao usuário interagir com o LoaderManager.

Loaders, em particular o CursorLoader, experam reter seus dados após serem parados. Isso permite à aplicação manter seus dados mesmo quando os métodos onStop() e onStart() são chamados na atividade ou fragmento, para que quando o usuário retorne à aplicação eles não tenham de esperar que os dados sejam novamente carregados. Você usa os métodos do LoaderManager.LoaderCallbacks quando precisa saber quando criar um novo loader e para dizer à aplicação quando é hora de parar de usar os dados do Loader.

O LoaderManager.LoaderCallbacks incluem os métodos:
  • OnCreateLoader() - Instancia e retorna um novo Loader para um dado ID.
  • OnLoadFinished() - Chamado quando um loader criado préviamente tem seu carregamento finalizado.
  • OnLoaderReset() - Chamado quando um loader criado préviamente está sendo resetado, fazendo com que seus dados fiquem indisponíveis.
 Existem exemplos diferentes em ApiDemos que ilustram como usar os loaders.

segunda-feira, 4 de abril de 2011

0 comentários

Interfaces de Usuários - Fragmentos Parte 03

Gerenciamento do ciclo de vida do Fragmento

Gerenciar o ciclo de vida do fragmento é muito parecido com o gerenciamento do ciclo de vida da atividade. Assim como a atividade, um fragmento pode existir em três estados distintos:

Resumed
O fragmento é visível na atividade sendo executada

Paused
Outra atividade está na tela e tem o foco mas uma atividade e seus fragmentos ainda estão vivos e visíveis (a atividade que está na tela está parcialmente transparente ou não cobre toda a extensão da tela).

Stopped
O fragmento não está visível. Ou a atividade hospedeira parou ou o fragmento foi removido da atividade mas adicionada ao back stack. O fragmento parado está vivo mas não está mais visível para o usuário e será morto caso a atividade seja morta.

Também como a atividade, você pode reter o estado de um fragmento usando um Bundle no caso do processo da atividade ser morto você precisar restaurar o estado do fragmento quando a atividade for restaurada. Você pode salvar o estado durando o callback onSaveInstanceState() do fragmento e restaurá-lo durante ou o onCreate(), onCreateView() ou onActivityCreated().

A mais significativa diferença nos ciclos de vida da atividade e do fragmento é como elas são guardadas em seus respectivos back stacks. Uma atividade é guardado dentro do back stack de atividades que são gerenciadas pelo sistema quando ela é parada por padrão. Contudo, um fragmento é guardado no back stack gerenciado pela atividade hospedeira apenas quando você explicitamente faz a requisição para que a instância seja salva chamando o addToBackStack() durante a transação que remove o fragmento.

Coordenando com o ciclo de vida da atividade

O ciclo de vida da atividade na qual o fragmento vive afeta diretamente o ciclo de vida do fragmento. Por exemplo, quando uma atividade recebe o onPause(), cada fragmento da atividade também receber o onPause().

Fragmentos tem alguns callbacks extras no ciclo de vida que gerenciam interações únicas com a atividade para realizar ações como construir e destruir interfaces. Esses métodos callback adicionais são:

onAttach()
Chamado quando o fragmento foi associado com uma atividade.

onCreateView()
Chamado para criar uma hierarquia de view associada com o fragmento.

onActivityCreated()
Chamado quando o método onCreate() da atividade retorna.

onDestroyView()
Chamado quando a hierarquia da view associada com o fragmento está sendo removido.

onDetach()
Chamado quando o fragmento está sendo dissociado da atividade.

domingo, 3 de abril de 2011

1 comentários

Interfaces de Usuários - Fragmentos Parte 02

Gerenciando Fragmentos


Para gerenciar os fragmentos em sua atividade, você precisa usar o FragmentManager. Para tal, chame getFragmentManager() a partir de sua atividade.

Algumas coisas que você pode fazer com o FragmentManager incluem:

  • Conseguir ver os fragmentos que existem na atividade com o findFragmentById ou findFragmentByTag.
  • Retirar fragmentos do back stack usando o popBackStack() (que simula o comando BACK)
  • Registrar um listener para mudanças no back stack com o addOnBackStackChangedListener().
Executando transações de fragmento

Uma grande característica de usar fragmentos em sua atividade é a habilidade de adicionar, remover, substituir e realizar outras ações com eles em resposta à interação do usuário. Cada conjunto de mudanças que você faz commit para a atividade é chamada de transação e você pode realizá-las usando as APIs em FragmentTransaction. Você também pode salvar cadas transação para um back stack gerenciado pela atividade, permitindo ao usuário navegar pelas mudanças que foram sendo feitas no fragmento.

Você pode conseguir uma instância de FragmentTransaction a partir do FragmentManager como mostrado no código abaixo:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Cada transação é um conjunto de mudanças que você pode realizar ao mesmo tempo. Você pode realizar todas as mudanças que quer para uma dada transação usando métodos como add(), remove() e replace(). Então, persistir as modificações usando commit().

Antes de cada commit, contudo, você pode querer chamar addToBackStack() para adicionar a transação para o back stack de transações de fragmento. Esse back stack é gerenciado pela atividade e permite ao usuário voltar para estados de fragmento anteriores pressionando o botão BACK.

Por exemplo, aqui está como substituir um fragmento por outro e preservar o estado anterior no back stack:
// Cria novo fragmento e transação
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();


// Substitui quaisquer views do tipo fragment_containter com esse fragmento
// e adiciona uma transação ao back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);


// Faz o commit da transação
transaction.commit();
Nesse exemplo, newFragment substitui os fragmentos (caso haja algum) que está no container do layout ientificado pelo ID de R.id.fragment_container . Chamando addToBackStack(), a transação que vai ser substituída é salva no back stack para que possa ser chamada de volta quando o usuário pressiona BACK.

Se você adicionar múltiplas mudanças para a transação (como outro add() ou remove()) e chamar addToBackStack(), então todas as mudanças aplicadas antes de chamar o commit() serão adicionadas para o back stack como uma transação única e o BACK poderá voltar a ele.

A maneira com a qual você adicionará mudanças para o FragmentTransaction não importa, exceto que:
  • Você deve chama commit() no final da transação
  • Se você está adicionando multiplos fragmentos para um mesmo container, então a maneira com a qual são adicionadas determinam a ordem que aparecerão na hierarquia da View.
Se você não chama addToBackStack() quando você faz uma transação que remove um fragmento, então o fragmento é destruido quando a transação faz o commit() e o usuário não pode mais navegar de volta a ele. Mas, se você chama o addToBackStack() quando está removendo um fragmento, então o fragmento é parado e poderá ser reinicializado se o usuário navegar de volta para ele.

Dica: Para cada transação de fragmento, você pode aplicar animações de transição, chamando setTransition() antes de fazer o commit()

Chamar o commit() não realiza a transação imediatamente. Ao invés disso, ele agenda a realização dela na thread da interface da atividade assim que a thread estiver disponível para tal. Se necessário você poderá chamar executePendingTransactions() para executar imediatamente as transações submetidas para o commit(). Fazendo isso usualmente não será necessário a não ser que exista uma dependência entre os trabalhos em outras threads.

Cuidado: Você pode fazer o commit() apenas antes da atividade salvar o seu estado. Se você tentar fazer um commit após esse ponto, uma exceção ocorrerá. Isso acontece pois o estado após o commit pode ser perdido se a atividade necessita ser restaurada. Para situações nas quais você pode perder o commit, use commitAllowingStateLoss().

Comunicando com a Atividade

Apesar do fragmento ser implementado como um objeto que é independente de uma atividade e que pode ser usada dentro de multiplas atividades, uma dada instância de um fragmento é diretamente relacionado à atividade que a contém.

Especificamente, o fragmento pode acessar a instância da Atividade com getActivity() e realizar tarefas facilmente como encontrar uma view no layout da atividade:
View listView = getActivity().findViewById(R.id.list);
Outrossim, sua atividade pode chamar métodos no fragmento adquirindo uma referência para o fragmento a partir do FragmentManager, usando findFragmentById() ou findFragmentByTag(). Por exemplo:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
Criando eventos de callback para a atividade


Em alguns casos, você pode precisar que um fragmento compartilhe eventos com a atividade. Uma boa forma de fazer isso é definir uma interface de callback dentro do fragmento e requerir que a atividade que a hospeda a implemente. Quando a atividade recebe um callback através da interface, ele pode compartilhar as informações com outros fragmentos no layout se necessário.


Por exemplo, se uma aplicação de notícias tem dois fragmentos em uma atividade - uma que mostra uma lista de artigos (fragmento A) e outra que mostra o artigo (fragmento B) - então o fragmento A deve dizer à atividade quando um item da lista é seleciona para que então possa dizer para o fragmento B para mostrá-lo na tela. Nesse caso, a interface OnArtigcleSelectedListener é declarada dentro do fragmento A.
public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}
Então a atividade que hospeda o fragmento implementa a interface acima e faz um override em onArticleSelected() para notificar o fragmento B de um evento no fragmento A. Para assegurar que a atividade hospedeira implemente a interface, o método onAttach() do fragmento A instancia o OnArticleSelectedListener fazendo o casting da atividade que é passada dentro do 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");
        }
    }
    ...
}
Se a atividade não foi implementada na interface, então o fragmento dá um throw para ClassCastException. Se tiver sucesso, o membro mListener guarda uma referência para a implementação da atividade de OnArticleSelectedListener, para que o fragmento A possa compartilhar eventos com a atividade chamando métodos definidos pela interface OnArticleSelectedListener. Por exemplo, se o fragmento A é uma extensão de ListFragment, cada vez que o usuário clicar num item de lista, o sistema chama onListItemClick() no fragmento, que então 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 qe é passado para onListItemClick é o ID da linha que é clicada e que é usada pela atividade para a captura do artigo no ContentProvider da aplicação.


Adicionando itens para a Action Bar


Seus fragmentos podem incluir itens de menu para a Options Menu da atividade (e, consequentemente, para a Action Bar) implementando o onCreateOptionsMenu(). Para fazer isso o método recebe a chamada mas deve chamar setHasOptionsMenu() durante o onCreate() para indicar que fragmento ele gostaria que fosse adicionado para o Options Menu.


Quaisquer itens que você queira adicionar para o Options Menu do fragmento são concatenados no menu de itens existente. O fragmento também recebe callbacks para onOptionsItemSelected() quando um item de menu é selecionado.

Você também pode registrar um view no seu layout de fragmento para prover 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().

Recursos técnicos e onde estamos no conteúdo do Android

Recursos técnicos

No site do Android existem inúmeros exemplos de código que ajudam muito a quem está começando na programação com essa plataforma. O site pode ser acessado por aqui. Se você tem dúvidas e quer ver alguns excelentes códigos criados pela equipe do Android, é o caminho certo.

Onde estamos no conteúdo


Até agora abordamos uma parte considerável do conteúdo de Android, ainda que não pareça. Só essa parte de interface é gigante!

Abaixo está o conteúdo que disse que ia (e vou) cobrir nesse blog:
  1. Visão geral do Android
  2. Criando interfaces de usuário
  3. Atividades e Intents
  4. Threads, Serviços, Receivers e Alerts
  5. Eventos de interface
  6. Técnicas de multimidia
  7. Interface de hardware
  8. Networking
  9. Métodos de data storage
  10. Serviços baseados em localização
  11. Desenvolvimento avançado em Android e;
  12. Debugging.
Nos próximos posts vou continuar falando de fragmentos (que são atividades mas marquei como interface já que respondem por interfaces para o usuário) e loaders.

Após isso continuarei com os intents (mas antes disso também falarei de Services, BoundServices e ContentProviders) e os três primeiros tópicos estarão cobertos.

Só que alguns de vocês podem estar se perguntando: ok, vou aprender tudo isso após o término dos posts programados para essa semana. Mas como vou conseguir unir tudo isso? Eu quero dizer, uma coisa é saber como o motor de um carro funciona, como é o conceito de motores a combustão. Outra coisa é saber como pegar peça por peça e fazer daquele monte de peças um motor de verdade.

Por isso que abri o post falando sobre os recursos técnicos.

Vou pegar alguns desses exemplos e explorá-los aqui para mostrar como juntar os conteúdos para fazer uma aplicação de verdade. De nada adianta ter o conhecimento geral se não conseguimos, na prática, criar um app, certo?

sábado, 2 de abril de 2011

0 comentários

Interfaces de Usuários - Fragmentos Parte 01

Um fragmento representa um comportamento ou uma porção da interface de uma atividade. Você pode combinar multiplos fragmentos em uma atividade única para contruir uma interface multi-painel e reutilizar um fragmento em múltiplas atividades.Você pode combinar multiplos fragmentos em uma única atividade para construir uma interface multi painel e reutilizar um fragmento em multiplas atividades. Você pode pensar em um fragmento como uma seção modular de uma atividade, que tem seu próprio ciclo de vida, recebe seus próprios eventos de entrada e que você pode adicionar ou remover enquanto a atividade está sendo rodada.

Um fragmento sempre deve ser embarcado em uma atividade e o ciclo de vida dele é diretamente afetado pelo ciclo de vida da atividade que o hospeda. Por exemplo, quando uma atividade está pausada, também os fragmentos estão e quando uma atividade é destruída, os fragmentos vão junto com eles. Contudo, quando uma atividade está rodando, você pode manipular cada fragmento independentemente, como adicioná-los ou removê-los. Quando você realiza uma transação em um fragmento, você também pode inserí-lo no back stack que é gerenciado pela atividade - cada entrada no back stack na atividade é um registro de uma transação de fragmento que ocorreu. O back stack permite ao usuário retornar para o fragmento anterior, pressionando o botão BACK.

Quando você adiciona um fragmento como parte de seu layout de atividade, ele vive no ViewGroup dentro da hierarquia da atividade e define seu layout de views. Você pode inserir um fragmento dentro do layout da atividade declarando um fragmento no arquivo de layout de atividade, como no elemento <fragment>, ou de seu código de aplicação adicionando-o a um ViewGroup existente. Contudo, a um fragmento não é requerido ser parte de um layout de atividade; você pode usar um fragmento como um trabalhador invisível da atividade.

Esse documento descreve como construir sua aplicação para usar fragmentos incluindo como os fragmentos podem manter seu estado quando adicionado ao back stack da atividade, compartilhar eventos com a atividade e outros fragmentos na atividade, contribuir para a action bar da atividade e mais.

Filosofia do Design

O Android introduzio os fragmentos no Android 3.0 (API Level "Honeycomb"), primariamente para suportar designs de interface mais dinâmicos e flexíveis em telas mais largas, como tablets. Como a tela dos tablets é mais larga que a de smartphones, existe mais espaço para combinar e fazer o intercâmbio de interface. Fragmentos permitem tais designs sem a necessidade de você gerenciar mudanças complexas para a hierarquia da View. Dividindo o layout de uma atividade em fragmentos, você pode permitir a modificação da aparência em tempo de execução e preservar essas mudanças no back stack que é gerenciado pela atividade.

Por exemplo, uma aplicação de notícias pode usar um fragmento para mostrar uma lista de artigos na esquerda e um outro fragmento para mostrar um artigo na direita, ambos os fragmentos aparecendo em uma única atividade, lado a lado, e cada fragmento tendo seus próprios métodos de ciclo de vida  e que possa gerenciar suas próprias interações com o usuário. Ou seja, ao invés de usar uma única atividade para selecionar um artigo e outra atividade para lê-lo, o usuário pode selecionar um artigo e lê-lo dentro da mesma atividade, como ilustrado abaixo:



Um fragmento pode ser modular e reutilizável em sua aplicação. Ou seja, já que o fragmento define seu próprio layout e seu comportamento usando seus próprios métodos callback do ciclo de vida, você pode incluir um fragmento em múltiplas atividades. Isso é especialmente importante já que permite que você adapte a experiência do usuário em diferentes tamanhos de tela. Por exemplo, você pode incluir multiplos fragmentos em uma atividade apenas quando a tela é grande o suficiente ou, quando é pequena, lançar as atividades de maneira separada para usar os diferentes fragmentos.

Por exemplo - para continuar com a aplicação de notícias citada acima - a aplicação pode embarcar dois fragmentos na atividade A, quando rodando em uma tela larga, como a de um tablet. Mas, em uma tela de tamanho normal, como a de um smartphone, não há espaço suficiente para ambos os fragmentos, então a atividade A inclui apenas o fragmento para a lista de artigos e quando o usuário seleciona um artigo, ele inicia a atividade B, que inclui o fragmento para ler o artigo.

Criando um fragmento

Para criar um fragmento, você deve criar um Fragment (ou uma subclasse existente dela). A classe Fragment tem código que se parece muito com uma atividade. Ela contém métodos callback similares a uma atividade, como onCreate(), onStart(), onPause() e onStop(). De fato, se você estiver convertendo uma aplicação Android para usar fragmentos, você deve simplesmente mover os métodos de código callback de sua atividade para os métodos callback respectivos do seu fragmento.

Usualmente, você deveria implementar pelo menos os seguintes métodos:

onCreate()
O sistema chama esse método quando está criando o fragmento. Dentro de sua implementação, você deve inicializar os componentes essenciais do fragmento para que ele possa ser pausado, parado e então reiniciado.

onCreateView()
O sistema chama quando é hora do fragmento ser desenhado na interface pela primeira vez. Para desenhar a interface do seu fragmento, você deve retornar uma View de seu método que é a raiz do layout dos fragmentos. Você pode retornar null se o fragmento não provê uma interface.

onPause()
O sistema chama esse método como a primeira indicação que o usuário está deixando o fragmento (o que não quer dizer que ele esteja sendo destruído). É usualmente onde você deve fazer o commit de quaisquer dados que devam ser salvos, já que o usuário poderá não voltar a esse fragmento.

A maioria das aplicações devem implementar pelo menos esses três métodos para cada fragmento, mas existem outros métodos callback que você também deve usar para gerenciar os vários estágios do ciclo de vida do fragmento. Existem algumas subclasses que você pode querer extender ao invés de usar a classe base Fragment:

DialogFragment
Mostra um diálogo flutuante. Usando essa classe para criar um diálogo é uma boa alternativa a usar os métodos de ajuda de diálogo na classe Activity, já que você pode incorporar um diálogo de fragmento no back stack de fragmentos gerenciados pela atividade, permitindo ao usuário retornar para um fragmento anterior.

ListFragment
Mostra uma lista de itens que são gerenciados por um adaptador (como um SimpleCursorAdapter), similar ao ListActivity. Ele provê vários métodos para gerenciar uma list view, como o onListItemClick que pode ser chamado para gerenciar eventos de click.


PreferenceFragment
Mostra a hierarquia do objeto Preference como uma lista, similar a Preferencectivity. É útil quando está criando uma atividade de configuração para sua aplicação.


Adicionando uma interface de usuário

Um fragmento é usualmente usado como parte da interface de usuário da atividade e contribui com seu próprio layout para a atividade.

Para prover um layout para um fragmento, você deve implementar o método onCreateView, que é o método que o sistema Android chama quando é hora do fragmento ser desenhado na tela. Sua implementação desse método retorna uma View que é a raiz do layout do fragmento.

Nota: Se seu fragmento é uma subclasse de ListFragment, a implementação padrão retorna um ListView de onCreateView() e, por isso, você não precisará implementá-lo.

Para retornar um layout do onCreateView(), você pode inflá-lo a partir de um recurso de layout definido no XML. Para ajudá-lo o onCreateView provê o objeto de LayoutInflater.

Por exemplo, aqui está uma subclasse de Fragment que carrega o layout a partir do arquivo example_fragment.xml.

public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Infla o layout para esse fragmento.
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}
O parâmetro de container passado para o onCreateView é o ViewGroup pai no qual o layout do fragmento deverá ser inserido. O parâmetro savedInstanceState é o Bundle que provê dados sobre a instância anterior do fragmento, se o fragmento está sendo recuperado.

O método inflate() recebe três argumentos:

  • O ID do recurso de layout que você deseja inflar.
  • O ViewGroup que será o pai do layout inflado. Passar o container é importante para que o sistema possa aplicar os parâmetros de layout para a View rais do layout inflado, especificado pela View pai na qual ele está sendo inflado.
  • Um argumento booleano indicando se o layout inflado deve ser anexado ao ViewGroup durante o processo de inflagem. Nesse caso, é falso pois o sistema já está inserindo o layout inflado dentro do container e passar um valor true faria com que fosse criado um viewgroup redundante no layout final.
Adicionando um fragmento a uma atividade

Normalmente, um fragmento contribui com uma porção da interface da aplicação que o hospeda, que é embarcado como parte da hierarquia geral da atividade. Existem duas maneiras com as quais você pode adicionar um fragmento a um layout de atividade:

Declarando um fragmento dentro do arquivo de layout da atividade

Nesse caso, você pode especificar as propriedades do layout para o fragmento como se ele fosse uma View.
<?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 dentro de <fragment> especifica a classe Fragment que instancia o layout.

Quando o sistema cria um layout de atividade, ele instancia cada fragmento especificado no layout e chama os métodos onCreateView() de cada um deles para retornar o layout do fragmento. O sistema insere a View retornada pelo fragmento diretamente no local do elemento <fragment>.

Nota: Cada fragmento requer um identificador único para que o sistema possa usá-lo para restaurar o fragmento se a atividade for reiniciada (e que você pode usar para capturar o fragmento para realizar transações). Existem três maneiras de você prover um ID para um fragmento:

  • Suprir o atributo android:id com um ID único
  • Suprir o atributo android:tag com um nome de ID em formato string e único
  • Se você não prover nenhum desses IDs acima, o sistema usa o ID da View que contém o fragmento.
Programaticamente adicionando o fragmento a um ViewGroup existente

Em qualquer momento enquando a atividade está rodando, você pode adicionar fragmentos para o layout de atividades. Você simplesmente precisa especificar um ViewGroup no qual quer colocar o fragmento.

Para fazer transações de fragmento em sua atividade (como adicionar, remover ou substituir fragmentos), você deve usar APIs do FragmentTransaction. Você pode conseguir uma instância do FragmentTransaction a partir de sua Activity usando o código abaixo:
FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Você pode então adicionar o fragmento usando o método add(), especificando o fragmento a adicionar e a View no qual quer inserí-lo.
ExampleFragment fragment = new ExampleFragment();fragmentTransaction.add(R.id.fragment_container, fragment);fragmentTransaction.commit();
O primeiro argumento passado para o método add() é o ViewGroup no qual o fragmento deverá ser inserido, especificado pelo ID do recurso e o segundo parâmetro é o fragmento em si.

Uma vez que você tiver feito as mudanças com o FragmentTransactions, você deve chamar o método commit() para que as mudanças tenham efeito.

Adicionando um fragmento que não tem Interface

Os exemplos acima mostram como adiciona um fragmento para a sua atividade para que seja provido uma interface. Contudo, você também querer usar um fragmento para prover um comportamento de background para a atividade sem apresentar interfaces adicionais.

Para adicionar um fragmento que não tem interface, adiciona o fragmento para a atividade usando add(Fragment, String). Esse método adiciona o fragmento mas, como não está associado a uma view no layout da atividade, não recebe a chamada para onCreateView(). Então não há a necessidade de se implementar esse método.

Suprindo uma string tag para o fragmento não é algo que só possa ser feito para fragmentos que não tenham interface - você também pode prover string tags para fragmentos que contenham interface - mas se o fragmento não tem interface, então a string é a única maneira de identificá-lo. Se você deseja capturar o fragmento dentro da atividade, você pode precisar usar o findFragmentByTab().

sábado, 26 de março de 2011

0 comentários

Atividades - Gerenciando Tarefas

A forma como o Android gerencia as tarefas e o back stack - colocando as atividades em uma sucessão na mesma tarefa e usando o conceito last in, first out - funciona perfeitamente para a maioria das aplicações e você não deveria se preocupar sobre como as atividades são associadas às tarefas ou como elas existem no back stack. Contudo, você deve decidir se quer interromper o comportamento padrão. Talvez você queira que uma atividade em sua aplicação comece uma nova tarefa quando ela é iniciada (ao invés de ser colocada como a tarefa corrente); ou, quando você inicia uma atividade, você queira trazer para a frente uma instância existente dela (ao invés de criar uma nova instância no topo do back stack); ou talvez você queira que seu back stack sera limpo de todas as atividades e só tenha uma atividade (a atividade root) quando o usuário saia da tarefa.

Você pode fazer isso e muitas outras coisas usando o atributo <activity> que se encontra no manifesto do Android e com flags em seus intents que você passa para o startActivity().

Nesse sentido, os atributos principals de <activity> que você pode usar são:

taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainsTaskState
finishOnTaskLaunch

E as flags de intent que você pode usar são:

FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP

Definindo os modos de lançamento (launch modes)

Launch modes permitem a você definir ua nova instância de uma atividade que está associada à tarefa corrente. Você pode definir diferentes launch modes de duas maneiras:

Usando o arquivo de manifesto
Quando você declara uma atividade em seu arquivo de manifesto, você pode manifestar como a atividade deverá ser associada com tarefa quando ela se inicia

Usando intent flags
Quando você chama startActivity(), você pode incluir a flag dentro do Intent que declara como (ou se) uma nova atividade deverá ser associada com a tarefa corrente.

Ou seja, se a atividade A inicia a atividade B, a atividade B pode definir em seu manifesto como ela deverá ser associada com a tarefa corrente e a atividade A pode também requisitar como a atividade B deve ser associada com a tarefa corrente. Se ambas atividades definem como a atividade B deveria ser associada com a tarefa, então a requisição da atividade A (como definida em seu intent) é honrada sobre a requisição da atividade B.

Usando o arquivo de manifesto

Quando se declara uma atividade no arquivo de manifesto, você pode especificar como a atividade estará associada com a tarefa usando o elemento <activity> dentro do atributo launchMode.

O atributo launchMode especifica uma instrução sobre como a atividade deve ser lançada dentro da tarefa. Existem quatro tipos diferentes de launch modes que você pode assinalar no atributo launchMode:

"standard" (o modo padrão)
O sistema cria uma nova instância da atividade na tarefa a partir da tarefa já existente e faz a rota do intent para ele. A atividade pode ser instanciada multiplas vezes e cada instância pode pertencer a diferentes tarefas e uma tarefa pode ter multiplas intâncias.

"singleTop"
Se uma instância de uma atividade já existe no topo da atividade corrente, o sistema faz o roteamento do intent para a instância através de uma chamada para o método onNewIntent(), ao invés de criar uma nova instância da atividade. A atividade pode ser instanciada multiplas vezes e cada instância pode pertencer a tarefas diferentes e uma tarefa pode ter multiplas instâncias (mas apenas se a atividade que estiver no topo do back stack não for uma instância existente da atividade).

Por exemplo, suponha que o back stack de uma tarefa consista da atividade raiz de A com as atividades B, C e D no topo (o stack ficaria como A-B-C-D onde D está no topo). Um intent chega para a atividade do tipo D. Se D tem o modo de launch mode em standard, uma nova instância da classe é lançada e o back stack se torna A-B-C-D-D. Contudo, se a atividade D estiver em modo singleTop, a instância existente de D é entregue ao intent através de onNewIntent(), já que está no topo do back stack - o back stack continua como A-B-C-D. Mas, se um intent chega para a atividade do tipo B, então uma nova instância de B é adicionada ao back stack, mesmo que o launch mode esteja em singleTop, fazendo com que o back stack fique como A-B-C-D-B.

"singleTask"
O sistema cria uma nova tarefa e instancia a atividade na raiz da nova tarefa. Contudo, se uma instância da atividade já existe em uma tarefa separada, o sistema faz o roteamento do intent para a instância existente através de uma chamada para o método onNewIntent(), ao invés de criar uma nova instância. Apenas uma instância de cada atividade pode existir a cada vez.

"singleInstance"
O mesmo que singleTask, exceto que o sistema não lança nenhuma outra atividade dentro da tarefa que está relacionada à instância. A atividade é simple única e o único membro de sua tarefa; quaisquer outras atividades iniciadas são apenas em uma tarefa separada.

Como outro exemplo, a aplicação de navegador do Android declara que a atividade de navegação deve sempre abrir sua própria tarefa - ela especifica que é singleTask no lanch mode em seu elemento <activity>. Isso significa que se sua aplicação tem um intent para abrir o navegador, a atividade não é colocada na mesma tarefa de sua aplicação. Ao invés disso, ele inicia uma tarefa nova apenas para o navegador ou, se o navegador já está aberto, a tarefa é chamada para a tela para receber a requisição.

Independente da atividade iniciar uma nova tarefa ou não ou a mesma tarefa como atividade ser aberta, o BACK sempre volta para a atividade anterior. Apesar disso ser verdade, se você iniciar uma atividade a partir de sua tarefa (Task A) que especifica o launch mode como singleTask, então a sua atividade deve ter uma instância no background que pertença a tarefa com seu próprio back stack (Task B). Nesse caso, quando a Task B for lançada para a tela para receber o novo intent, o BACK navega por todas as atividades da Task B antes de retornar para a atividade que está no topo da Task A. A imagem abaixo ilustra esse cenário.


Usando Intent Flags

Quando você inicia uma atividade, você pode modificar a associação padrão de uma atividade para que sua tarefa incluindo flags de intent que são entregues no startActivity(). As flags que você pode usar para modificar o comportamento padrão são:

FLAG_ACTIVITY_NEW_TASK
Inicia a atividade em uma nova tarefa. Se a tarefa já está rodando para a atividade que você está iniciando agora, a tarefa é trazida para a frente com seu último estado restaurado e a atividade receber um novo intent em onNewIntent().

Essa flag produz o mesmo comportamento de "singleTask".

FLAG_ACTIVITY_SINGLE_TOP
Se uma atividade se inicia na atividade corrente (no topo do back stack), então a instância existente recebe a chamada para onNewIntent(), ao invés de criar uma nova instância da atividade.

Essa flag produz o mesmo comportamento de "singleTop".

FLAG_ACTIVITY_CLEAR_TOP
Se a atividade iniciada já está rodando na tarefa corrente, então ao invés de lançar uma nova instância da atividade, todas as outras atividades no topo são destruídas e o intent é entregue para a instância reiniciada da atividade (que agora está no topo), através do onNewIntent().

Não existe nenhum atributo de launchMode que produza esse comportamento.

Essa flag é mais comumente usada em conjunção com FLAG_ACTIVITY_NEW_TASK. Quando usadas em conjunto, essas flags são uma forma de localizar uma atividade existente em outra tarefa e colocá-la em uma posição onde possa responder a um intent.

Gerenciando afinidades

Uma affinity (afinidade) indica para qual tarefa uma atividade prefere pertencer. Por padrão, todas as atividades de uma mesma aplicação tem afinidade umas às outras. Então, por padrão, todas as atividades de uma aplicação preferem estar dentro da mesma tarefa. Mas, você pode modificar a afinidade padrão de uma atividade. Atividades definidas em aplicações diferentes podem compartilhar uma afinidade, ou atividades definidas na mesma aplicação podem ser marcadas para ter afinidades distintas.

Você pode modificar a atividade de qualquer dada atividade com o atributo taskAffinity dentro do elemento <activity>.

O atributo taskAffinity recebe um valor string que deve ser único do nome de pacote padrão declarado no elemento <manifest>, pois o sistema usa o nome para identificar a tarefa padrão de afinidade da aplicação.

A afinidade deve ser usada em duas circunstâncias:

  • Quando um intent que lança uma atividade contém a flag FLAG_ACTIVITY_NEW_TASK.

    Uma nova atividade é, por padrão, lançada dentro da tarefa da atividade que chamou o startActivity(). Ela é colocada no mesmo back stack de quem fez a sua chamada. Mas, se o intent passado para o startActivity() contém a flag FLAG_ACTIVITY_NEW_TASK, o sistema procura por uma tarefa diferente para hospedar a nova atividade. Em muitos casos, é uma nova tarefa a ser criada. Só que não precisa ser assim. Se já existe uma tarefa com a mesma afinidade da nova atividade, a atividade é lançada nessa tarefa. Se não existe, então é criada uma nova tarefa.

    Se essa flag fizer com que a atividade seja criada numa nova tarefa e o usuário pressionar o botão HOME, então deve haver alguma maneira do usuário navegar de volta à tarefa. Algumas entidades (como o gerenciador de notificação) sempre inicia atividades em uma tarefa externa, nunca como parte de sua própria tarefa e, por conta disso, eles sempre colocar a flag de nova tarefa em seus intents quando eles chamam startActivity(). Se você tem uma atividade que pode ser chamada por uma entidade externa que use essa flag, tome cuidado para que o usuário tenha uma maneira de voltar para a tarefa que foi iniciada.
  • Quando uma atividade tem o atributo allowTaskReparenting setado para true.

    Nesse caso, a atividade pode mover-se da tarefa que ela iniciou para uma tarefa que tenha afinidade quando a tarefa vai para a tela.

    Por exemplo, suponha que uma atividade que reporte as condições de tempo em cidades selecionadas sejam definidas como parte de uma aplicação de viagens. Ela tem a mesma afinidade de outras atividades na mesma aplicação e permite o re-parenting em seus atributos. Quando uma das atividades inicia a atividade de tempo, ele inicialmente pertence à mesma tarefa de sua atividade. Contudo, quando a tarefa da aplicação de viagens vem para a frente, a atividade de tempo é reassinalada (re-parented) para essa tarefa e mostrada dentro dela.
Limpando o back stack

Se o usuário deixar uma tarefa por um longo período, o sistema limpa a tarefa de todas as atividades exceto a atividade raiz. Quando o usuário retorna à tarefa, apenas a atividade raiz é restaurada. O sistema se comporta dessa maneira pois após um determinado tempo é muito provável que o usuário tenha abandonado o que eles estavam fazendo e iniciaram outra tarefa.

Existem alguns atributos de atividade que podem mudar esse comportamento:

alwaysRetainTaskState
Se esse atributo está setado como true na raiz da tarefa, o comportamento padrão mencionado acima não acontece. A tarefa mantém todas as atividades no stack mesmo após um longo período.

clearTaskOnLaunch
Se esse atributo está setado como true na raiz da tarefa, o stack é limpo e só mantém a atividade raiz quando o usuário deixa a tarefa e retorna a ela. Em outras palavras, é o oposto de alwaysRetainTaskState. O usuário sempre retorna à tarefa em seu estado inicial, mesmo depois de deixar a tarefa por apenas alguns instantes.

finishOnTaskLaunch
Esse atributo é parecido com clearTaskOnLaunch mas opera como uma atividade única e não uma tarefa. Ela pode inclusive causar a destruição da atividade incluindo a atividade raiz. Quando está setado para true, a atividade permanece parte da tarefa apenas dentro da sessão corrente. Se o usuário sair da tarefa e retornar, mesmo que imediatamente, a atividade não mais estará presente por lá.

Iniciando uma tarefa

Você pode configurar qual atividade é o ponto de entrada de uma tarefa provendo um intent filter com android.intent.action.MAIN como ação específica e android.intent.category.LAUNCHER como categoria específicada. 
<activity ... >
    <intent-filter ... >
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    ...
</activity>
Um intent filter dessa natureza faz com que o ícone e o label da atividade sejam mostrados no launcher da aplicação, dando aos usuários uma maneira de lançar a atividade e retornar para a tarefa que ela cria a qualquer momento que seja lançada.

A segunda habilidade é importante: usuários podem ser permitidos a deixar a tarefa e então voltar para ela usando o activity launcher. Por essa razão, os launch modes singleTask e singleInstance devem ser usadas apenas quando a atividade tem um filtro ACTION_MAIN e um filtro CATEGORY_LAUNCHER. Imagine, por exemplo, o que pode acontecer caso um filtro desses esteja faltando: um intent lança uma atividade singleTask, iniciando uma nova tarefa e o usuário fica um tempo nela. O usuário então pressiona a tecla HOME. A tarefa é mandada para o background e fica invisível. Como não é representada no launcher, o usuário não tem mais como retornar à tarefa.

Agora, em casos em que você não quer que o usuário possa retornar à aplicação, configure o elemento finishOnTaskLaunch como true.

quarta-feira, 23 de março de 2011

0 comentários

Atividades - Tarefas e Back Stack

Uma aplicação usualmente contém multiplas atividades. Cada atividade deve ser desenhada para um tipo específico de ação que o usuário quer executar e cada atividade pode iniciar uma nova atividade. Por exemplo, uma aplicação de email pode ter uma atividade que mostra uma lista de novos emails. Quando o usuário seleciona um email, uma nova atividade abre para que o email seja mostrado.

Uma atividade pode também iniciar atividades que existem em outras aplicações no dispositivo. Por exemplo, se sua aplicação quer enviar um email, você pode definir um intent para executar a ação de enviar o email e incluir alguns dados, como o endereço de email e a mensagem a ser enviada. Uma atividade de outra aplicação que declara a si mesmo como capaz de executar tal tarefa então se abre. Nesse caso, o intent (ou intenção) é enviar um email, então a atividade que é responsável pela composição de email se abre (se multiplas atividades suportam a mesma intent, então o sistema deixa o usuário escolher qual utilizará). Assim que o email é enviado, sua atividade é volta à tela e para o usuário vai parecer que a atividade de envio de email é parte de sua aplicação. Mesmo que a atividade seja de uma aplicação diferente, o Android mantém essa experiência bastante fluída para o usuário, mantendo ambas atividades dentro de uma mesma tarefa.

Uma tarefa é uma coleção de atividades onde os usuários interagem fazendo certos trabalhos. As atividades são arranjadas numa pilha (a "back stack") na ordem em que as atividades são abertas.

O home screen do dispositivo é normalemente o ponto de partida onde quase todas as tarefas se iniciam. Quando o usuário toca o ícone de uma aplicação dentro do launcher (ou mesmo a partir de um atalho na área de trabalho), a aplicação é aberta e vem para a frente. Se nenhuma tarefa existir para a aplicação (a aplicação não foi usada recentemente), então uma nova tarefa é criada e a atividade principal (mais) para aquela aplicação se abre como a atividade raiz na pilha.

Quando a atividade corrente chama outra atividade, uma nova atividade é colocada no topo da pilha e ganha o foco. A atividade anterior permanece na pilha, mas está parada. Quando uma atividade pára, o sistema mantém o estado corrente de sua interface de usuário. Quando o usuário pressiona o botão BACK, a atividade corrente é retirada do topo da pilha (i.e. a atividade é destruída) e a atividade anterior se reinicia (o estado anterior da interface também é restaurado). Atividades na pilha nunca são rearranjadas, apenas inseridas ou retiradas da pilha - colocadas na pilha quando iniciada pela atividade atual e retirada quando o usuário pressiona BACK. Dessa maneira, o back stack funciona com a estrutura "last in, first out". Abaixo uma imagem que mostra o timeline mostrando o progresso de atividades dentro do back stack.


Se o usuário continuar a pressionar o botão BACK, então cada atividade na pilha será retirada e a atividade anterior será reaberta até que seja retornado para a home screen (ou qualquer outra atividade que estivesse sendo rodada quando a tarefa se iniciou). Quando todas as atividades são removidas da pilha, a tarefa não mais existe.

Uma tarefa é uma unidade coesa que pode ser movida para background quando o usuário inicia uma nova tarefa ou quando volta para a home screen, pressionando o botão HOME. Enquanto estiver em modo background, todas as atividades dentro da tarefa são paradas, mas o back stack para a tarefa se mantém intacto - a tarefa simplesmente perdeu o foco enquanto outra tarefa está sendo rodada, como mostrada na imagem acima. Uma tarefa pode então retornar para a tela para que usuários possam continuar do ponto onde pararam. Suponha, por exemplo, que a tarefa corrente (Task A) tem três atividades em sua pilha - dois dentro da atividade corrente. O usuário pressiona o botão HOME e inicia uma nova aplicação a partir do launcher. Quando a home screen aparece, a Task A vai para background e fica lá, aguardando ser chamada novamente. Quando a nova aplicação se inicia, o sistema inicia uma nova tarefa para essa aplicação (Task B) que tem sua própria pilha de atividades. Após interagir com a aplicação, o usuário retorna à home screen novamente e seleciona a aplicação que originalmente iniciou a tarefa Task A. Agora, Task A volta do background para a tela - todas as suas três atividades na pilha estão intactas e a atividade no topo da pilha se reinicia. Nesse ponto, o usuário pode também voltar para a Task B indo para a home screen e selecionando novamente a aplicação que iniciou a tarefa Task B. Esse é um exemplo de multitasking em Android.

Nota: Multiplas tarefas podem ser guardadas em background de uma vez. Contudo, se o usuário estiver rodando muitas tarefas em background de uma vez, o sistema pode começar a destruí-las para liberar recursos no dispositivo, causando a perda dessas atividades.

Como as atividades que ficam na back stack nunca são rearranjadas, se sua aplicação permitir iniciar uma atividade particular a partir de mais de uma atividade, uma nova instância para essa atividade é criada e inserida na pilha (ao invés de trazer a atividade já existente para o topo). Pode conta disso, uma atividade em sua aplicação pode ser instanciada multiplas vezes (mesmo a partir de diferentes tarefas). Por conta disso, quando o usuário navegar no sentido inverso usando o botão BACK, cada instancia da atividade é revelada na ordem inversa a que elas foram abertas. Você pode modificar o comportamento e veremos como fazê-lo mais tarde.

Para sumarizar o comportamento padrão das atividades e tarefas:

  • Quando a atividade A inicia a atividade B, a atividade A é parada, mas o sistema mantém seu estado. Se o usuário pressiona o botão BACK enquanto usa a atividade B, a atividade A reinicia-se com seu estado restaurado.
  • Quando o usuário deixa uma tarefa pressionando o botão HOME, a atividade corrente é parada e sua tarefa é enviada para background. O sistema mantém o estado de cada atividade dentro da tarefa. Se o usuário voltar para a tarefa, ela volta para a tela e reinicia a atividade que estiver no topo da pilha.
  • Se o usuário pressiona o botão de BACK, a atividade sendo executada é retirada da pilha e destruída. A atividade anterior é então reiniciada. Quando uma atividade é destruída, o sistema NÃO mantém seu estado de atividade.
  • Atividades podem ser instanciadas multiplas vezes, mesmo a partir de outras tarefas.
No próximo post, como gerenciar tarefas.

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. 

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...