studio sirven que pasar para otro navegar los llamar fragments entre desde addtobackstack activity android tabs android-fragments

pasar - para que sirven los fragment en android



Separar Back Stack para cada pestaña en Android usando Fragments (12)

Almacenar referencias fuertes a fragmentos no es la forma correcta.

FragmentManager proporciona putFragment(Bundle, String, Fragment) y saveFragmentInstanceState(Fragment) .

Cualquiera de los dos es suficiente para implementar una backstack.

Usando putFragment , en lugar de reemplazar un Fragment, se separa el anterior y se agrega el nuevo. Esto es lo que hace el marco para reemplazar una transacción que se agrega a la backstack. putFragment almacena un índice en la lista actual de Fragmentos activos y esos Fragmentos son guardados por el marco durante los cambios de orientación.

La segunda forma, al usar saveFragmentInstanceState , guarda todo el estado del fragmento en un Bundle, lo que le permite realmente eliminarlo, en lugar de separarlo. El uso de este enfoque hace que la pila de respaldo sea más fácil de manipular, ya que puede hacer estallar un Fragmento cuando lo desee.

Usé el segundo método para este uso:

SignInFragment ----> SignUpFragment ---> ChooseBTDeviceFragment / / /------------------------/

No deseo que el usuario regrese a la pantalla de inicio de sesión, desde el tercero, presionando el botón Atrás. También hago flip animaciones entre ellos (utilizando onCreateAnimation ), por lo que las soluciones hacky no funcionan, al menos sin que el usuario advierta claramente que algo no está bien.

Este es un caso de uso válido para una backstack personalizada, haciendo lo que el usuario espera ...

