sábado, 13 de outubro de 2012

Fragments

Olá pessoal,

Conforme prometido há algumas semanas atrás venho falar hoje de um recurso muito interessante chamado Fragments.

O recurso Fragments foi adicionado a API do Android a partir da versão 3.0 (Honeycomb) e também está disponível nas bibliotecas de suporte, o que permite ao desenvolvedor utilizar estes recursos a partir da versão 1.6 do Android.

Os Fragments permitem ao desenvolvedor encapsular código criando componentes reutilizáveis que possuem seu próprio ciclo de vida e sua própria interface de usuário. Este recurso se assemelha muito aos User Controls presentes nas linguagens de desenvolvimento .NET da Microsoft.

Uma grande vantagem desse recurso é que ele permite ao desenvolvedor criar aplicações que suportam múltiplos layouts, onde você pode definir o posicionamento dos objetos de acordo com o tamanho da tela do usuário. Imagine um aplicativo que irá exibir uma lista de imagens e quando o usuário selecionar uma delas esta imagem será mostrada, simples não? Agora vamos pensar em otimizar este cenário, condicionando a tela de nosso aplicativo de acordo com a tela do dispositivo do usuário. Em um smartphone comum, onde a visualização é geralmente em retrato, nosso aplicativo irá mostrar somente a lista de imagens, e quando o usuário selecionar uma imagem, o nosso aplicativo irá limpar tela e mostrar a imagem, já em um tablet a tela pode ser melhor aproveitada, mostrando do lado esquerdo a lista de imagens e do lado direto a imagem selecionada pelo usuário. Esta situação pode ser resolvida utilizando os Fragments e combinando o layout. A aplicação disponível para download deste artigo faz exatamente isso. Veja abaixo a visualização do mesmo aplicativo em duas telas diferentes.

Normal

Tablet

Esta aplicação tem dois tipos de Fragments, um que possui layout definido nos resources (PictureViewFragment) e o outro (ListPictureFragment) que não tem layout definido mas é representado normalmente na tela. Como isso é possível? O ListPictureFragment é uma extensão do ListView, por isso ele já tem a sua definição de layout. Já o PictureViewFragment é composto somente por um ImageView.

public class ListPictureFragment extends ListFragment

Vamos pensar em layout neste momento. Na pasta de resources, temos layout para landscape, grande e normal. Como já escrevi neste blog, os nomes das pastas definem os layouts de acordo com a situação. Vamos analisar o layout normal do Activity principal comparando com o layout large da mesma Activity.

Normal

<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <FrameLayout
        android:id="@id/fragment_container"
        android:name="com.example.fragments.PictureViewFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</GridLayout>


Large

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <fragment
        android:layout_weight="1"
        android:id="@id/ListPictureFragmentId"
        android:name="com.example.fragments.ListPictureFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent" />

    <fragment
        android:layout_weight="2"
        android:id="@id/PictureViewFragmentId"
        android:name="com.example.fragments.PictureViewFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent" />

</LinearLayout>


Repare que é muito diferente um layout do outro. O primeiro tem apenas um FrameLayout e o outro possui os dois Fragments que serão mostrados na tela. A principio, você deve ter ficado com a mesma dúvida que eu tive quando vi isso. Mas há uma explicação muito lógica para isso. Na visualização normal, este FrameLayout colocado na tela serve como um ponto de transição entre os objetos que serão mostrado dentro da Activity, isso faz com que você possa trabalhar com a troca de Fragments dentro da mesma Activity. Já o segundo caso, mostra que os dois Fragments já estão na tela e neste caso não haverá transição de objetos. Mesmo sabendo deste detalhe, ainda há partes que ficam nebulosas. Então vamos ver este código e entender como a Activity trata a visualização do layout quando é criada.


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// se tiver o container de fragments...
if (findViewById(R.id.fragment_container) != null) {

// se tiver restaurando de um estado anterior não necessita de
// mudanças...
if (savedInstanceState != null)
return;

ListPictureFragment listPictureFragment = new ListPictureFragment();

// cria um novo fragmento e adiciona pois se não encontrou, este é a visualização Normal
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, listPictureFragment).commit();

}

}

Os dois pontos chaves deste código estão destacados. O primeiro é a condição que verifica o tipo de layout de acordo com os objetos presentes na tela. O segundo é utilizado se o FrameLayout foi encontrado na tela, indica que este layout será o normal, então irá adicionar o Fragment a tela. Você pode reparar também que o objeto FragmentManager é o responsável pela manipulação dos Fragments, o objeto FragmentManager é retornado pelo método getSupportFragmentManager. Ainda pensando no formato de tela de um smartphone comum, se o usuário clicar em um item da lista, a imagem será mostrada em outro Fragment, como mostrado no código abaixo:


public void onPictureItemClicked(int position) {

PictureViewFragment pictureViewFragment = (PictureViewFragment) getSupportFragmentManager().findFragmentById(R.id.PictureViewFragmentId);

if (pictureViewFragment != null) {

pictureViewFragment.ShowImage(position);

} else {

PictureViewFragment newFragment = new PictureViewFragment();
Bundle args = new Bundle();
args.putInt(PictureViewFragment.ARG_POSITION, position);
newFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
}
}

Quando o usuário clica no item é verificado se há o PictureViewFragment na tela, se tiver (tela grande) irá mostrar a imagem usando o método ShowImage(), se não tiver (tela normal) irá criar o Fragment, definir a posição inicial atráves de argumentos e em seguida utiliza uma transaction para substituir o FrameLayout pelo novo Fragment. O ponto em destaque é de extrema importância, pois através desta chamada o usuário pode utilizar o botão Voltar para retornar a tela anterior.

Toda a transação de troca de um Fragment deve estar entre o beginTransaction e o commit do objeto FragmentTransaction. 

Voltando um pouco na explicação sobre os Fragments, é importante saber que as classes que criamos com o intuito de ser um Fragment deve ser herdada da classe Fragment ou de uma classe que seja herdada de Fragment.

public class ListPictureFragment extends ListFragment

public class PictureViewFragment extends Fragment

Observe que a ListPictureFragment herda de ListFragment que é uma classe com operações de lista para ser implementada como um Fragment. Além da ListFragment, há também a WebViewFragment e a DialogFragment que serão discutidas mais para frente aqui mesmo neste blog.

É importante conhecer o ciclo de vida do Fragment que é um pouco diferente do ciclo de vida da atividade. Pelo diagrama abaixo você pode verificar que há alguns outros estados. 


No nosso aplicativo, utilizamos o método onAttach do Fragment para definir o callback da nossa aplicação para a classe da Activity principal.

Espero que este post tenha sido útil para vocês. 


*se você fizer o teste em um smartphone, tente colocar a tela em landscape, você irá utilizar o segundo layout, no entanto há um bug neste ponto que não consegui resolver a tempo de escrever este post, mas vou procurar a correção resolver este problema.

Abraço a todos.






2 comentários:

  1. Teria como disponibilizar um novo link para download do projeto? Obrigada.

    ResponderExcluir
  2. Olá... tente por este link.

    http://www.4shared.com/zip/4YuIWOr0ce/Fragments.html?

    ResponderExcluir