Reemplazar fragmento dentro de un ViewPager
Estoy intentando usar Fragment ViewPager
con FragmentPagerAdapter
. Lo que busco lograr es reemplazar un fragmento, ubicado en la primera página del archivo ViewPager
, por otro.
El buscapersonas se compone de dos páginas. El primero es el FirstPagerFragment
, el segundo es el SecondPagerFragment
. Haciendo clic en un botón de la primera página. Me gustaría reemplazarlo FirstPagerFragment
con NextFragment.
Ahí está mi código a continuación.
public class FragmentPagerActivity extends FragmentActivity {
static final int NUM_ITEMS = 2;
MyAdapter mAdapter;
ViewPager mPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_pager);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
}
/**
* Pager Adapter
*/
public static class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
if(position == 0) {
return FirstPageFragment.newInstance();
} else {
return SecondPageFragment.newInstance();
}
}
}
/**
* Second Page FRAGMENT
*/
public static class SecondPageFragment extends Fragment {
public static SecondPageFragment newInstance() {
SecondPageFragment f = new SecondPageFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
return inflater.inflate(R.layout.second, container, false);
}
}
/**
* FIRST PAGE FRAGMENT
*/
public static class FirstPageFragment extends Fragment {
Button button;
public static FirstPageFragment newInstance() {
FirstPageFragment f = new FirstPageFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
View root = inflater.inflate(R.layout.first, container, false);
button = (Button) root.findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
trans.addToBackStack(null);
trans.commit();
}
});
return root;
}
/**
* Next Page FRAGMENT in the First Page
*/
public static class NextFragment extends Fragment {
public static NextFragment newInstance() {
NextFragment f = new NextFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Log.d("DEBUG", "onCreateView");
return inflater.inflate(R.layout.next, container, false);
}
}
}
...y aquí los archivos xml
fragment_pager.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:padding="4dip"
android:gravity="center_horizontal"
android:layout_width="match_parent" android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</android.support.v4.view.ViewPager>
</LinearLayout>
first.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/first_fragment_root_id"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/button"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="to next"/>
</LinearLayout>
Ahora el problema... ¿en qué ID debo usar?
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
?
Si uso R.id.first_fragment_root_id
, el reemplazo funciona, pero el Visor de jerarquía muestra un comportamiento extraño, como se muestra a continuación.
Al principio la situación es
después del reemplazo la situación es
Como puede ver, algo anda mal. Espero encontrar el mismo estado que se muestra en la primera imagen después de reemplazar el fragmento.
Existe otra solución que no necesita modificar el código fuente de ViewPager
y FragmentStatePagerAdapter
y funciona con la FragmentPagerAdapter
clase base utilizada por el autor.
Me gustaría comenzar respondiendo la pregunta del autor sobre qué identificación debería usar; es el ID del contenedor, es decir, el ID del propio buscapersonas. Sin embargo, como probablemente habrás notado, usar esa ID en tu código no hace que suceda nada. Te explicaré por qué:
En primer lugar, para ViewPager
volver a llenar las páginas, debe llamar notifyDataSetChanged()
a la que reside en la clase base de su adaptador.
En segundo lugar, ViewPager
utiliza el getItemPosition()
método abstracto para comprobar qué páginas deben destruirse y cuáles conservarse. La implementación predeterminada de esta función siempre devuelve POSITION_UNCHANGED
, lo que hace ViewPager
que se mantengan todas las páginas actuales y, en consecuencia, no se adjunte la nueva página. Por lo tanto, para que el reemplazo de fragmentos funcione, getItemPosition()
debe anularse en su adaptador y debe regresar POSITION_NONE
cuando se le llame con un fragmento antiguo, que se ocultará, como argumento.
Esto también significa que su adaptador siempre debe saber qué fragmento debe mostrarse en la posición 0 FirstPageFragment
o NextFragment
. Una forma de hacerlo es proporcionar un oyente al crear FirstPageFragment
, al que se llamará cuando llegue el momento de cambiar fragmentos. Sin embargo, creo que es bueno permitir que su adaptador de fragmentos maneje todos los cambios de fragmentos y las llamadas a ViewPager
y FragmentManager
.
En tercer lugar, FragmentPagerAdapter
almacena en caché los fragmentos usados con un nombre que se deriva de la posición, de modo que si había un fragmento en la posición 0, no será reemplazado aunque la clase sea nueva. Hay dos soluciones, pero la más sencilla es utilizar la remove()
función de FragmentTransaction
, que también eliminará su etiqueta.
Era mucho texto, aquí hay un código que debería funcionar en su caso:
public class MyAdapter extends FragmentPagerAdapter
{
static final int NUM_ITEMS = 2;
private final FragmentManager mFragmentManager;
private Fragment mFragmentAtPos0;
public MyAdapter(FragmentManager fm)
{
super(fm);
mFragmentManager = fm;
}
@Override
public Fragment getItem(int position)
{
if (position == 0)
{
if (mFragmentAtPos0 == null)
{
mFragmentAtPos0 = FirstPageFragment.newInstance(new FirstPageFragmentListener()
{
public void onSwitchToNextFragment()
{
mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
mFragmentAtPos0 = NextFragment.newInstance();
notifyDataSetChanged();
}
});
}
return mFragmentAtPos0;
}
else
return SecondPageFragment.newInstance();
}
@Override
public int getCount()
{
return NUM_ITEMS;
}
@Override
public int getItemPosition(Object object)
{
if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
}
public interface FirstPageFragmentListener
{
void onSwitchToNextFragment();
}
A partir del 13 de noviembre de 2012, el repaso de fragmentos en ViewPager parece haberse vuelto mucho más fácil. Google lanzó Android 4.2 con soporte para fragmentos anidados y también es compatible con la nueva biblioteca de soporte de Android v11 , por lo que funcionará desde 1.6.
Es muy similar a la forma normal de reemplazar un fragmento excepto que usa getChildFragmentManager. Parece funcionar, excepto que la pila de fragmentos anidados no aparece cuando el usuario hace clic en el botón Atrás. Según la solución en esa pregunta vinculada, debe llamar manualmente a popBackStackImmediate() en el administrador secundario del fragmento. Por lo tanto, debe anular onBackPressed() de la actividad ViewPager donde obtendrá el fragmento actual de ViewPager y llamará a getChildFragmentManager().popBackStackImmediate() en él.
Obtener el fragmento que se muestra actualmente también es un poco complicado. Utilicé esta sucia solución "android:switcher:VIEWPAGER_ID:INDEX" , pero también puedes realizar un seguimiento de todos los fragmentos de ViewPager tú mismo como se explica en la segunda solución de esta página .
Así que aquí está mi código para un ViewPager con 4 ListViews con una vista detallada que se muestra en ViewPager cuando el usuario hace clic en una fila y con el botón Atrás funcionando. Intenté incluir solo el código relevante en aras de la brevedad, así que deja un comentario si quieres que se cargue la aplicación completa en GitHub.
InicioActividad.java
public class HomeActivity extends SherlockFragmentActivity {
FragmentAdapter mAdapter;
ViewPager mPager;
TabPageIndicator mIndicator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mAdapter = new FragmentAdapter(getSupportFragmentManager());
mPager = (ViewPager)findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mIndicator = (TabPageIndicator)findViewById(R.id.indicator);
mIndicator.setViewPager(mPager);
}
// This the important bit to make sure the back button works when you're nesting fragments. Very hacky, all it takes is some Google engineer to change that ViewPager view tag to break this in a future Android update.
@Override
public void onBackPressed() {
Fragment fragment = (Fragment) getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":"+mPager.getCurrentItem());
if (fragment != null) // could be null if not instantiated yet
{
if (fragment.getView() != null) {
// Pop the backstack on the ChildManager if there is any. If not, close this activity as normal.
if (!fragment.getChildFragmentManager().popBackStackImmediate()) {
finish();
}
}
}
}
class FragmentAdapter extends FragmentPagerAdapter {
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return ListProductsFragment.newInstance();
case 1:
return ListActiveSubstancesFragment.newInstance();
case 2:
return ListProductFunctionsFragment.newInstance();
case 3:
return ListCropsFragment.newInstance();
default:
return null;
}
}
@Override
public int getCount() {
return 4;
}
}
}
ListaProductosFragmento.java
public class ListProductsFragment extends SherlockFragment {
private ListView list;
public static ListProductsFragment newInstance() {
ListProductsFragment f = new ListProductsFragment();
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View V = inflater.inflate(R.layout.list, container, false);
list = (ListView)V.findViewById(android.R.id.list);
list.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// This is important bit
Fragment productDetailFragment = FragmentProductDetail.newInstance();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.addToBackStack(null);
transaction.replace(R.id.products_list_linear, productDetailFragment).commit();
}
});
return V;
}
}