private static final String STATE_BACKSTACK = "SetupActivity.STATE_BACKSTACK"; private MyBackStack mBackStack; @Override protected void onCreate(Bundle state) { super.onCreate(state); if (state == null) { mBackStack = new MyBackStack(); FragmentManager fm = getSupportFragmentManager(); FragmentTransaction tr = fm.beginTransaction(); tr.add(R.id.act_base_frg_container, new SignInFragment()); tr.commit(); } else { mBackStack = state.getParcelable(STATE_BACKSTACK); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable(STATE_BACKSTACK, mBackStack); } private void showFragment(Fragment frg, boolean addOldToBackStack) { final FragmentManager fm = getSupportFragmentManager(); final Fragment oldFrg = fm.findFragmentById(R.id.act_base_frg_container); FragmentTransaction tr = fm.beginTransaction(); tr.replace(R.id.act_base_frg_container, frg); // This is async, the fragment will only be removed after this returns tr.commit(); if (addOldToBackStack) { mBackStack.push(fm, oldFrg); } } @Override public void onBackPressed() { MyBackStackEntry entry; if ((entry = mBackStack.pop()) != null) { Fragment frg = entry.recreate(this); FragmentManager fm = getSupportFragmentManager(); FragmentTransaction tr = fm.beginTransaction(); tr.replace(R.id.act_base_frg_container, frg); tr.commit(); // Pop it now, like the framework implementation. fm.executePendingTransactions(); } else { super.onBackPressed(); } }

public class MyBackStack implements Parcelable { private final List<MyBackStackEntry> mList; public MyBackStack() { mList = new ArrayList<MyBackStackEntry>(4); } public void push(FragmentManager fm, Fragment frg) { push(MyBackStackEntry.newEntry(fm, frg); } public void push(MyBackStackEntry entry) { if (entry == null) { throw new NullPointerException(); } mList.add(entry); } public MyBackStackEntry pop() { int idx = mList.size() - 1; return (idx != -1) ? mList.remove(idx) : null; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { final int len = mList.size(); dest.writeInt(len); for (int i = 0; i < len; i++) { // MyBackStackEntry''s class is final, theres no // need to use writeParcelable mList.get(i).writeToParcel(dest, flags); } } protected MyBackStack(Parcel in) { int len = in.readInt(); List<MyBackStackEntry> list = new ArrayList<MyBackStackEntry>(len); for (int i = 0; i < len; i++) { list.add(MyBackStackEntry.CREATOR.createFromParcel(in)); } mList = list; } public static final Parcelable.Creator<MyBackStack> CREATOR = new Parcelable.Creator<MyBackStack>() { @Override public MyBackStack createFromParcel(Parcel in) { return new MyBackStack(in); } @Override public MyBackStack[] newArray(int size) { return new MyBackStack[size]; } }; }

public final class MyBackStackEntry implements Parcelable { public final String fname; public final Fragment.SavedState state; public final Bundle arguments; public MyBackStackEntry(String clazz, Fragment.SavedState state, Bundle args) { this.fname = clazz; this.state = state; this.arguments = args; } public static MyBackStackEntry newEntry(FragmentManager fm, Fragment frg) { final Fragment.SavedState state = fm.saveFragmentInstanceState(frg); final String name = frg.getClass().getName(); final Bundle args = frg.getArguments(); return new MyBackStackEntry(name, state, args); } public Fragment recreate(Context ctx) { Fragment frg = Fragment.instantiate(ctx, fname); frg.setInitialSavedState(state); frg.setArguments(arguments); return frg; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(fname); dest.writeBundle(arguments); if (state == null) { dest.writeInt(-1); } else if (state.getClass() == Fragment.SavedState.class) { dest.writeInt(0); state.writeToParcel(dest, flags); } else { dest.writeInt(1); dest.writeParcelable(state, flags); } } protected MyBackStackEntry(Parcel in) { final ClassLoader loader = getClass().getClassLoader(); fname = in.readString(); arguments = in.readBundle(loader); switch (in.readInt()) { case -1: state = null; break; case 0: state = Fragment.SavedState.CREATOR.createFromParcel(in); break; case 1: state = in.readParcelable(loader); break; default: throw new IllegalStateException(); } } public static final Parcelable.Creator<MyBackStackEntry> CREATOR = new Parcelable.Creator<MyBackStackEntry>() { @Override public MyBackStackEntry createFromParcel(Parcel in) { return new MyBackStackEntry(in); } @Override public MyBackStackEntry[] newArray(int size) { return new MyBackStackEntry[size]; } }; }

Estoy tratando de implementar pestañas para la navegación en una aplicación de Android. Como TabActivity y ActivityGroup están en desuso, me gustaría implementarlo usando Fragments.

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 posterior separada para cada pestaña?

Por ejemplo, los Fragmentos A y B estarían en la Pestaña 1 y Fragmento 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. Entonces, el Fragmento A podría reemplazarse con el Fragmento B. Cuando se selecciona la Pestaña 2, se debe mostrar el Fragmento C. Si se selecciona la Pestaña 1, el Fragmento B debería volver a aparecer. 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


El marco no hará esto por usted automáticamente. Necesitarás construir y administrar tus propios backstacks para cada pestaña.

Para ser sincero, esto parece una cosa realmente cuestionable. No me lo puedo imaginar dando como resultado una interfaz de usuario decente, si la tecla de retroceso va a hacer diferentes cosas dependiendo de la pestaña que tengo, especialmente si la tecla de retroceso también tiene su comportamiento normal de cerrar toda la actividad cuando está en la parte superior de la pila ... suena desagradable.

Si intenta construir algo así como la interfaz de usuario de un navegador web, obtener un UX que sea natural para el usuario implicará una gran cantidad de ajustes sutiles de comportamiento dependiendo del contexto, por lo que definitivamente tendrá que hacer su propia pila de respaldo gestión en lugar de confiar en alguna implementación predeterminada en el marco. Por ejemplo, intente prestar atención a la forma en que la tecla de retroceso interactúa con el navegador estándar de varias maneras en que puede entrar y salir de ella. (Cada "ventana" en el navegador es esencialmente una pestaña).


Este es un problema complejo ya que Android solo maneja 1 back stack, pero esto es factible. Me tomó varios días crear una biblioteca llamada Tab Stacker que hace exactamente lo que está buscando: un historial de fragmentos para cada pestaña. Es de código abierto y totalmente 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 ejemplo para ver si 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.


Este hilo fue muy, muy interesante y útil.
Gracias a Krishnabhadra por su explicación y código, uso su código y mejoré un poco, lo que permite mantener las pilas, CurrentTab, etc. de la configuración de cambio (rotando 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. Quizás Krishnabhadra agregará esto a su código.

Recuperar datos en Crear:

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.app_main_tab_fragment_layout); /* * Navigation stacks for each tab gets created.. * tab identifier is used as key to get respective stack for each tab */ //if we are recreating this activity... if (savedInstanceState!=null) { mStacks = (HashMap<String, Stack<Fragment>>) savedInstanceState.get("stack"); mCurrentTab = savedInstanceState.getString("currentTab"); } else { mStacks = new HashMap<String, Stack<Fragment>>(); mStacks.put(AppConstants.TAB_A, new Stack<Fragment>()); mStacks.put(AppConstants.TAB_B, new Stack<Fragment>()); } mTabHost = (TabHost)findViewById(android.R.id.tabhost); mTabHost.setup(); initializeTabs(); //set the listener the last, to avoid overwrite mCurrentTab everytime we add a new Tab mTabHost.setOnTabChangedListener(listener); }

Guarde las variables y póngalas en Bundle:

//Save variables while recreating @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putSerializable("stack", mStacks); outState.putString("currentTab", mCurrentTab); //outState.putInt("tabHost",mTabHost); }

Si existe un CurrentTab anterior, establezca esto; de lo contrario, cree un nuevo Tab_A:

public void initializeTabs(){ /* Setup your tab icons and content views.. Nothing special in this..*/ TabHost.TabSpec spec = mTabHost.newTabSpec(AppConstants.TAB_A); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_a_state_btn)); mTabHost.addTab(spec); spec = mTabHost.newTabSpec(AppConstants.TAB_B); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_b_state_btn)); mTabHost.addTab(spec); //if we have non default Tab as current, change it if (mCurrentTab!=null) { mTabHost.setCurrentTabByTag(mCurrentTab); } else { mCurrentTab=AppConstants.TAB_A; pushFragments(AppConstants.TAB_A, new AppTabAFirstFragment(), false,true); } }

I hope this helps other people.



Estoy 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),

tabA -> ScreenA1, ScreenA2 tabB -> ScreenB1, ScreenB2

Tuve los mismos requisitos en el pasado, y lo hice usando TabActivityGroup (que también se TabActivityGroup en ese momento) y Actividades. Esta vez quería usar Fragmentos.

Así que así es como lo hice.

1. Crea una clase de Fragmento base

public class BaseFragment extends Fragment { AppMainTabActivity mActivity; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mActivity = (AppMainTabActivity) this.getActivity(); } public void onBackPressed(){ } public void onActivityResult(int requestCode, int resultCode, Intent data){ } }

Todos los fragmentos en su aplicación pueden extender esta clase Base. Si desea usar fragmentos especiales como ListFragment , debe crear una clase base para eso también. Tendrás claro el uso de onBackPressed() y onActivityResult() si lees la publicación en su totalidad.

2. Crea algunos identificadores de pestañas, accesibles en cualquier lugar del proyecto

public class AppConstants{ public static final String TAB_A = "tab_a_identifier"; public static final String TAB_B = "tab_b_identifier"; //Your other constants, if you have them.. }

nada que explicar aquí ...

3. Ok, actividad de la pestaña principal: revisa los comentarios en el código.

public class AppMainFragmentActivity extends FragmentActivity{ /* Your Tab host */ private TabHost mTabHost; /* A HashMap of stacks, where we use tab identifier as keys..*/ private HashMap<String, Stack<Fragment>> mStacks; /*Save current tabs identifier in this..*/ private String mCurrentTab; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.app_main_tab_fragment_layout); /* * Navigation stacks for each tab gets created.. * tab identifier is used as key to get respective stack for each tab */ mStacks = new HashMap<String, Stack<Fragment>>(); mStacks.put(AppConstants.TAB_A, new Stack<Fragment>()); mStacks.put(AppConstants.TAB_B, new Stack<Fragment>()); mTabHost = (TabHost)findViewById(android.R.id.tabhost); mTabHost.setOnTabChangedListener(listener); mTabHost.setup(); initializeTabs(); } private View createTabView(final int id) { View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null); ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon); imageView.setImageDrawable(getResources().getDrawable(id)); return view; } public void initializeTabs(){ /* Setup your tab icons and content views.. Nothing special in this..*/ TabHost.TabSpec spec = mTabHost.newTabSpec(AppConstants.TAB_A); mTabHost.setCurrentTab(-3); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_home_state_btn)); mTabHost.addTab(spec); spec = mTabHost.newTabSpec(AppConstants.TAB_B); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_status_state_btn)); mTabHost.addTab(spec); } /*Comes here when user switch tab, or we do programmatically*/ TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() { public void onTabChanged(String tabId) { /*Set current tab..*/ mCurrentTab = tabId; if(mStacks.get(tabId).size() == 0){ /* * First time this tab is selected. So add first fragment of that tab. * Dont need animation, so that argument is false. * We are adding a new fragment which is not present in stack. So add to stack is true. */ if(tabId.equals(AppConstants.TAB_A)){ pushFragments(tabId, new AppTabAFirstFragment(), false,true); }else if(tabId.equals(AppConstants.TAB_B)){ pushFragments(tabId, new AppTabBFirstFragment(), false,true); } }else { /* * We are switching tabs, and target tab is already has atleast one fragment. * No need of animation, no need of stack pushing. Just show the target fragment */ pushFragments(tabId, mStacks.get(tabId).lastElement(), false,false); } } }; /* Might be useful if we want to switch tab programmatically, from inside any of the fragment.*/ public void setCurrentTab(int val){ mTabHost.setCurrentTab(val); } /* * To add fragment to a tab. * tag -> Tab identifier * fragment -> Fragment to show, in tab identified by tag * shouldAnimate -> should animate transaction. false when we switch tabs, or adding first fragment to a tab * true when when we are pushing more fragment into navigation stack. * shouldAdd -> Should add to fragment navigation stack (mStacks.get(tag)). false when we are switching tabs (except for the first time) * true in all other cases. */ public void pushFragments(String tag, Fragment fragment,boolean shouldAnimate, boolean shouldAdd){ if(shouldAdd) mStacks.get(tag).push(fragment); FragmentManager manager = getSupportFragmentManager(); FragmentTransaction ft = manager.beginTransaction(); if(shouldAnimate) ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left); ft.replace(R.id.realtabcontent, fragment); ft.commit(); } public void popFragments(){ /* * Select the second last fragment in current tab''s stack.. * which will be shown after the fragment transaction given below */ Fragment fragment = mStacks.get(mCurrentTab).elementAt(mStacks.get(mCurrentTab).size() - 2); /*pop current fragment from stack.. */ mStacks.get(mCurrentTab).pop(); /* We have the target fragment in hand.. Just show it.. Show a standard navigation animation*/ FragmentManager manager = getSupportFragmentManager(); FragmentTransaction ft = manager.beginTransaction(); ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right); ft.replace(R.id.realtabcontent, fragment); ft.commit(); } @Override public void onBackPressed() { if(mStacks.get(mCurrentTab).size() == 1){ // We are already showing first fragment of current tab, so when back pressed, we will finish this activity.. finish(); return; } /* Each fragment represent a screen in application (at least in my requirement, just like an activity used to represent a screen). So if I want to do any particular action * when back button is pressed, I can do that inside the fragment itself. For this I used AppBaseFragment, so that each fragment can override onBackPressed() or onActivityResult() * kind of events, and activity can pass it to them. Make sure just do your non navigation (popping) logic in fragment, since popping of fragment is done here itself. */ ((AppBaseFragment)mStacks.get(mCurrentTab).lastElement()).onBackPressed(); /* Goto previous fragment in navigation stack of this tab */ popFragments(); } /* * Imagine if you wanted to get an image selected using ImagePicker intent to the fragment. Ofcourse I could have created a public function * in that fragment, and called it from the activity. But couldn''t resist myself. */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(mStacks.get(mCurrentTab).size() == 0){ return; } /*Now current fragment on screen gets onActivityResult callback..*/ mStacks.get(mCurrentTab).lastElement().onActivityResult(requestCode, resultCode, data); } }

4. app_main_tab_fragment_layout.xml (en caso de que alguien esté interesado)

<?xml version="1.0" encoding="utf-8"?> <TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="0"/> <FrameLayout android:id="@+android:id/realtabcontent" android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1"/> <TabWidget android:id="@android:id/tabs" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="0"/> </LinearLayout> </TabHost>

5. AppTabAFirstFragment.java (Primer fragmento en la pestaña A, simliar para todas las pestañas)

public class AppTabAFragment extends BaseFragment { private Button mGotoButton; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_one_layout, container, false); mGoToButton = (Button) view.findViewById(R.id.goto_button); mGoToButton.setOnClickListener(listener); return view; } private OnClickListener listener = new View.OnClickListener(){ @Override public void onClick(View v){ /* Go to next fragment in navigation stack*/ mActivity.pushFragments(AppConstants.TAB_A, new AppTabAFragment2(),true,true); } } }

