Tivemos que implementar exatamente o mesmo comportamento que você descreve para um aplicativo recentemente. As telas e o fluxo geral do aplicativo já estavam definidos, então tivemos que ficar com ele (é um clone do aplicativo iOS ...). Felizmente, conseguimos nos livrar dos botões de retorno na tela :)
Nós invadimos a solução usando uma mistura de TabActivity, FragmentActivities (estávamos usando a biblioteca de suporte para fragmentos) e Fragmentos. Em retrospectiva, tenho certeza de que não foi a melhor decisão de arquitetura, mas conseguimos fazer a coisa funcionar. Se eu tivesse que fazer isso de novo, provavelmente tentaria fazer uma solução mais baseada em atividades (sem fragmentos), ou tentaria ter apenas uma atividade para as guias e permitir que todo o resto fosse visualizações (que considero muito mais reutilizáveis do que as atividades em geral).
Portanto, os requisitos eram ter algumas guias e telas aninhadas em cada guia:
tab 1
screen 1 -> screen 2 -> screen 3
tab 2
screen 4
tab 3
screen 5 -> 6
etc ...
Digamos: o usuário começa na guia 1, navega da tela 1 para a tela 2 e depois para a tela 3, depois alterna para a guia 3 e navega da tela 4 para 6; se retornou à guia 1, ele deve ver a tela 3 novamente e, se pressionou Voltar, deve retornar à tela 2; De volta e ele está na tela 1; mude para a guia 3 e ele estará na tela 6 novamente.
A principal atividade no aplicativo é MainTabActivity, que estende o TabActivity. Cada guia está associada a uma atividade, digamos ActivityInTab1, 2 e 3. E então cada tela será um fragmento:
MainTabActivity
ActivityInTab1
Fragment1 -> Fragment2 -> Fragment3
ActivityInTab2
Fragment4
ActivityInTab3
Fragment5 -> Fragment6
Cada ActivityInTab contém apenas um fragmento de cada vez e sabe como substituir um fragmento por outro (praticamente o mesmo que um ActvityGroup). O legal é que é muito fácil manter pilhas de costas separadas para cada guia dessa maneira.
A funcionalidade de cada ActivityInTab era a mesma: saber como navegar de um fragmento para outro e manter uma pilha inversa; portanto, colocamos isso em uma classe base. Vamos chamá-lo simplesmente de ActivityInTab:
abstract class ActivityInTab extends FragmentActivity { // FragmentActivity is just Activity for the support library.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_in_tab);
}
/**
* Navigates to a new fragment, which is added in the fragment container
* view.
*
* @param newFragment
*/
protected void navigateTo(Fragment newFragment) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.content, newFragment);
// Add this transaction to the back stack, so when the user presses back,
// it rollbacks.
ft.addToBackStack(null);
ft.commit();
}
}
O activity_in_tab.xml é exatamente isso:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:isScrollContainer="true">
</RelativeLayout>
Como você pode ver, o layout da visualização para cada guia era o mesmo. Isso ocorre porque é apenas um FrameLayout chamado conteúdo que contém cada fragmento. Os fragmentos são os que têm a visualização de cada tela.
Apenas para os pontos de bônus, também adicionamos um pouco de código para mostrar uma caixa de diálogo de confirmação quando o usuário pressiona Voltar e não há mais fragmentos para retornar:
// In ActivityInTab.java...
@Override
public void onBackPressed() {
FragmentManager manager = getSupportFragmentManager();
if (manager.getBackStackEntryCount() > 0) {
// If there are back-stack entries, leave the FragmentActivity
// implementation take care of them.
super.onBackPressed();
} else {
// Otherwise, ask user if he wants to leave :)
showExitDialog();
}
}
Essa é basicamente a configuração. Como você pode ver, cada FragmentActivity (ou simplesmente Activity in Android> 3) está cuidando de todo o empilhamento com seu próprio FragmentManager.
Uma atividade como ActivityInTab1 será realmente simples, apenas mostrará seu primeiro fragmento (por exemplo, tela):
public class ActivityInTab1 extends ActivityInTab {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
navigateTo(new Fragment1());
}
}
Então, se um fragmento precisar navegar para outro fragmento, ele precisará fazer uma seleção um pouco desagradável ... mas não é tão ruim assim:
// In Fragment1.java for example...
// Need to navigate to Fragment2.
((ActivityIntab) getActivity()).navigateTo(new Fragment2());
Então é isso mesmo. Tenho certeza de que esta não é uma solução muito canônica (e principalmente não muito boa), então gostaria de perguntar aos desenvolvedores experientes do Android qual seria a melhor abordagem para obter essa funcionalidade e, se não for "como é" done "no Android, eu apreciaria se você pudesse me indicar algum link ou material que explique qual é a maneira do Android de abordar isso (guias, telas aninhadas em guias etc.). Sinta-se livre para separar esta resposta nos comentários :)
Como sinal de que esta solução não é muito boa, recentemente tive que adicionar algumas funcionalidades de navegação ao aplicativo. Algum botão bizarro que deve levar o usuário de uma guia para outra e para uma tela aninhada. Fazer isso de forma programática foi uma chatice, por causa de problemas de quem sabe quem e de quando lidar com fragmentos e atividades realmente instanciados e inicializados. Acho que teria sido muito mais fácil se essas telas e guias fossem apenas apenas visualizações.
Por fim, se você precisar sobreviver às alterações de orientação, é importante que seus fragmentos sejam criados usando setArguments / getArguments. Se você definir variáveis de instância nos construtores de seus fragmentos, ficará ferrado. Mas, felizmente, é realmente fácil de corrigir: salve tudo em setArguments no construtor e, em seguida, recupere essas coisas com getArguments no onCreate para usá-las.