Reemplazar fragmento dentro de un ViewPager

Resuelto Noodles asked hace 54 años • 0 respuestas

Estoy intentando usar Fragment ViewPagercon 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 FirstPagerFragmentcon 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.

Noodles avatar Jan 01 '70 08:01 Noodles
Aceptado

Existe otra solución que no necesita modificar el código fuente de ViewPagery FragmentStatePagerAdaptery funciona con la FragmentPagerAdapterclase 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 ViewPagervolver a llenar las páginas, debe llamar notifyDataSetChanged()a la que reside en la clase base de su adaptador.

En segundo lugar, ViewPagerutiliza 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 ViewPagerque 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_NONEcuando 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 FirstPageFragmento 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 ViewPagery FragmentManager.

En tercer lugar, FragmentPagerAdapteralmacena 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();
}
wize avatar Feb 03 '2012 10:02 wize

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;
}
}
georgiecasey avatar Dec 18 '2012 01:12 georgiecasey