Esta podría no ser la manera más pulida y correcta. Pero funcionó maravillosamente en mi caso. Además, solo tenía este requisito en modo retrato. Nunca tuve que usar este código en un proyecto que respaldaba ambas orientaciones. Entonces no puedo decir qué tipo de desafíos enfrento allí ...

EDITAR:

Si alguien quiere un proyecto completo, he github un proyecto de muestra a github .


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: implementarlo como iPhone.

Las principales ventajas:

  • use la biblioteca android.support.design con TabLayout;
  • cada pestaña tiene su propia pila usando FragmentManager (sin guardar referencias de fragmentos);
  • soporte para enlaces profundos (cuando necesita abrir una pestaña específica y el nivel de fragmento específico en ella);
  • guardar / restaurar estados de pestañas;
  • métodos adaptativos del ciclo de vida de fragmentos en pestañas;
  • bastante fácil de implementar para sus necesidades.

Espero que ayude a alguien.

¡Gracias!


Renuncia:

Siento 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 algo bastante estándar de Android. No va a resolver el problema para todos, pero puede ayudar a algunos.

Si la diferencia principal entre los fragmentos es solo la de los datos que los respaldan (es decir, no hay grandes diferencias en el diseño), puede que no sea necesario 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 en la lista es un padre con un 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 descendientes debajo de la lista de padres inicial y podemos o no tener datos del servidor para cuando un usuario intenta acceder a una profundidad determinada más allá de la primera. Como la lista se construye desde un cursor de base de datos, y los fragmentos usan un cargador de cursor y un adaptador de cursor para completar 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 ''a'' y ''de'' apropiados que coincidirán 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 pasados ​​desde la interfaz de usuario.

