Estoy tratando de implementar pestañas para la navegación en una aplicación de Android. Dado que TabActivity y ActivityGroup están en desuso, me gustaría implementarlo usando Fragments en su lugar.
Sé cómo configurar un fragmento para cada pestaña y luego cambiar fragmentos cuando se hace clic en una pestaña. Pero, ¿cómo puedo tener una pila separada para cada pestaña?
Por ejemplo, los Fragmentos A y B estarían en la Pestaña 1 y los Fragmentos C y D en la Pestaña 2. Cuando se inicia la aplicación, se muestra el Fragmento A y se selecciona la Pestaña 1. Luego, el Fragmento A podría reemplazarse por el Fragmento B. Cuando se selecciona la Pestaña 2, se debe mostrar el Fragmento C. Si se selecciona la Pestaña 1, se debe mostrar nuevamente el Fragmento B. En este punto, debería ser posible usar el botón Atrás para mostrar el Fragmento A.
Además, es importante que el estado de cada pestaña se mantenga cuando se gira el dispositivo.
BR Martin
fuente
Llego terriblemente tarde a esta pregunta. Pero dado que este hilo ha sido muy informativo y útil para mí, pensé que sería mejor publicar mis dos peniques aquí.
Necesitaba un flujo de pantalla como este (un diseño minimalista con 2 pestañas y 2 vistas en cada pestaña),
Tenía los mismos requisitos en el pasado, y lo hice usando
TabActivityGroup
(que también estaba en desuso en ese momento) y Actividades. Esta vez quería usar Fragmentos.Así es como lo hice.
1. Crear una clase de fragmento base
Todos los fragmentos en su aplicación pueden extender esta clase Base. Si desea utilizar fragmentos especiales como
ListFragment
debería crear una clase base para eso también. Tendrás claro el uso deonBackPressed()
yonActivityResult()
si lees la publicación completa.2. Cree algunos identificadores de pestaña, accesibles desde cualquier lugar del proyecto
nada que explicar aquí ...
3. Ok, Actividad de la pestaña principal: revise los comentarios en código.
4. app_main_tab_fragment_layout.xml (en caso de que alguien esté interesado).
5. AppTabAFirstFragment.java (primer fragmento en la pestaña A, similar para todas las pestañas)
Esta podría no ser la forma más pulida y correcta. Pero funcionó muy bien en mi caso. Además, solo tenía este requisito en modo vertical. Nunca tuve que usar este código en un proyecto que admite ambas orientaciones. Así que no puedo decir qué tipo de desafíos enfrento allí ...
EDITAR:
Si alguien quiere un proyecto completo, he enviado un proyecto de muestra a github .
fuente
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:
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:
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:
El activity_in_tab.xml es solo esto:
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:
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):
Entonces, si un fragmento necesita navegar a otro fragmento, tiene que hacer un pequeño lanzamiento desagradable ... pero no es tan malo:
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.
fuente
Esto se puede lograr fácilmente con ChildFragmentManager
Aquí hay una publicación sobre esto con el proyecto asociado. echar un vistazo,
http://tausiq.wordpress.com/2014/06/06/android-multiple-fragments-stack-in-each-viewpager-tab/
fuente
Almacenar referencias fuertes a fragmentos no es la forma correcta.
FragmentManager proporciona
putFragment(Bundle, String, Fragment)
ysaveFragmentInstanceState(Fragment)
.Cualquiera de los dos es suficiente para implementar un backstack.
Usando
putFragment
, en lugar de reemplazar un Fragmento, separa el antiguo y agrega el nuevo. Esto es lo que el marco hace a una transacción de reemplazo que se agrega al backstack.putFragment
almacena un índice en la lista actual de Fragmentos activos y el Marco guarda esos Fragmentos durante los cambios de orientación.La segunda forma, usando
saveFragmentInstanceState
, guarda todo el estado del fragmento en un paquete que le permite realmente eliminarlo, en lugar de separarlo. El uso de este enfoque hace que la pila posterior sea más fácil de manipular, ya que puede hacer estallar un Fragmento cuando lo desee.Usé el segundo método para este caso de uso:
No quiero que el usuario regrese a la pantalla Registrarse, desde la tercera, presionando el botón Atrás. También doy vuelta a las animaciones entre ellos (usando
onCreateAnimation
), para que las soluciones hacky no funcionen, al menos sin que el usuario note claramente que algo no está bien.Este es un caso de uso válido para un backstack personalizado, que hace lo que el usuario espera ...
fuente
Descargo de responsabilidad:
Creo que este es el mejor lugar para publicar una solución relacionada en la que he trabajado para un tipo similar de problema que parece ser bastante estándar en Android. No va a resolver el problema para todos, pero puede ayudar a algunos.
Si la diferencia principal entre sus fragmentos es solo los datos que los respaldan (es decir, no hay muchas diferencias de diseño grandes), entonces es posible que no necesite reemplazar el fragmento, sino simplemente intercambiar los datos subyacentes y actualizar la vista.
Aquí hay una descripción de un posible ejemplo para este enfoque:
Tengo una aplicación que usa ListViews. Cada elemento de la lista es un padre con cierto número de hijos. Cuando toca el elemento, se debe abrir una nueva lista con esos elementos secundarios, dentro de la misma pestaña ActionBar que la lista original. Estas listas anidadas tienen un diseño muy similar (algunos ajustes condicionales aquí y allá tal vez), pero los datos son diferentes.
Esta aplicación tiene varias capas de descendencia debajo de la lista principal inicial y es posible que tengamos o no datos del servidor cuando el usuario intente acceder a cierta profundidad más allá de la primera. Debido a que la lista se construye a partir de un cursor de base de datos, y los fragmentos usan un cargador de cursor y un adaptador de cursor para llenar la vista de lista con elementos de lista, todo lo que debe suceder cuando se registra un clic es:
1) Cree un nuevo adaptador con los campos apropiados 'a' y 'desde' que coincidan con las nuevas vistas de elementos que se agregan a la lista y las columnas devueltas por el nuevo cursor.
2) Configure este adaptador como el nuevo adaptador para ListView.
3) Cree un nuevo URI basado en el elemento en el que se hizo clic y reinicie el cargador de cursor con el nuevo URI (y proyección). En este ejemplo, el URI se asigna a consultas específicas con los argumentos de selección transmitidos desde la IU.
4) Cuando los nuevos datos se hayan cargado desde el URI, cambie el cursor asociado con el adaptador al nuevo cursor, y la lista se actualizará.
No hay backstack asociado con esto, ya que no estamos usando transacciones, por lo que tendrá que crear las suyas propias o reproducir las consultas a la inversa cuando salga de la jerarquía. Cuando intenté esto, las consultas fueron lo suficientemente rápidas como para volver a realizarlas en oNBackPressed () hasta que esté en la parte superior de la jerarquía, momento en el cual el marco vuelve a tomar el botón Atrás.
Si se encuentra en una situación similar, asegúrese de leer los documentos: http://developer.android.com/guide/topics/ui/layout/listview.html
http://developer.android.com/reference/android/support/v4/app/LoaderManager.LoaderCallbacks.html
¡Espero que esto ayude a alguien!
fuente
Tuve exactamente el mismo problema e implementé un proyecto de código abierto de github que cubre la pestaña apilada, la navegación hacia atrás y hacia arriba y está bien probado y documentado:
https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs
fuente
Este es un problema complejo ya que Android solo maneja 1 back stack, pero esto es factible. Me llevó días crear una biblioteca llamada Tab Stacker que hace exactamente lo que estás buscando: un historial de fragmentos para cada pestaña. Es de código abierto y está completamente documentado, y se puede incluir fácilmente con gradle. Puede encontrar la biblioteca en github: https://github.com/smart-fun/TabStacker
También puede descargar la aplicación de muestra para ver que el comportamiento corresponde a sus necesidades:
https://play.google.com/apps/testing/fr.arnaudguyon.tabstackerapp
Si tiene alguna pregunta, no dude en enviar un correo.
fuente
Me gustaría sugerir mi propia solución en caso de que alguien esté buscando y quiera probar y elegir la mejor para sus necesidades.
https://github.com/drusak/tabactivity
El propósito de crear la biblioteca es bastante banal: impleméntelo como iPhone.
Las principales ventajas:
fuente
ListFragment
s además deFragment
s, así que dupliqué BaseTabFragment.java a BaseTabListFragment.java y tuve que extender ListFragment. Luego tuve que cambiar varias partes del código donde siempre se suponía que esperaba un BaseTabFragment. ¿Hay una mejor manera?Una solución simple:
Cada vez que cambie la pestaña / vista de raíz llame:
Borrará la BackStack. Recuerde llamar a esto antes de cambiar el fragmento raíz.
Y agregue fragmentos con esto:
Tenga en cuenta que
.addToBackStack(null)
ytransaction.add
, por ejemplo, podría cambiarse contransaction.replace
.fuente
Este hilo fue muy muy interesante y útil.
Gracias Krishnabhadra por su explicación y código, uso su código y mejoré un poco, permitiendo que las pilas, currentTab, etc ... persistan de cambiar la configuración (girando principalmente).
Probado en dispositivos reales 4.0.4 y 2.3.6, no probado en emulador
Cambio esta parte del código en "AppMainTabActivity.java", el resto permanece igual. Tal vez Krishnabhadra agregará esto en su código.
Recuperar datos en Crear:
Guarde las variables y póngalas en Bundle:
Si existe una CurrentTab anterior, configúrela, de lo contrario, cree una nueva Tab_A:
Espero que esto ayude a otras personas.
fuente
Recomendaría no utilizar backstack basado en HashMap> hay muchos errores en el modo "no mantener actividades". No restaurará correctamente el estado en caso de que esté profundamente en la pila de fragmentos. Y también se rastreará en un fragmento de mapa anidado (con excepción: Fragmento que no se encontró ninguna vista para ID). Coz HashMap> después de que la aplicación background \ foreground sea nula
Optimizo el código anterior para trabajar con la pila de fragmentos
Es inferior TabView
Actividad principal Clase
tags_activity.xml
<
tags_icon.xml
fuente