pasar - navegar entre fragments android studio
Los fragmentos de ViewPager no se vuelven a crear después de FragmentTransaction.replace() seguido del botón Atrás (2)
Después de luchar con esto durante unos días, me di cuenta de que el problema en realidad era que pasé el FragmentManager
incorrecto a ViewPager
. De hecho, es el FragmentManager
devuelto por Fragment.getChildFragmentManager()
que debe utilizarse. Esto tiene sentido, ya que es el Fragment
mismo el que almacena el estado del Fragment
hijo en el ViewPager
. No estoy seguro de por qué no pude hacer ese trabajo antes, pero ahora el LifeCycle de la aplicación funciona bien con la siguiente configuración en el método onCreate()
mi Fragment
principal:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.mm_main_fragment, container, false);
mMMActivity = (MMActivity) container.getContext();
mMMActivity.getSupportActionBar().setTitle(getString(R.string.mm_toolbar_title_main_fragment));
// Setup ViewPager.
mViewPager = (ViewPager) view.findViewById(R.id.mm_pager);
mAdapter = new MMPagerAdapter(getChildFragmentManager(), mMMActivity); // <-- This is the key
mViewPager.setAdapter(mAdapter);
mViewPager.setOffscreenPageLimit(PagerConstants.OFFSCREEN_PAGE_LIMIT);
mViewPager.setCurrentItem(PagerConstants.PAGE_FILTER_RECIPES);
// Setup TabLayout.
TabLayout tabLayout = (TabLayout) view.findViewById(R.id.mm_tablayout);
tabLayout.setupWithViewPager(mViewPager);
return view;
}
Esta fue también la solución de otra pregunta . Como nota al margen, utilizo esta solución para obtener los Fragment
en mi ViewPager
y funciona muy bien con LifeCycle.
Aplicación de prueba:
En la búsqueda de este problema, creé una aplicación de prueba para esto. Los interesados pueden clonarlo desde here . Tiene colores feos.
Estoy tratando de implementar el estado de guardar y restaurar, pero tengo problemas al reemplazar el Fragment
principal con un Fragment
PreferenceFragment
y luego presionar el botón Atrás. Mi Fragment
principal consiste en una ViewPager
con un FragmentPagerAdapter
con 3 Fragments
para deslizar. Ninguna de las devoluciones de llamada Fragment.onCreateView()
para mis 3 Fragment
se invoca después de presionar el botón Atrás. He intentado todas las soluciones que he venido aquí en SO, pero no he podido resolver el problema.
Otra cosa posiblemente importante a tener en cuenta es que los datos para mis 3 Fragment
ViewPager
se almacenan en clases separadas que se instancian y se puede acceder a través de la Activity
. Los 3 Fragment
contienen todos RecyclerView
s para enumerar una buena cantidad de datos. Esto se hace por limpieza, pero también para que estos datos persistan en la Activity
. Probablemente esto no sea un problema, ya que funciona bien al iniciar la aplicación, pero también porque el problema principal es que mis Fragments
no se vuelven a crear.
Comportamiento inesperado:
En la creación de aplicaciones, todo funciona bien, pero cuando reemplazo mi Fragment
principal (que contiene un ViewPager
con un FragmentPagerAdapter
) por otro y luego presiono el botón Atrás, los Fragment
de ViewPager
no se ViewPager
crear. Se onCreateView()
de mi Fragment
principal.
Preguntas:
¿Qué me estoy perdiendo? ¿Hay alguna otra devolución de llamada que deba ser creada? ¿Dónde y cómo debería recrear los Fragments
?
¿Qué he intentado?
- Cambié el
FragmentAdapter
en uso, pero realmente debería usargetSupportFragmentManager()
como fue la solución here .
EDITAR: El uso de FragmentManager
erróneo realmente fue la fuente de mis problemas. Ver mi respuesta a continuación.
- Agregue
@Override public int getItemPosition(Object object) {return POSITION_NONE;}
alFragmentPagerAdapter
, como se sugiere here . - Cambie de
FragmentPagerAdapter
aFragmentStatePagerAdapter
que no debería importar aquí, por lo que yo entiendo. Las partes internas deFragmentStatePagerAdapter
realmente conservan losFragments
, pero no se representan de nuevo. - Agrega el
Fragment
principal de nuevo usandoFragmentTransaction
cuando se llama aonCreateView()
y en varias de las otras devoluciones de llamada de eseFragment
, miraMMMainFragment
continuación. - Varias otras cosas.
Mi clase MainFragment con el diseño XML correspondiente
public class MMMainFragment extends Fragment
{
private MMViewPager mMMViewPager = null;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Note that this method IS called when the back button is pressed.
// I have tried setting the content back to this instance in several places.
View view = inflater.inflate(R.layout.mm_main_fragment, container, false);
// Setup ViewPager.
mMMViewPager = (MMViewPager) view.findViewById(R.id.mm_pager);
TabLayout tabLayout = (TabLayout) view.findViewById(R.id.mm_tablayout);
tabLayout.setupWithViewPager(mMMViewPager);
return view;
}
public MMViewPager getMMViewPager()
{
return mMMViewPager;
}
}
mm_main_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.design.widget.TabLayout
android:id="@+id/mm_tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable" />
<com.mycompany.myapp.gui.mmpager.MMViewPager
android:id="@+id/mm_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
Luego, en Activity.onCreate()
simplemente hago:
// Insert Main Fragment.
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.mm_content_frame, new MMMainFragment())
.commit();
donde mm_content_frame
es FrameLayout
donde reemplazo y FrameLayout
Fragment
s. Luego, cuando se presiona un botón para ver las preferencias, ejecuto el siguiente fragmento a continuación en la Activity
. Agregué este Fragment
a la parte posterior para poder usar el botón Atrás.
public void showSettingsFragment()
{
getSupportActionBar().setTitle(getString(R.string.mm_drawer_settings));
getSupportFragmentManager().beginTransaction()
.replace(R.id.mm_content_frame, new MMPreferencesFragment(), FragmentConstants.SETTINGS_FRAGMENT_TAG)
.addToBackStack(null)
.commit();
}
La clase MMViewPager:
public class MMViewPager extends ViewPager
{
private MMActivity mMMActivity;
private MMPagerAdapter mMMPagerAdapter;
public MMViewPager(Context context, AttributeSet attrs)
{
super(context, attrs);
mMMActivity = (MMActivity) context;
mMMPagerAdapter = new MMPagerAdapter(mMMActivity.getSupportFragmentManager(), mMMActivity);
this.setOffscreenPageLimit(PagerConstants.OFFSCREEN_PAGE_LIMIT);
this.setAdapter(mMMMPagerAdapter);
this.setCurrentItem(PagerConstants.PAGE_FILTER_RECIPES);
}
}
La clase MMPagerAdapter, actualmente un FragmentStatePagerAdapter
:
public class MMPagerAdapter extends FragmentStatePagerAdapter
{
private MMActivity mMMActivity;
public MMPagerAdapter(FragmentManager fragmentManager, MMActivity mmActivity)
{
super(fragmentManager);
mMMActivity = mmActivity;
}
@Override
public Fragment getItem(int position)
{
switch (position)
{
case PagerConstants.PAGE_FILTER_RECIPES:
return new FilteredRecipesFragment();
case PagerConstants.PAGE_SELECTED_RECIPES:
return new SelectedRecipesFragment();
case PagerConstants.PAGE_SHOPPING_LIST:
return new ShoppingListFragment();
default:
return null;
}
}
@Override
public int getCount()
{
return PagerConstants.NUMBER_OF_PAGES; // 3
}
@Override
public CharSequence getPageTitle(int position)
{
switch (position)
{
case PagerConstants.PAGE_FILTER_RECIPES:
return mMMActivity.getResources().getString(R.string.mm_title_recipe_filter_fragment);
case PagerConstants.PAGE_SELECTED_RECIPES:
return mMMActivity.getResources().getString(R.string.mm_title_selected_recipes_fragment);
case PagerConstants.PAGE_SHOPPING_LIST:
return mMMActivity.getResources().getString(R.string.mm_title_shopping_list_fragment);
default:
return "Tab";
}
}
}
Como referencia, aquí está también el diseño principal de la Activity
:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/mm_drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Source: http://developer.android.com/training/implementing-navigation/nav-drawer.html -->
<!-- The main content view -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Source: http://android-developers.blogspot.in/2014/10/appcompat-v21-material-design-for-pre.html -->
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mm_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:layout_alignParentTop="true"
style="@style/MMActionBar"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<!-- The FrameLayout where I replace Fragments -->
<FrameLayout
android:id="@+id/mm_content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/mm_toolbar"/>
</RelativeLayout>
<ListView
android:id="@+id/mm_drawer_listview"
android:layout_width="260dp"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingStart="16dp"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:background="@color/mm_white" />
</android.support.v4.widget.DrawerLayout>
En cuanto a, entiendo que hayas resuelto tu problema. Me he enfrentado a un problema bastante similar en el pasado. Tal vez esto ayude a otros a intentar algo similar.
Lo que me ayudó para retener mi fragmento fue reemplazar el
@Override
public Fragment getItem(int position)
{
switch (position)
{
case PagerConstants.PAGE_FILTER_RECIPES:
return new FilteredRecipesFragment();
case PagerConstants.PAGE_SELECTED_RECIPES:
return new SelectedRecipesFragment();
case PagerConstants.PAGE_SHOPPING_LIST:
return new ShoppingListFragment();
default:
return null;
}
}
método de mi adaptador.
Cambio el Adapter
para mantener un HashMap
de Fragment
. De esta forma, los fragmentos se crean una vez y luego se recuperan de HashMap. Según su estructura, puede usar una ArrayList
lugar de HashMap.
Esto se hace de una manera eficiente e "inteligente" de implementar getItemPosition(Object object)
para return POSITION_NONE;
o la posición correcta del artículo.
Realmente no recuerdo si tenía el getChildFragmentManager
en el adaptador o no.
Supongo que podría profundizar en mi sistema de archivos si necesitas más información :)