4) Cuando los datos nuevos se hayan cargado desde el URI, cambie el cursor asociado con el adaptador al nuevo cursor y la lista se actualizará.

No hay una copia de respaldo asociada a esto, ya que no estamos usando transacciones, por lo que tendrá que crear la suya propia o realizar las consultas al revés al retroceder fuera de la jerarquía. Cuando probé 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 que el marco vuelve a ocupar 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!


Tuve exactamente el mismo problema e implementé un proyecto de código abierto github que cubre la exploración apilada, hacia atrás y hacia arriba, y está bien probado y documentado:

https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs

Este es un marco simple y pequeño para las pestañas de navegación y la conmutación de fragmentos y el manejo de la navegación hacia arriba y hacia atrás. Cada pestaña tiene su propia pila de fragmentos. Utiliza ActionBarSherlock y es compatible con el nivel 8 de la API.


Tuvimos que implementar exactamente el mismo comportamiento que describiste para una aplicación recientemente. Las pantallas y el flujo general de la aplicación ya estaban definidos, así que tuvimos que seguir con esto (es un clon de la aplicación de iOS ...). Afortunadamente, logramos deshacernos de los botones de retroceso en pantalla :)

Hemos pirateado 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 funcionara. Si tuviera que hacerlo de nuevo, probablemente intentaría hacer una solución más basada en la actividad (sin fragmentos), o intentar tener solo una Actividad para las pestañas y dejar que todo lo demás sean vistas (que, en mi opinión, son mucho más) reutilizable que las actividades en general).

