Tuvimos que implementar exactamente el mismo comportamiento que usted describe para una aplicación recientemente. Las pantallas y el flujo general de la aplicación ya estaban definidos, por lo que tuvimos que mantenerlo (es un clon de la aplicación iOS ...). Afortunadamente, logramos deshacernos de los botones de retroceso en pantalla :)
Pirateamos la solución usando una mezcla de TabActivity, FragmentActivities (estábamos usando la biblioteca de soporte para fragmentos) y Fragments. En retrospectiva, estoy bastante seguro de que no fue la mejor decisión de arquitectura, pero logramos que funcione. Si tuviera que hacerlo de nuevo, probablemente trataría de hacer una solución más basada en la actividad (sin fragmentos), o trataría de tener solo una Actividad para las pestañas y dejar que el resto sean vistas (que creo que son mucho más) reutilizable que las actividades en general).
Entonces, los requisitos eran tener algunas pestañas y pantallas anidables en cada pestaña:
tab 1
screen 1 -> screen 2 -> screen 3
tab 2
screen 4
tab 3
screen 5 -> 6
etc ...
Entonces, diga: el usuario comienza en la pestaña 1, navega de la pantalla 1 a la pantalla 2 y luego a la pantalla 3, luego cambia a la pestaña 3 y navega de la pantalla 4 a 6; si vuelve a la pestaña 1, debería volver a ver la pantalla 3 y si presiona Atrás debería volver a la pantalla 2; Vuelve y está en la pantalla 1; cambie a la pestaña 3 y estará en la pantalla 6 nuevamente.
La actividad principal en la aplicación es MainTabActivity, que extiende TabActivity. Cada pestaña está asociada con una actividad, digamos ActivityInTab1, 2 y 3. Y luego cada pantalla será un fragmento:
MainTabActivity
ActivityInTab1
Fragment1 -> Fragment2 -> Fragment3
ActivityInTab2
Fragment4
ActivityInTab3
Fragment5 -> Fragment6
Cada ActivityInTab contiene solo un fragmento a la vez y sabe cómo reemplazar un fragmento por otro (más o menos lo mismo que un Grupo de gravedad). Lo bueno es que es bastante fácil mantener pilas separadas para cada pestaña de esta manera.
La funcionalidad para cada ActivityInTab era la misma: saber cómo navegar de un fragmento a otro y mantener una pila de reserva, por lo que colocamos eso en una clase base. Llamémoslo simplemente 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();
}
}
El activity_in_tab.xml es solo esto:
<?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 puede ver, el diseño de la vista para cada pestaña era el mismo. Eso es porque es solo un FrameLayout llamado contenido que contendrá cada fragmento. Los fragmentos son los que tienen la vista de cada pantalla.
Solo para los puntos de bonificación, también agregamos un pequeño código para mostrar un diálogo de confirmación cuando el usuario presiona Atrás y no hay más fragmentos a los que volver:
// 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();
}
}
Eso es más o menos la configuración. Como puede ver, cada FragmentActivity (o simplemente Activity en Android> 3) se encarga de todo el apilamiento con su propio FragmentManager.
Una actividad como ActivityInTab1 será realmente simple, solo mostrará su primer fragmento (es decir, la pantalla):
public class ActivityInTab1 extends ActivityInTab {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
navigateTo(new Fragment1());
}
}
Entonces, si un fragmento necesita navegar a otro fragmento, tiene que hacer un pequeño lanzamiento desagradable ... pero no es tan malo:
// In Fragment1.java for example...
// Need to navigate to Fragment2.
((ActivityIntab) getActivity()).navigateTo(new Fragment2());
Así que eso es todo. Estoy bastante seguro de que esta no es una solución muy canónica (y sobre todo no muy buena), por lo que me gustaría preguntar a los desarrolladores experimentados de Android cuál sería un mejor enfoque para lograr esta funcionalidad, y si esto no es "cómo es hecho "en Android, agradecería que me señale algún enlace o material que explique cuál es la forma de Android de abordar esto (pestañas, pantallas anidadas en pestañas, etc.). Siéntase libre de desgarrar esta respuesta en los comentarios :)
Una señal de que esta solución no es muy buena es que recientemente tuve que agregar algunas funciones de navegación a la aplicación. Algún botón extraño que debería llevar al usuario de una pestaña a otra y a una pantalla anidada. Hacerlo programáticamente fue un dolor de cabeza, debido a los problemas de quién sabe quién y cómo lidiar con cuándo se fragmentan e inicializan fragmentos y actividades. Creo que hubiera sido mucho más fácil si esas pantallas y pestañas fueran solo Vistas en realidad.
Finalmente, si necesita sobrevivir a los cambios de orientación, es importante que sus fragmentos se creen usando setArguments / getArguments. Si configuras variables de instancia en los constructores de tus fragmentos, estarás jodido. Pero afortunadamente, eso es realmente fácil de solucionar: solo guarde todo en setArguments en el constructor y luego recupere esas cosas con getArguments en onCreate para usarlas.