Así que 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 ...

Digamos así: el usuario comienza en la pestaña 1, navega desde la pantalla 1 a la pantalla 2 y luego a la pantalla 3, luego cambia a la pestaña 3 y navega desde la pantalla 4 a 6; si se vuelve a cambiar a la pestaña 1, debería ver la pantalla 3 nuevamente y si presiona Atrás debería regresar a la pantalla 2; De vuelta otra vez y él está en la pantalla 1; cambia a la pestaña 3 y él está en la pantalla 6 nuevamente.

La actividad principal de la aplicación es MainTabActivity, que amplía TabActivity. Cada pestaña está asociada a 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 ActvityGroup). Lo bueno es que es bastante fácil mantener separadores de cada pestaña de esta manera.

La funcionalidad para cada ActivityInTab era bastante similar: saber cómo navegar de un fragmento a otro y mantener una pila de respaldo, por lo que ponemos 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 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 por 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(); } }

Esa es prácticamente la configuración. Como puede ver, cada FragmentActivity (o simplemente Actividad en Android> 3) se ocupa de todo el back-stacking 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 ser un poco 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 más o menos. 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 indicaras 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 romper esta respuesta en los comentarios :)

Como una señal de que esta solución no es muy buena, 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. Hacer eso programáticamente fue un dolor de cabeza, debido a los problemas de quién sabe quién y cómo se trata de cuándo se fragmentan y se inicializan y se crean instancias de fragmentos y actividades. Creo que hubiera sido mucho más fácil si esas pantallas y pestañas fueran realmente solo Vistas.

Finalmente, si necesita sobrevivir a los cambios de orientación, es importante que sus fragmentos se creen usando setArguments / getArguments. Si estableces variables de instancia en los constructores de tus fragmentos, serás atormentado. Pero, afortunadamente, eso es realmente fácil de solucionar: simplemente guarde todo en setArguments en el constructor y luego recupere esas cosas con getArguments en onCreate para usarlas.


Una solución simple:

Cada vez que cambias de pestaña / vista raíz, llama a:

fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

Va a borrar el BackStack. Recuerde llamar esto antes de cambiar el fragmento raíz.

Y agrega fragmentos con esto:

FragmentTransaction transaction = getFragmentManager().beginTransaction(); NewsDetailsFragment newsDetailsFragment = NewsDetailsFragment.newInstance(newsId); transaction.add(R.id.content_frame, newsDetailsFragment).addToBackStack(null).commit();

Tenga en cuenta que .addToBackStack(null) y transaction.add podrían p. Ej. .addToBackStack(null) con transaction.replace .


Yo recomendaría no usar backstack basado en HashMap> hay muchos errores en el modo "do not keep activities". No restaurará correctamente el estado en caso de que esté profundamente en la pila del fragmento. Y también se fragmentará en un fragmento de mapa anidado (con excepción: Fragmento sin vista encontrada para ID). Coz HashMap> después de la aplicación background / foreground será nulo

Optimizo el código anterior para trabajar con backstack del fragmento

Es inferior TabView

Actividad principal Clase

import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.widget.ImageView; import android.widget.TabHost; import android.widget.TextView; import com.strikersoft.nida.R; import com.strikersoft.nida.abstractActivity.BaseActivity; import com.strikersoft.nida.screens.tags.mapTab.MapContainerFragment; import com.strikersoft.nida.screens.tags.searchTab.SearchFragment; import com.strikersoft.nida.screens.tags.settingsTab.SettingsFragment; public class TagsActivity extends BaseActivity { public static final String M_CURRENT_TAB = "M_CURRENT_TAB"; private TabHost mTabHost; private String mCurrentTab; public static final String TAB_TAGS = "TAB_TAGS"; public static final String TAB_MAP = "TAB_MAP"; public static final String TAB_SETTINGS = "TAB_SETTINGS"; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().requestFeature(Window.FEATURE_ACTION_BAR); getActionBar().hide(); setContentView(R.layout.tags_activity); mTabHost = (TabHost) findViewById(android.R.id.tabhost); mTabHost.setup(); if (savedInstanceState != null) { mCurrentTab = savedInstanceState.getString(M_CURRENT_TAB); initializeTabs(); mTabHost.setCurrentTabByTag(mCurrentTab); /* when resume state it''s important to set listener after initializeTabs */ mTabHost.setOnTabChangedListener(listener); } else { mTabHost.setOnTabChangedListener(listener); initializeTabs(); } } private View createTabView(final int id, final String text) { View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null); ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon); imageView.setImageDrawable(getResources().getDrawable(id)); TextView textView = (TextView) view.findViewById(R.id.tab_text); textView.setText(text); return view; } /* create 3 tabs with name and image and add it to TabHost */ public void initializeTabs() { TabHost.TabSpec spec; spec = mTabHost.newTabSpec(TAB_TAGS); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_tag_drawable, getString(R.string.tab_tags))); mTabHost.addTab(spec); spec = mTabHost.newTabSpec(TAB_MAP); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_map_drawable, getString(R.string.tab_map))); mTabHost.addTab(spec); spec = mTabHost.newTabSpec(TAB_SETTINGS); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_settings_drawable, getString(R.string.tab_settings))); mTabHost.addTab(spec); } /* first time listener will be trigered immediatelly after first: mTabHost.addTab(spec); for set correct Tab in setmTabHost.setCurrentTabByTag ignore first call of listener */ TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() { public void onTabChanged(String tabId) { mCurrentTab = tabId; if (tabId.equals(TAB_TAGS)) { pushFragments(SearchFragment.getInstance(), false, false, null); } else if (tabId.equals(TAB_MAP)) { pushFragments(MapContainerFragment.getInstance(), false, false, null); } else if (tabId.equals(TAB_SETTINGS)) { pushFragments(SettingsFragment.getInstance(), false, false, null); } } }; /* Example of starting nested fragment from another fragment: Fragment newFragment = ManagerTagFragment.newInstance(tag.getMac()); TagsActivity tAct = (TagsActivity)getActivity(); tAct.pushFragments(newFragment, true, true, null); */ public void pushFragments(Fragment fragment, boolean shouldAnimate, boolean shouldAdd, String tag) { FragmentManager manager = getFragmentManager(); FragmentTransaction ft = manager.beginTransaction(); if (shouldAnimate) { ft.setCustomAnimations(R.animator.fragment_slide_left_enter, R.animator.fragment_slide_left_exit, R.animator.fragment_slide_right_enter, R.animator.fragment_slide_right_exit); } ft.replace(R.id.realtabcontent, fragment, tag); if (shouldAdd) { /* here you can create named backstack for realize another logic. ft.addToBackStack("name of your backstack"); */ ft.addToBackStack(null); } else { /* and remove named backstack: manager.popBackStack("name of your backstack", FragmentManager.POP_BACK_STACK_INCLUSIVE); or remove whole: manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); */ manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); } ft.commit(); } /* If you want to start this activity from another */ public static void startUrself(Activity context) { Intent newActivity = new Intent(context, TagsActivity.class); newActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(newActivity); context.finish(); } @Override public void onSaveInstanceState(Bundle outState) { outState.putString(M_CURRENT_TAB, mCurrentTab); super.onSaveInstanceState(outState); } @Override public void onBackPressed(){ super.onBackPressed(); } }

tags_activity.xml

<

?xml version="1.0" encoding="utf-8"?> <TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="0"/> <FrameLayout android:id="@+android:id/realtabcontent" android:background="@drawable/bg_main_app_gradient" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <TabWidget android:id="@android:id/tabs" android:background="#EAE7E1" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="0"/> </LinearLayout> </TabHost>

tags_icon.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/tabsLayout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/bg_tab_gradient" android:gravity="center" android:orientation="vertical" tools:ignore="contentDescription" > <ImageView android:id="@+id/tab_icon" android:layout_marginTop="4dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/tab_text" android:layout_marginBottom="3dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/tab_text_color"/> </LinearLayout>