Reemplazar fragmento dentro de un ViewPager

268

Estoy tratando de usar Fragment con un ViewPageruso de FragmentPagerAdapter. Lo que estoy buscando lograr es reemplazar un fragmento, posicionado en la primera página del ViewPager, con otro.

El buscapersonas se compone de dos páginas. El primero es el FirstPagerFragment, el segundo es el SecondPagerFragment. Al hacer clic en un botón de la primera página. Me gustaría reemplazar el FirstPagerFragmentcon el 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 ... qué ID debo usar en

trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());

?

Si lo uso R.id.first_fragment_root_id, el reemplazo funciona, pero Hierarchy Viewer 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, hay algo mal, espero encontrar el mismo estado que se muestra en la primera imagen después de reemplazar el fragmento.

Tallarines
fuente
¿Generaste ese diagrama de diseño de alguna manera o lo hiciste manualmente?
Jeroen Vannevel
@Noodles: necesito hacer lo mismo. Su pregunta ha generado un hilo largo con muchas respuestas diferentes. ¿Qué funcionó mejor al final? ¡Muchas gracias!
narb
1
@JeroenVannevel es posible que ya lo haya descubierto, pero ese diagrama de diseño es developer.android.com/tools/performance/hierarchy-viewer/…
Ankit Popli
tengo algún problema, necesito tu ayuda stackoverflow.com/questions/40149039/…
albert

Respuestas:

162

Hay otra solución que no necesita modificar el código fuente de ViewPagery FragmentStatePagerAdapter, y funciona con la FragmentPagerAdapterclase base utilizada por el autor.

Me gustaría comenzar respondiendo a la pregunta del autor sobre qué identificación debe usar; es la ID del contenedor, es decir, la ID del propio buscapersonas. Sin embargo, como probablemente te hayas dado cuenta, usar esa ID en tu código no hace que suceda nada. Explicaré por qué:

En primer lugar, para ViewPagerrepoblar las páginas, debe llamar a notifyDataSetChanged()eso reside en la clase base de su adaptador.

En segundo lugar, ViewPagerutiliza el getItemPosition()método abstracto para verificar qué páginas deben destruirse y cuáles deben conservarse. La implementación predeterminada de esta función siempre regresa POSITION_UNCHANGED, lo que hace ViewPagerque se mantengan todas las páginas actuales y, en consecuencia, no se adjunte su 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 lo llama con un fragmento antiguo, oculto, como argumento.

Esto también significa que su adaptador siempre debe ser consciente de qué fragmento debe mostrarse en la posición 0, FirstPageFragmento NextFragment. Una forma de hacerlo es proporcionar un oyente al crear FirstPageFragment, que se llamará cuando sea el momento de cambiar fragmentos. Sin embargo, creo que es bueno dejar que su adaptador de fragmentos maneje todos los conmutadores de fragmentos y llamadas a ViewPagery FragmentManager.

Tercero, FragmentPagerAdapteralmacena en caché los fragmentos usados ​​por un nombre derivado de la posición, por lo que si había un fragmento en la posición 0, no se reemplazará aunque la clase sea nueva. Hay dos soluciones, pero la más simple es usar la remove()función de FragmentTransaction, que también eliminará su etiqueta.

Eso fue 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();
}

Espero que esto ayude a cualquiera!

wize
fuente
24
también funciona para mí, pero ¿cómo implementar el botón Atrás para mostrar el primer fragmento?
user1159819
66
Gran explicación, pero ¿podría publicar el código fuente completo de esto, específicamente las clases FirstPageFragment y SecondPageFragment.
georgiecasey
66
¿Cómo agrega FirstPageFragmentListener al paquete en newInstance ()?
miguel
44
¿Puedes agregar el código donde manejas con el FirstPageFragment.newInstance()parámetro oyente?
MiguelHincapieC
66
Esto solo funciona para mí si llamo commitNow () en lugar de commit () al eliminar el fragmento. No estoy seguro de por qué mi caso de uso es diferente, pero espero que esto ahorre algo de tiempo a alguien.
Ian Lovejoy
37

A partir del 13 de noviembre de 2012, la reparación de fragmentos en un 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 esto 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 extraño, utilicé esta sucia solución "android: switcher: VIEWPAGER_ID: INDEX", pero también puede realizar un seguimiento de todos los fragmentos del ViewPager usted mismo, como se explica en la segunda solución en 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. Traté de incluir solo el código relevante por razones de brevedad, así que deje un comentario si desea que la aplicación completa se cargue en GitHub.

HomeActivity.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;
    }

 }
}

ListProductsFragment.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
fuente
77
¿Te será posible subir tu código a github? Gracias.
Gautham el
44
@georgiecasey: descargué API 17, Android 4.2 que me permitió comenzar a usar getChildFragmentManager (). Pero debo estar perdiendo algo de su solución, ya que ahora estoy superponiendo fragmentos viejos + nuevos en la pantalla. Nota: Estoy tratando de usar su solución en concierto con el ejemplo del código TabsAdapter de Google de developer.android.com/reference/android/support/v4/view/… . Gracias por cualquier sugerencia y / o código de referencia.
gcl1
27
¿A qué se refiere R.id.products_list_linear? Enlace a su código tal como se le ofreció.
howettl
1
@howettl las referencias de ID al miembro de fragmento externo. La clave para resolver este problema es usar fragmentos anidados (de esa manera no tiene que manipular el adaptador del visor).
Indrek Kõue
2
¿Esto alguna vez llegó a github?
Baruch
32

Basado en la respuesta de @wize, que encontré útil y elegante, pude lograr lo que quería parcialmente, porque quería que la capacidad volviera al primer Fragmento una vez reemplazado. Lo logré modificando un poco su código.

Este sería el FragmentPagerAdapter:

public static class MyAdapter extends FragmentPagerAdapter {
    private final class CalendarPageListener implements
            CalendarPageFragmentListener {
        public void onSwitchToNextFragment() {
            mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
                    .commit();
            if (mFragmentAtPos0 instanceof FirstFragment){
                mFragmentAtPos0 = NextFragment.newInstance(listener);
            }else{ // Instance of NextFragment
                mFragmentAtPos0 = FirstFragment.newInstance(listener);
            }
            notifyDataSetChanged();
        }
    }

    CalendarPageListener listener = new CalendarPageListener();;
    private Fragment mFragmentAtPos0;
    private FragmentManager mFragmentManager;

    public MyAdapter(FragmentManager fm) {
        super(fm);
        mFragmentManager = fm;
    }

    @Override
    public int getCount() {
        return NUM_ITEMS;
    }

    @Override
    public int getItemPosition(Object object) {
        if (object instanceof FirstFragment && mFragmentAtPos0 instanceof NextFragment)
            return POSITION_NONE;
        if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstFragment)
            return POSITION_NONE;
        return POSITION_UNCHANGED;
    }

    @Override
    public Fragment getItem(int position) {
        if (position == 0)
            return Portada.newInstance();
        if (position == 1) { // Position where you want to replace fragments
            if (mFragmentAtPos0 == null) {
                mFragmentAtPos0 = FirstFragment.newInstance(listener);
            }
            return mFragmentAtPos0;
        }
        if (position == 2)
            return Clasificacion.newInstance();
        if (position == 3)
            return Informacion.newInstance();

        return null;
    }
}

public interface CalendarPageFragmentListener {
    void onSwitchToNextFragment();
}

Para realizar el reemplazo, simplemente defina un campo estático, del tipo CalendarPageFragmentListenere inicializado a través de los newInstancemétodos de los fragmentos correspondientes y llame FirstFragment.pageListener.onSwitchToNextFragment()o NextFragment.pageListener.onSwitchToNextFragment()respiretevely.

mdelolmo
fuente
¿Cómo se realiza el reemplazo? No es posible inicializar el oyente sin anular el método onSwitchToNext.
Leandros
1
¿no pierde el estado guardado en mFragmentAtPos0 si gira el dispositivo?
seb
@seb, en realidad mejoré esta solución para guardar la mFragmentAtPos0referencia al guardar el estado de la actividad. No es la solución más elegante, pero funciona.
mdelolmo
Puedes ver mi respuesta . Reemplaza los fragmentos y vuelve al primero.
Lyd
@mdelolmo ¿Podría echar un vistazo a esta pregunta sobre ViewPager? Gracias stackoverflow.com/questions/27937250/…
Hoa Vu
21

He implementado una solución para:

  • Reemplazo dinámico de fragmentos dentro de la pestaña
  • Mantenimiento del historial por pestaña
  • Trabajando con cambios de orientación

Los trucos para lograr esto son los siguientes:

  • Use el método notifyDataSetChanged () para aplicar el reemplazo del fragmento
  • Use el administrador de fragmentos solo para la etapa posterior y no para el reemplazo del fragamento
  • Mantener el historial utilizando el patrón de recuerdo (pila)

El código del adaptador es el siguiente:

public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.TabListener, ViewPager.OnPageChangeListener {

/** The sherlock fragment activity. */
private final SherlockFragmentActivity mActivity;

/** The action bar. */
private final ActionBar mActionBar;

/** The pager. */
private final ViewPager mPager;

/** The tabs. */
private List<TabInfo> mTabs = new LinkedList<TabInfo>();

/** The total number of tabs. */
private int TOTAL_TABS;

private Map<Integer, Stack<TabInfo>> history = new HashMap<Integer, Stack<TabInfo>>();

/**
 * Creates a new instance.
 *
 * @param activity the activity
 * @param pager    the pager
 */
public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
    super(activity.getSupportFragmentManager());
    activity.getSupportFragmentManager();
    this.mActivity = activity;
    this.mActionBar = activity.getSupportActionBar();
    this.mPager = pager;
    mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
}

/**
 * Adds the tab.
 *
 * @param image         the image
 * @param fragmentClass the class
 * @param args          the arguments
 */
public void addTab(final Drawable image, final Class fragmentClass, final Bundle args) {
    final TabInfo tabInfo = new TabInfo(fragmentClass, args);
    final ActionBar.Tab tab = mActionBar.newTab();
    tab.setTabListener(this);
    tab.setTag(tabInfo);
    tab.setIcon(image);

    mTabs.add(tabInfo);
    mActionBar.addTab(tab);

    notifyDataSetChanged();
}

@Override
public Fragment getItem(final int position) {
    final TabInfo tabInfo = mTabs.get(position);
    return Fragment.instantiate(mActivity, tabInfo.fragmentClass.getName(), tabInfo.args);
}

@Override
public int getItemPosition(final Object object) {
    /* Get the current position. */
    int position = mActionBar.getSelectedTab().getPosition();

    /* The default value. */
    int pos = POSITION_NONE;
    if (history.get(position).isEmpty()) {
        return POSITION_NONE;
    }

    /* Checks if the object exists in current history. */
    for (Stack<TabInfo> stack : history.values()) {
        TabInfo c = stack.peek();
        if (c.fragmentClass.getName().equals(object.getClass().getName())) {
            pos = POSITION_UNCHANGED;
            break;
        }
    }
    return pos;
}

@Override
public int getCount() {
    return mTabs.size();
}

@Override
public void onPageScrollStateChanged(int arg0) {
}

@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}

@Override
public void onPageSelected(int position) {
    mActionBar.setSelectedNavigationItem(position);
}

@Override
public void onTabSelected(final ActionBar.Tab tab, final FragmentTransaction ft) {
    TabInfo tabInfo = (TabInfo) tab.getTag();
    for (int i = 0; i < mTabs.size(); i++) {
        if (mTabs.get(i).equals(tabInfo)) {
            mPager.setCurrentItem(i);
        }
    }
}

@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}

public void replace(final int position, final Class fragmentClass, final Bundle args) {
    /* Save the fragment to the history. */
    mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();

    /* Update the tabs. */
    updateTabs(new TabInfo(fragmentClass, args), position);

    /* Updates the history. */
    history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));

    notifyDataSetChanged();
}

/**
 * Updates the tabs.
 *
 * @param tabInfo
 *          the new tab info
 * @param position
 *          the position
 */
private void updateTabs(final TabInfo tabInfo, final int position) {
    mTabs.remove(position);
    mTabs.add(position, tabInfo);
    mActionBar.getTabAt(position).setTag(tabInfo);
}

/**
 * Creates the history using the current state.
 */
public void createHistory() {
    int position = 0;
    TOTAL_TABS = mTabs.size();
    for (TabInfo mTab : mTabs) {
        if (history.get(position) == null) {
            history.put(position, new Stack<TabInfo>());
        }
        history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
        position++;
    }
}

/**
 * Called on back
 */
public void back() {
    int position = mActionBar.getSelectedTab().getPosition();
    if (!historyIsEmpty(position)) {
        /* In case there is not any other item in the history, then finalize the activity. */
        if (isLastItemInHistory(position)) {
            mActivity.finish();
        }
        final TabInfo currentTabInfo = getPrevious(position);
        mTabs.clear();
        for (int i = 0; i < TOTAL_TABS; i++) {
            if (i == position) {
                mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
            } else {
                TabInfo otherTabInfo = history.get(i).peek();
                mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
            }
        }
    }
    mActionBar.selectTab(mActionBar.getTabAt(position));
    notifyDataSetChanged();
}

/**
 * Returns if the history is empty.
 *
 * @param position
 *          the position
 * @return  the flag if empty
 */
private boolean historyIsEmpty(final int position) {
    return history == null || history.isEmpty() || history.get(position).isEmpty();
}

private boolean isLastItemInHistory(final int position) {
    return history.get(position).size() == 1;
}

/**
 * Returns the previous state by the position provided.
 *
 * @param position
 *          the position
 * @return  the tab info
 */
private TabInfo getPrevious(final int position) {
    TabInfo currentTabInfo = history.get(position).pop();
    if (!history.get(position).isEmpty()) {
        currentTabInfo = history.get(position).peek();
    }
    return currentTabInfo;
}

/** The tab info class */
private static class TabInfo {

    /** The fragment class. */
    public Class fragmentClass;

    /** The args.*/
    public Bundle args;

    /**
     * Creates a new instance.
     *
     * @param fragmentClass
     *          the fragment class
     * @param args
     *          the args
     */
    public TabInfo(Class fragmentClass, Bundle args) {
        this.fragmentClass = fragmentClass;
        this.args = args;
    }

    @Override
    public boolean equals(final Object o) {
        return this.fragmentClass.getName().equals(o.getClass().getName());
    }

    @Override
    public int hashCode() {
        return fragmentClass.getName() != null ? fragmentClass.getName().hashCode() : 0;
    }

    @Override
    public String toString() {
        return "TabInfo{" +
                "fragmentClass=" + fragmentClass +
                '}';
    }
}

La primera vez que agrega todas las pestañas, debemos llamar al método createHistory (), para crear el historial inicial

public void createHistory() {
    int position = 0;
    TOTAL_TABS = mTabs.size();
    for (TabInfo mTab : mTabs) {
        if (history.get(position) == null) {
            history.put(position, new Stack<TabInfo>());
        }
        history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
        position++;
    }
}

Cada vez que desee reemplazar un fragmento a una pestaña específica, llame a: replace (posición int final, clase final fragmentClass, args de paquete final)

/* Save the fragment to the history. */
    mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();

    /* Update the tabs. */
    updateTabs(new TabInfo(fragmentClass, args), position);

    /* Updates the history. */
    history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));

    notifyDataSetChanged();

Al presionar hacia atrás, debe llamar al método back ():

public void back() {
    int position = mActionBar.getSelectedTab().getPosition();
    if (!historyIsEmpty(position)) {
        /* In case there is not any other item in the history, then finalize the activity. */
        if (isLastItemInHistory(position)) {
            mActivity.finish();
        }
        final TabInfo currentTabInfo = getPrevious(position);
        mTabs.clear();
        for (int i = 0; i < TOTAL_TABS; i++) {
            if (i == position) {
                mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
            } else {
                TabInfo otherTabInfo = history.get(i).peek();
                mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
            }
        }
    }
    mActionBar.selectTab(mActionBar.getTabAt(position));
    notifyDataSetChanged();
}

La solución funciona con la barra de acción Sherlock y con un gesto de deslizar.

mspapant
fuente
¡es genial! ¿podrías ponerlo en el github?
Nicramus
Esta implementación es la más limpia de todas las proporcionadas aquí, y aborda todos los problemas. Debería ser la respuesta aceptada.
dazedviper
¿Alguien tuvo problemas con esta implementación? Estoy teniendo 2 de ellos. 1. la primera parte posterior no se registra; tengo que hacer clic dos veces para que se registre. 2. Después de hacer clic de nuevo (dos veces) en la pestaña a y voy a la pestaña b, cuando vuelvo a la pestaña a solo muestra el contenido de la pestaña b
yaronbic
7

tl; dr: utilice un fragmento de host que sea responsable de reemplazar su contenido alojado y realice un seguimiento del historial de navegación hacia atrás (como en un navegador).

Como su caso de uso consiste en una cantidad fija de pestañas, mi solución funciona bien: la idea es llenar ViewPager con instancias de una clase personalizada HostFragment, que pueda reemplazar su contenido alojado y mantenga su propio historial de navegación hacia atrás. Para reemplazar el fragmento alojado, realice una llamada al método hostfragment.replaceFragment():

public void replaceFragment(Fragment fragment, boolean addToBackstack) {
    if (addToBackstack) {
        getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
    } else {
        getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
    }
}

Todo lo que hace el método es reemplazar el diseño del marco con la identificación R.id.hosted_fragmentcon el fragmento proporcionado al método.

¡Consulte mi tutorial sobre este tema para obtener más detalles y un ejemplo de trabajo completo en GitHub!

Marktani
fuente
Aunque esto parece más torpe, creo que es la mejor manera (en términos de estructura y facilidad de mantenimiento) para hacerlo.
Sira Lam
Termino golpeándome un NPE. : / No puedo encontrar la vista que se va a reemplazar
Fariz Fakkel
6

Algunas de las soluciones presentadas me ayudaron mucho a resolver parcialmente el problema, pero todavía falta una cosa importante en las soluciones que ha producido excepciones inesperadas y contenido de página negra en lugar de contenido fragmentado en algunos casos.

El caso es que la clase FragmentPagerAdapter está utilizando la ID del elemento para almacenar fragmentos almacenados en caché en FragmentManager . Por esta razón, debe anular también el método getItemId (int position) para que devuelva, por ejemplo, posición para páginas de nivel superior y 100 + posición para páginas de detalles. De lo contrario, el fragmento de nivel superior creado anteriormente se devolvería de la memoria caché en lugar del fragmento de nivel de detalle.

Además, estoy compartiendo aquí un ejemplo completo de cómo implementar una actividad similar a pestañas con páginas Fragment usando ViewPager y botones de pestañas usando RadioGroup que permite el reemplazo de páginas de nivel superior con páginas detalladas y también admite el botón de retroceso. Esta implementación admite solo un nivel de apilamiento posterior (lista de elementos - detalles del elemento) pero la implementación de apilamiento posterior de varios niveles es sencilla. Este ejemplo funciona bastante bien en casos normales, excepto que está lanzando una NullPointerException en caso de que cambie, por ejemplo, a la segunda página, cambie el fragmento de la primera página (aunque no sea visible) y regrese a la primera página. Publicaré una solución a este problema una vez que lo resuelva:

public class TabsActivity extends FragmentActivity {

  public static final int PAGE_COUNT = 3;
  public static final int FIRST_PAGE = 0;
  public static final int SECOND_PAGE = 1;
  public static final int THIRD_PAGE = 2;

  /**
   * Opens a new inferior page at specified tab position and adds the current page into back
   * stack.
   */
  public void startPage(int position, Fragment content) {
    // Replace page adapter fragment at position.
    mPagerAdapter.start(position, content);
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Initialize basic layout.
    this.setContentView(R.layout.tabs_activity);

    // Add tab fragments to view pager.
    {
      // Create fragments adapter.
      mPagerAdapter = new PagerAdapter(pager);
      ViewPager pager = (ViewPager) super.findViewById(R.id.tabs_view_pager);
      pager.setAdapter(mPagerAdapter);

      // Update active tab in tab bar when page changes.
      pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int index, float value, int nextIndex) {
          // Not used.
        }

        @Override
        public void onPageSelected(int index) {
          RadioGroup tabs_radio_group = (RadioGroup) TabsActivity.this.findViewById(
            R.id.tabs_radio_group);
          switch (index) {
            case 0: {
              tabs_radio_group.check(R.id.first_radio_button);
            }
            break;
            case 1: {
              tabs_radio_group.check(R.id.second_radio_button);
            }
            break;
            case 2: {
              tabs_radio_group.check(R.id.third_radio_button);
            }
            break;
          }
        }

        @Override
        public void onPageScrollStateChanged(int index) {
          // Not used.
        }
      });
    }

    // Set "tabs" radio group on checked change listener that changes the displayed page.
    RadioGroup radio_group = (RadioGroup) this.findViewById(R.id.tabs_radio_group);
    radio_group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
      @Override
      public void onCheckedChanged(RadioGroup radioGroup, int id) {
        // Get view pager representing tabs.
        ViewPager view_pager = (ViewPager) TabsActivity.this.findViewById(R.id.tabs_view_pager);
        if (view_pager == null) {
          return;
        }

        // Change the active page.
        switch (id) {
          case R.id.first_radio_button: {
            view_pager.setCurrentItem(FIRST_PAGE);
          }
          break;
          case R.id.second_radio_button: {
            view_pager.setCurrentItem(SECOND_PAGE);
          }
          break;
          case R.id.third_radio_button: {
            view_pager.setCurrentItem(THIRD_PAGE);
          }
          break;
        }
      });
    }
  }

  @Override
  public void onBackPressed() {
    if (!mPagerAdapter.back()) {
      super.onBackPressed();
    }
  }

  /**
   * Serves the fragments when paging.
   */
  private class PagerAdapter extends FragmentPagerAdapter {

    public PagerAdapter(ViewPager container) {
      super(TabsActivity.this.getSupportFragmentManager());

      mContainer = container;
      mFragmentManager = TabsActivity.this.getSupportFragmentManager();

      // Prepare "empty" list of fragments.
      mFragments = new ArrayList<Fragment>(){};
      mBackFragments = new ArrayList<Fragment>(){};
      for (int i = 0; i < PAGE_COUNT; i++) {
        mFragments.add(null);
        mBackFragments.add(null);
      }
    }

    /**
     * Replaces the view pager fragment at specified position.
     */
    public void replace(int position, Fragment fragment) {
      // Get currently active fragment.
      Fragment old_fragment = mFragments.get(position);
      if (old_fragment == null) {
        return;
      }

      // Replace the fragment using transaction and in underlaying array list.
      // NOTE .addToBackStack(null) doesn't work
      this.startUpdate(mContainer);
      mFragmentManager.beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
        .remove(old_fragment).add(mContainer.getId(), fragment)
        .commit();
      mFragments.set(position, fragment);
      this.notifyDataSetChanged();
      this.finishUpdate(mContainer);
    }

    /**
     * Replaces the fragment at specified position and stores the current fragment to back stack
     * so it can be restored by #back().
     */
    public void start(int position, Fragment fragment) {
      // Remember current fragment.
      mBackFragments.set(position, mFragments.get(position));

      // Replace the displayed fragment.
      this.replace(position, fragment);
    }

    /**
     * Replaces the current fragment by fragment stored in back stack. Does nothing and returns
     * false if no fragment is back-stacked.
     */
    public boolean back() {
      int position = mContainer.getCurrentItem();
      Fragment fragment = mBackFragments.get(position);
      if (fragment == null) {
        // Nothing to go back.
        return false;
      }

      // Restore the remembered fragment and remove it from back fragments.
      this.replace(position, fragment);
      mBackFragments.set(position, null);
      return true;
    }

    /**
     * Returns fragment of a page at specified position.
     */
    @Override
    public Fragment getItem(int position) {
      // If fragment not yet initialized, create its instance.
      if (mFragments.get(position) == null) {
        switch (position) {
          case FIRST_PAGE: {
            mFragments.set(FIRST_PAGE, new DefaultFirstFragment());
          }
          break;
          case SECOND_PAGE: {
            mFragments.set(SECOND_PAGE, new DefaultSecondFragment());
          }
          break;
          case THIRD_PAGE: {
            mFragments.set(THIRD_PAGE, new DefaultThirdFragment());
          }
          break;
        }
      }

      // Return fragment instance at requested position.
      return mFragments.get(position);
    }

    /**
     * Custom item ID resolution. Needed for proper page fragment caching.
     * @see FragmentPagerAdapter#getItemId(int).
     */
    @Override
    public long getItemId(int position) {
      // Fragments from second level page hierarchy have their ID raised above 100. This is
      // important to FragmentPagerAdapter because it is caching fragments to FragmentManager with
      // this item ID key.
      Fragment item = mFragments.get(position);
      if (item != null) {
        if ((item instanceof NewFirstFragment) || (item instanceof NewSecondFragment) ||
          (item instanceof NewThirdFragment)) {
          return 100 + position;
        }
      }

      return position;
    }

    /**
     * Returns number of pages.
     */
    @Override
    public int getCount() {
      return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object)
    {
      int position = POSITION_UNCHANGED;
      if ((object instanceof DefaultFirstFragment) || (object instanceof NewFirstFragment)) {
        if (object.getClass() != mFragments.get(FIRST_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      if ((object instanceof DefaultSecondragment) || (object instanceof NewSecondFragment)) {
        if (object.getClass() != mFragments.get(SECOND_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      if ((object instanceof DefaultThirdFragment) || (object instanceof NewThirdFragment)) {
        if (object.getClass() != mFragments.get(THIRD_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      return position;
    }

    private ViewPager mContainer;
    private FragmentManager mFragmentManager;

    /**
     * List of page fragments.
     */
    private List<Fragment> mFragments;

    /**
     * List of page fragments to return to in onBack();
     */
    private List<Fragment> mBackFragments;
  }

  /**
   * Tab fragments adapter.
   */
  private PagerAdapter mPagerAdapter;
}
Blackhex
fuente
Hmm, hay otro problema con este código. Cuando deseo iniciar una actividad desde un fragmento que se eliminó y se agregó nuevamente, aparece un error: "Estado de ahorro de falla: NewFirstFragment activo {405478a0} ha borrado el índice: -1" (seguimiento de pila completa: nopaste.info/547a23dfea.html ) Mirando a las fuentes de Android, me doy cuenta de que tiene algo que ver con backstack, pero no sé cómo solucionarlo hasta ahora. ¿Alguien tiene una pista?
Blackhex
2
Genial, usando <code> mFragmentManager.beginTransaction (). Detach (old_fragment) .attach (fragment) .commit (); </code> en lugar de <pre> mFragmentManager.beginTransaction (). SetTransition (FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .remove ( old_fragment) .add (mContainer.getId (), fragment) .commit (); </pre> en el ejemplo anterior parece solucionar los dos problemas :-).
Blackhex
1
Si usa este método, asegúrese de no permitir que Android vuelva a crear su actividad al girar el dispositivo. De lo contrario, el PagerAdapter se volverá a crear y perderá sus mBackFragments. Esto también confundirá seriamente al FragmentManager. El uso de BackStack evita este problema, a expensas de una implementación más complicada de PagerAdapter (es decir, no se puede heredar de FragmentPagerAdapter).
Norman
6

He creado un ViewPager con 3 elementos y 2 subelementos para el índice 2 y 3 y aquí lo que quería hacer.

ingrese la descripción de la imagen aquí

He implementado esto con la ayuda de preguntas y respuestas anteriores de StackOverFlow y aquí está el enlace.

ViewPagerChildFragments

Rukmal Dias
fuente
Su proyecto falla, si gira el dispositivo.
Dory
6

Para reemplazar un fragmento dentro de una ViewPagerque se puede mover códigos fuente de ViewPager, PagerAdaptery FragmentStatePagerAdapterclases en su proyecto y añadir siguiente código.

en ViewPager:

public void notifyItemChanged(Object oldItem, Object newItem) {
    if (mItems != null) {
            for (ItemInfo itemInfo : mItems) {
                        if (itemInfo.object.equals(oldItem)) {
                                itemInfo.object = newItem;
                        }
                    }
       }
       invalidate();
    }

en FragmentStatePagerAdapter:

public void replaceFragmetns(ViewPager container, Fragment oldFragment, Fragment newFragment) {
       startUpdate(container);

       // remove old fragment

       if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
       int position = getFragmentPosition(oldFragment);
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, null);
        mFragments.set(position, null);

        mCurTransaction.remove(oldFragment);

        // add new fragment

        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        mFragments.set(position, newFragment);
        mCurTransaction.add(container.getId(), newFragment);

       finishUpdate(container);

       // ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
       handleGetItemInbalidated(container, oldFragment, newFragment);

       container.notifyItemChanged(oldFragment, newFragment);
    }

protected abstract void handleGetItemInbalidated(View container, Fragment oldFragment, Fragment newFragment);
protected abstract int  getFragmentPosition(Fragment fragment);

handleGetItemInvalidated()asegura que después de la próxima llamada getItem()devuelva newFragment getFragmentPosition()devuelve la posición del fragmento en su adaptador.

Ahora, para reemplazar fragmentos llame

mAdapter.replaceFragmetns(mViewPager, oldFragment, newFragment);

Si está interesado en un proyecto de ejemplo, solicite las fuentes.

AndroidTeam At Mail.Ru
fuente
¿Cómo deberían ser los métodos getFragmentPosition () y handleGetItemInbalidated ()? Soy incapaz de actualizar getItem () para devolver NewFragment .. Por favor ayuda ..
¡Hola! Encuentre el proyecto de prueba en este enlace files.mail.ru/eng?back=%2FEZU6G4
AndroidTeam At Mail.Ru
1
Su proyecto de prueba falla, si gira el dispositivo.
seb
1
@ AndroidTeamAtMail.Ru el enlace parece haber caducado. ¿Podrían compartir el código?
Sunny
Puedes ver mi respuesta . Reemplaza los fragmentos y vuelve al primero.
Lyd
4

Funciona muy bien con la solución de AndroidTeam, sin embargo, descubrí que necesitaba la capacidad de retroceder de manera muy similar, FrgmentTransaction.addToBackStack(null) pero simplemente agregar esto solo hará que el Fragmento sea reemplazado sin notificar al ViewPager. La combinación de la solución provista con esta mejora menor le permitirá volver al estado anterior simplemente anulando el onBackPressed()método de la actividad . El mayor inconveniente es que solo retrocederá uno a la vez, lo que puede generar múltiples clics de retroceso

private ArrayList<Fragment> bFragments = new ArrayList<Fragment>();
private ArrayList<Integer> bPosition = new ArrayList<Integer>();

public void replaceFragmentsWithBackOut(ViewPager container, Fragment oldFragment, Fragment newFragment) {
    startUpdate(container);

    // remove old fragment

    if (mCurTransaction == null) {
         mCurTransaction = mFragmentManager.beginTransaction();
     }
    int position = getFragmentPosition(oldFragment);
     while (mSavedState.size() <= position) {
         mSavedState.add(null);
     }

     //Add Fragment to Back List
     bFragments.add(oldFragment);

     //Add Pager Position to Back List
     bPosition.add(position);

     mSavedState.set(position, null);
     mFragments.set(position, null);

     mCurTransaction.remove(oldFragment);

     // add new fragment

     while (mFragments.size() <= position) {
         mFragments.add(null);
     }
     mFragments.set(position, newFragment);
     mCurTransaction.add(container.getId(), newFragment);

    finishUpdate(container);

    // ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
    handleGetItemInvalidated(container, oldFragment, newFragment);

    container.notifyItemChanged(oldFragment, newFragment);
 }


public boolean popBackImmediate(ViewPager container){
    int bFragSize = bFragments.size();
    int bPosSize = bPosition.size();

    if(bFragSize>0 && bPosSize>0){
        if(bFragSize==bPosSize){
            int last = bFragSize-1;
            int position = bPosition.get(last);

            //Returns Fragment Currently at this position
            Fragment replacedFragment = mFragments.get(position);               
            Fragment originalFragment = bFragments.get(last);

            this.replaceFragments(container, replacedFragment, originalFragment);

            bPosition.remove(last);
            bFragments.remove(last);

            return true;
        }
    }

    return false;       
}

Espero que esto ayude a alguien.

Además, en lo que respecta, getFragmentPosition()es más o menos getItem()a la inversa. Usted sabe qué fragmentos van a dónde, solo asegúrese de devolver la posición correcta en la que estará. Aquí hay un ejemplo:

    @Override
    protected int getFragmentPosition(Fragment fragment) {
            if(fragment.equals(originalFragment1)){
                return 0;
            }
            if(fragment.equals(replacementFragment1)){
                return 0;
            }
            if(fragment.equals(Fragment2)){
                return 1;
            }
        return -1;
    }
conocedor
fuente
3

En su onCreateViewmétodo, en containerrealidad es una ViewPagerinstancia.

Entonces, solo llamando

ViewPager vpViewPager = (ViewPager) container;
vpViewPager.setCurrentItem(1);

cambiará el fragmento actual en su ViewPager.

Ivan Bajalovic
fuente
1
Después de horas de búsqueda, era esta línea de código la que necesitaba para que todas mis pestañas funcionaran correctamente. vpViewPager.setCurrentItem(1);. Revisé el ejemplo de cada persona, sin que pasara nada cada vez hasta que finalmente llegara al tuyo. Gracias.
Brandon
1
alucinante ... esto es increíble !! No me di cuenta de que la variable contenedor era el visor en sí. Esta respuesta tiene que ser explorada primero antes que todas las otras largas. Además, las variables de escucha no se transmiten al constructor de fragmentos, lo que simplifica toda la implementación cuando Android recrea automáticamente fragmentos utilizando el constructor predeterminado sin argumentos cuando la configuración cambia.
Kevin Lee
3
¿No es solo cambiar a una pestaña existente? ¿Cómo se relaciona esto con el reemplazo de fragmentos?
Behelit
2
De acuerdo con @behelit, esto no responde a la pregunta, solo moverá la ViewPagerpágina a otra, no reemplazará ningún fragmento.
Yoann Hercouet
2

Aquí está mi solución relativamente simple para este problema. Las claves de esta solución son usarlas en FragmentStatePagerAdapterlugar de usarlas, FragmentPagerAdapterya que la primera eliminará los fragmentos no utilizados por usted mientras que la segunda aún conserva sus instancias. El segundo es el uso de POSITION_NONEin getItem (). He usado una lista simple para hacer un seguimiento de mis fragmentos. Mi requisito era reemplazar toda la lista de fragmentos a la vez con una nueva lista, pero lo siguiente podría modificarse fácilmente para reemplazar fragmentos individuales:

public class MyFragmentAdapter extends FragmentStatePagerAdapter {
    private List<Fragment> fragmentList = new ArrayList<Fragment>();
    private List<String> tabTitleList = new ArrayList<String>();

    public MyFragmentAdapter(FragmentManager fm) {
        super(fm);
    }

    public void addFragments(List<Fragment> fragments, List<String> titles) {
        fragmentList.clear();
        tabTitleList.clear();
        fragmentList.addAll(fragments);
        tabTitleList.addAll(titles);
        notifyDataSetChanged();
    }

    @Override
    public int getItemPosition(Object object) {
        if (fragmentList.contains(object)) {
            return POSITION_UNCHANGED;
        }
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int item) {
        if (item >= fragmentList.size()) {
            return null;
        }
        return fragmentList.get(item);
    }

    @Override
    public int getCount() {
        return fragmentList.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return tabTitleList.get(position);
    }
}
Chris Knight
fuente
2

También hice una solución, que está trabajando con Stacks . Es un enfoque más modular , por lo que no tiene que especificar cada Fragmento y Fragmento de detalle en su FragmentPagerAdapter. Se basa en el ejemplo de ActionbarSherlock que se deriva si estoy en la aplicación de demostración de Google.

/**
 * This is a helper class that implements the management of tabs and all
 * details of connecting a ViewPager with associated TabHost.  It relies on a
 * trick.  Normally a tab host has a simple API for supplying a View or
 * Intent that each tab will show.  This is not sufficient for switching
 * between pages.  So instead we make the content part of the tab host
 * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
 * view to show as the tab content.  It listens to changes in tabs, and takes
 * care of switch to the correct paged in the ViewPager whenever the selected
 * tab changes.
 * 
 * Changed to support more Layers of fragments on each Tab.
 * by sebnapi (2012)
 * 
 */
public class TabsAdapter extends FragmentPagerAdapter
        implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
    private final Context mContext;
    private final TabHost mTabHost;
    private final ViewPager mViewPager;

    private ArrayList<String> mTabTags = new ArrayList<String>();
    private HashMap<String, Stack<TabInfo>> mTabStackMap = new HashMap<String, Stack<TabInfo>>();

    static final class TabInfo {
        public final String tag;
        public final Class<?> clss;
        public Bundle args;

        TabInfo(String _tag, Class<?> _class, Bundle _args) {
            tag = _tag;
            clss = _class;
            args = _args;
        }
    }

    static class DummyTabFactory implements TabHost.TabContentFactory {
        private final Context mContext;

        public DummyTabFactory(Context context) {
            mContext = context;
        }

        @Override
        public View createTabContent(String tag) {
            View v = new View(mContext);
            v.setMinimumWidth(0);
            v.setMinimumHeight(0);
            return v;
        }
    }

    public interface SaveStateBundle{
        public Bundle onRemoveFragment(Bundle outState);
    }

    public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
        super(activity.getSupportFragmentManager());
        mContext = activity;
        mTabHost = tabHost;
        mViewPager = pager;
        mTabHost.setOnTabChangedListener(this);
        mViewPager.setAdapter(this);
        mViewPager.setOnPageChangeListener(this);
    }

    /**
     * Add a Tab which will have Fragment Stack. Add Fragments on this Stack by using
     * addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args)
     * The Stack will hold always the default Fragment u add here.
     * 
     * DON'T ADD Tabs with same tag, it's not beeing checked and results in unexpected
     * beahvior.
     * 
     * @param tabSpec
     * @param clss
     * @param args
     */
    public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args){
        Stack<TabInfo> tabStack = new Stack<TabInfo>();

        tabSpec.setContent(new DummyTabFactory(mContext));
        mTabHost.addTab(tabSpec);
        String tag = tabSpec.getTag();
        TabInfo info = new TabInfo(tag, clss, args);

        mTabTags.add(tag);                  // to know the position of the tab tag 
        tabStack.add(info);
        mTabStackMap.put(tag, tabStack);
        notifyDataSetChanged();
    }

    /**
     * Will add the Fragment to Tab with the Tag _tag. Provide the Class of the Fragment
     * it will be instantiated by this object. Proivde _args for your Fragment.
     * 
     * @param fm
     * @param _tag
     * @param _class
     * @param _args
     */
    public void addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args){
        TabInfo info = new TabInfo(_tag, _class, _args);
        Stack<TabInfo> tabStack = mTabStackMap.get(_tag);   
        Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
        if(frag instanceof SaveStateBundle){
            Bundle b = new Bundle();
            ((SaveStateBundle) frag).onRemoveFragment(b);
            tabStack.peek().args = b;
        }
        tabStack.add(info);
        FragmentTransaction ft = fm.beginTransaction();
        ft.remove(frag).commit();
        notifyDataSetChanged();
    }

    /**
     * Will pop the Fragment added to the Tab with the Tag _tag
     * 
     * @param fm
     * @param _tag
     * @return
     */
    public boolean popFragment(FragmentManager fm, String _tag){
        Stack<TabInfo> tabStack = mTabStackMap.get(_tag);   
        if(tabStack.size()>1){
            tabStack.pop();
            Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
            FragmentTransaction ft = fm.beginTransaction();
            ft.remove(frag).commit();
            notifyDataSetChanged();
            return true;
        }
        return false;
    }

    public boolean back(FragmentManager fm) {
        int position = mViewPager.getCurrentItem();
        return popFragment(fm, mTabTags.get(position));
    }

    @Override
    public int getCount() {
        return mTabStackMap.size();
    }

    @Override
    public int getItemPosition(Object object) {
        ArrayList<Class<?>> positionNoneHack = new ArrayList<Class<?>>();
        for(Stack<TabInfo> tabStack: mTabStackMap.values()){
            positionNoneHack.add(tabStack.peek().clss);
        }   // if the object class lies on top of our stacks, we return default
        if(positionNoneHack.contains(object.getClass())){
            return POSITION_UNCHANGED;
        }
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int position) {
        Stack<TabInfo> tabStack = mTabStackMap.get(mTabTags.get(position));
        TabInfo info = tabStack.peek();
        return Fragment.instantiate(mContext, info.clss.getName(), info.args);
    }

    @Override
    public void onTabChanged(String tabId) {
        int position = mTabHost.getCurrentTab();
        mViewPager.setCurrentItem(position);
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int position) {
        // Unfortunately when TabHost changes the current tab, it kindly
        // also takes care of putting focus on it when not in touch mode.
        // The jerk.
        // This hack tries to prevent this from pulling focus out of our
        // ViewPager.
        TabWidget widget = mTabHost.getTabWidget();
        int oldFocusability = widget.getDescendantFocusability();
        widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
        mTabHost.setCurrentTab(position);
        widget.setDescendantFocusability(oldFocusability);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }

}

Agregue esto para la funcionalidad del botón de retroceso en su actividad principal:

@Override
public void onBackPressed() {
  if (!mTabsAdapter.back(getSupportFragmentManager())) {
    super.onBackPressed();
  }
}

Si desea guardar el estado del fragmento cuando se elimina. Deje que su Fragment implemente la interfaz y SaveStateBundledevuelva en la función un paquete con su estado de guardado. Obtenga el paquete después de la creación de instancias this.getArguments().

Puede crear una instancia de una pestaña como esta:

mTabsAdapter.addTab(mTabHost.newTabSpec("firstTabTag").setIndicator("First Tab Title"),
                FirstFragmentActivity.FirstFragmentFragment.class, null);

funciona de manera similar si desea agregar un Fragmento en la parte superior de una Pila de fichas. Importante : creo que no funcionará si desea tener 2 instancias de la misma clase en la parte superior de dos pestañas. Hicimos esta solución rápidamente juntos, por lo que solo puedo compartirla sin proporcionar ninguna experiencia con ella.

seb
fuente
Recibo una excepción de puntero nulo cuando presiono el botón de retroceso y mi fragmento reemplazado tampoco es visible. Entonces, tal vez estoy haciendo algo mal: ¿cuál debería ser la llamada correcta para cambiar los fragmentos correctamente?
Jeroen
Actualización: tengo la mayoría de las cosas arregladas, sin embargo, si presiono un botón de retroceso después de agregar un nuevo fragmento y luego voy a otra pestaña y regreso, obtengo una excepción de puntero nulo. Entonces tengo 2 preguntas: 1: ¿cómo puedo eliminar el historial de una pestaña y asegurarme de que solo esté presente el último fragmento (parte superior de la pila)? 2: ¿cómo puedo deshacerme de la excepción de puntero nulo?
Jeroen
@Jeroen 1: puede obtener la pila correcta de mTabStackMap, y luego eliminar todos los objetos TabInfo que se encuentran debajo de la parte superior de la pila, 2: muy difícil de decir, hacer una pregunta con el stacktrace y luego podría tratar de averiguarlo. ¿Seguro que no es una falla en su código? ¿Esto sucede también si cambias a una pestaña (y luego regresas) que está en el vecindario directo (derecha o izquierda)?
seb
Ok, me encontré con un nuevo problema: si giro mi pantalla, ¿entonces la pila parece estar vacía nuevamente? Parece perder el rastro de los elementos anteriores ...
Jeroen
1
@seb gracias. Estaba usando este código, pero los fragmentos anidados ahora son posibles, por lo que ya no es un problema. :)
Garcon
2

Reemplazar fragmentos en un visor es bastante complicado, pero es muy posible y puede verse muy elegante. Primero, debe dejar que el visor mismo se encargue de eliminar y agregar los fragmentos. Lo que sucede es que cuando reemplaza el fragmento dentro de SearchFragment, su visor retiene sus vistas de fragmentos. Entonces terminas con una página en blanco porque el SearchFragment se elimina cuando intentas reemplazarlo.

La solución es crear un oyente dentro de su visor que maneje los cambios realizados fuera de él, así que primero agregue este código a la parte inferior de su adaptador.

public interface nextFragmentListener {
    public void fragment0Changed(String newFragmentIdentification);
}

Luego, debe crear una clase privada en su visor que se convierta en un oyente para cuando desee cambiar su fragmento. Por ejemplo, podría agregar algo como esto. Tenga en cuenta que implementa la interfaz que acaba de crear. Entonces, cada vez que llame a este método, ejecutará el código dentro de la clase a continuación.

private final class fragmentChangeListener implements nextFragmentListener {


    @Override
    public void fragment0Changed(String fragment) {
        //I will explain the purpose of fragment0 in a moment
        fragment0 = fragment;
        manager.beginTransaction().remove(fragAt0).commit();

        switch (fragment){
            case "searchFragment":
                fragAt0 = SearchFragment.newInstance(listener);
                break;
            case "searchResultFragment":
                fragAt0 = Fragment_Table.newInstance(listener);
                break;
        }

        notifyDataSetChanged();
    }

Hay dos cosas principales que señalar aquí:

  1. fragAt0 es un fragmento "flexible". Puede tomar cualquier tipo de fragmento que le des. Esto le permite convertirse en su mejor amigo para cambiar el fragmento en la posición 0 al fragmento que desee.
  2. Observe los oyentes que se colocan en el constructor 'newInstance (oyente). Así es como llamarás a fragment0Changed (String newFragmentIdentification) `. El siguiente código muestra cómo se crea el oyente dentro de su fragmento.

    static nextFragmentListener listenerSearch;

        public static Fragment_Journals newInstance(nextFragmentListener listener){
                listenerSearch = listener;
                return new Fragment_Journals();
            }

Podrías llamar al cambio dentro de tu onPostExecute

private class SearchAsyncTask extends AsyncTask<Void, Void, Void>{

    protected Void doInBackground(Void... params){
        .
        .//some more operation
        .
    }
    protected void onPostExecute(Void param){

        listenerSearch.fragment0Changed("searchResultFragment");
    }

}

Esto activaría el código dentro de su visor para cambiar su fragmento en la posición cero fragAt0 para convertirse en un nuevo searchResultFragment. Hay dos piezas más pequeñas que necesitará agregar al visor antes de que sea funcional.

Uno estaría en el método de anulación getItem del visor.

@Override
public Fragment getItem(int index) {

    switch (index) {
    case 0:
        //this is where it will "remember" which fragment you have just selected. the key is to set a static String fragment at the top of your page that will hold the position that you had just selected.  

        if(fragAt0 == null){

            switch(fragment0){
            case "searchFragment":
                fragAt0 = FragmentSearch.newInstance(listener);
                break;
            case "searchResultsFragment":
                fragAt0 = FragmentSearchResults.newInstance(listener);
                break;
            }
        }
        return fragAt0;
    case 1:
        // Games fragment activity
        return new CreateFragment();

    }

Ahora, sin esta pieza final, todavía obtendría una página en blanco. Un poco cojo, pero es una parte esencial de viewPager. Debe anular el método getItemPosition del visor. Normalmente, este método devolverá POSITION_UNCHANGED, que le indica al visor que mantenga todo igual y, por lo tanto, getItem nunca será llamado para colocar el nuevo fragmento en la página. Aquí hay un ejemplo de algo que podrías hacer

public int getItemPosition(Object object)
{
    //object is the current fragment displayed at position 0.  
    if(object instanceof SearchFragment && fragAt0 instanceof SearchResultFragment){
        return POSITION_NONE;
    //this condition is for when you press back
    }else if{(object instanceof SearchResultFragment && fragAt0 instanceof SearchFragment){
        return POSITION_NONE;
    }
        return POSITION_UNCHANGED
}

Como dije, el código se involucra mucho, pero básicamente tienes que crear un adaptador personalizado para tu situación. Las cosas que mencioné permitirán cambiar el fragmento. Probablemente tomará mucho tiempo empapar todo para que sea paciente, pero todo tendrá sentido. Vale la pena tomarse el tiempo porque puede hacer una aplicación realmente elegante.

Aquí está la pepita para manejar el botón Atrás. Pones esto dentro de tu actividad principal

 public void onBackPressed() {
    if(mViewPager.getCurrentItem() == 0) {
        if(pagerAdapter.getItem(0) instanceof FragmentSearchResults){
            ((Fragment_Table) pagerAdapter.getItem(0)).backPressed();
        }else if (pagerAdapter.getItem(0) instanceof FragmentSearch) {
            finish();
        }
    }

Deberá crear un método llamado backPressed () dentro de FragmentSearchResults que llame a fragment0changed. Esto junto con el código que mostré antes manejará presionar el botón Atrás. Buena suerte con tu código para cambiar el visor. Requiere mucho trabajo y, por lo que he encontrado, no hay adaptaciones rápidas. Como dije, básicamente estás creando un adaptador de visor personalizado y permitiéndote manejar todos los cambios necesarios usando escuchas

usuario3331142
fuente
Gracias por esta solución Pero, ¿ha tenido algún problema después de girar la pantalla? Mi aplicación se bloquea cuando giro la pantalla e intento volver a cargar otro fragmento en la misma pestaña.
AnteGemini
Ha pasado un tiempo desde que hice el desarrollo de Android, pero supongo que hay una pieza de datos que ya no está allí con la que cuenta tu aplicación. Cuando gira, toda la actividad se reinicia y puede provocar bloqueos si intenta volver a cargar su estado anterior sin todos los datos que necesita (por ejemplo, del paquete en onPause, onResume)
usuario3331142
2

Esta es mi forma de lograr eso.

En primer lugar, agregue la pestaña Root_fragmentinterior viewPageren la que desea implementar el fragmentevento de clic de botón . Ejemplo;

@Override
public Fragment getItem(int position) {
  if(position==0)
      return RootTabFragment.newInstance();
  else
      return SecondPagerFragment.newInstance();
}

En primer lugar, RootTabFragmentdebe incluirse FragmentLayoutpara el cambio de fragmentos.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/root_frame"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
</FrameLayout>

Luego, adentro RootTabFragment onCreateView, implemente fragmentChangepara suFirstPagerFragment

getChildFragmentManager().beginTransaction().replace(R.id.root_frame, FirstPagerFragment.newInstance()).commit();

Después de eso, implemente el onClickevento para su botón dentro FirstPagerFragmenty haga un cambio de fragmento como ese nuevamente.

getChildFragmentManager().beginTransaction().replace(R.id.root_frame, NextFragment.newInstance()).commit();

Espero que esto te ayude, chico.

Min Khant Lu
fuente
1

Encontré una solución simple, que funciona bien incluso si desea agregar nuevos fragmentos en el medio o reemplazar el fragmento actual. En mi solución, debería anular lo getItemId()que debería devolver una identificación única para cada fragmento. No se posiciona como por defecto.

Ahí está:

public class DynamicPagerAdapter extends FragmentPagerAdapter {

private ArrayList<Page> mPages = new ArrayList<Page>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

public DynamicPagerAdapter(FragmentManager fm) {
    super(fm);
}

public void replacePage(int position, Page page) {
    mPages.set(position, page);
    notifyDataSetChanged();
}

public void setPages(ArrayList<Page> pages) {
    mPages = pages;
    notifyDataSetChanged();
}

@Override
public Fragment getItem(int position) {
    if (mPages.get(position).mPageType == PageType.FIRST) {
        return FirstFragment.newInstance(mPages.get(position));
    } else {
        return SecondFragment.newInstance(mPages.get(position));
    }
}

@Override
public int getCount() {
    return mPages.size();
}

@Override
public long getItemId(int position) {
    // return unique id
    return mPages.get(position).getId();
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment fragment = (Fragment) super.instantiateItem(container, position);
    while (mFragments.size() <= position) {
        mFragments.add(null);
    }
    mFragments.set(position, fragment);
    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    mFragments.set(position, null);
}

@Override
public int getItemPosition(Object object) {
    PagerFragment pagerFragment = (PagerFragment) object;
    Page page = pagerFragment.getPage();
    int position = mFragments.indexOf(pagerFragment);
    if (page.equals(mPages.get(position))) {
        return POSITION_UNCHANGED;
    } else {
        return POSITION_NONE;
    }
}
}

Aviso: en este ejemplo FirstFragmenty SecondFragmentextiende la clase abstracta PageFragment, que tiene método getPage().

Sergei Vasilenko
fuente
1

Estoy haciendo algo similar a Wize, pero en mi respuesta puedes cambiar entre los dos fragmentos cuando quieras. Y con la respuesta wize tengo algunos problemas al cambiar la orientación de la pantalla y cosas así. Así se ve el PagerAdapter:

    public class MyAdapter extends FragmentPagerAdapter
{
    static final int NUM_ITEMS = 2;
    private final FragmentManager mFragmentManager;
    private Fragment mFragmentAtPos0;
     private Map<Integer, String> mFragmentTags;
     private boolean isNextFragment=false;

    public MyAdapter(FragmentManager fm)
    {
        super(fm);
        mFragmentManager = fm;
         mFragmentTags = new HashMap<Integer, String>();
    }

    @Override
    public Fragment getItem(int position)
    {
        if (position == 0)
        {


            if (isPager) {
                mFragmentAtPos0 = new FirstPageFragment();
            } else {
                mFragmentAtPos0 = new NextFragment();
            }
            return mFragmentAtPos0;
        }
        else
            return SecondPageFragment.newInstance();
    }

    @Override
    public int getCount()
    {
        return NUM_ITEMS;
    }


 @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object obj = super.instantiateItem(container, position);
        if (obj instanceof Fragment) {
            // record the fragment tag here.
            Fragment f = (Fragment) obj;
            String tag = f.getTag();
            mFragmentTags.put(position, tag);
        }
        return obj;
    }


    public void onChange(boolean isNextFragment) {

        if (mFragmentAtPos0 == null)
            mFragmentAtPos0 = getFragment(0);
        if (mFragmentAtPos0 != null)
            mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();


        if (!isNextFragment) {
            mFragmentAtFlashcards = new FirstPageFragment();
        } else {
            mFragmentAtFlashcards = new NextFragment();
        }

        notifyDataSetChanged();


    }


    @Override
    public int getItemPosition(Object object)
    {
        if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
            return POSITION_NONE;
         if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstPageFragment)
            return POSITION_NONE;
        return POSITION_UNCHANGED;
    }


    public Fragment getFragment(int position) {
        String tag = mFragmentTags.get(position);
        if (tag == null)
            return null;
        return mFragmentManager.findFragmentByTag(tag);
    }
}

El oyente que implementé en la actividad del contenedor del adaptador para ponerlo en el fragmento al adjuntarlo, esta es la actividad:

    public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener {

//...

  @Override
    public void onChange(boolean isNextFragment) {
        if (pagerAdapter != null)
            pagerAdapter.onChange(isNextFragment);


    }

//...
}

Luego, en el fragmento, coloca al oyente cuando adjunta un llamado:

public class FirstPageFragment extends Fragment{


private ChangeFragmentListener changeFragmentListener;


//...
 @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        changeFragmentListener = ((PagerContainerActivity) activity);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        changeFragmentListener = null;
    }
//...
//in the on click to change the fragment
changeFragmentListener.onChange(true);
//...
}

Y finalmente el oyente:

public interface changeFragmentListener {

    void onChange(boolean isNextFragment);

}
Jachumbelechao Unto Mantekilla
fuente
1

Seguí las respuestas de @wize y @mdelolmo y obtuve la solución. Gracias toneladas. Pero, ajusté un poco estas soluciones para mejorar el consumo de memoria.

Problemas que observé:

Guardan la instancia de la Fragmentcual se reemplaza. En mi caso, es un Fragmento que tiene MapViewy pensé que era costoso. Por lo tanto, estoy manteniendo el FragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)lugar de Fragmentsí mismo.

Aquí está mi implementación.

  public static class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {

    private SwitchFragListener mSwitchFragListener;
    private Switch mToggle;
    private int pagerAdapterPosChanged = POSITION_UNCHANGED;
    private static final int TOGGLE_ENABLE_POS = 2;


    public DemoCollectionPagerAdapter(FragmentManager fm, Switch toggle) {
        super(fm);
        mToggle = toggle;

        mSwitchFragListener = new SwitchFragListener();
        mToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                mSwitchFragListener.onSwitchToNextFragment();
            }
        });
    }

    @Override
    public Fragment getItem(int i) {
        switch (i)
        {
            case TOGGLE_ENABLE_POS:
                if(mToggle.isChecked())
                {
                    return TabReplaceFragment.getInstance();
                }else
                {
                    return DemoTab2Fragment.getInstance(i);
                }

            default:
                return DemoTabFragment.getInstance(i);
        }
    }

    @Override
    public int getCount() {
        return 5;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return "Tab " + (position + 1);
    }

    @Override
    public int getItemPosition(Object object) {

        //  This check make sures getItem() is called only for the required Fragment
        if (object instanceof TabReplaceFragment
                ||  object instanceof DemoTab2Fragment)
            return pagerAdapterPosChanged;

        return POSITION_UNCHANGED;
    }

    /**
     * Switch fragments Interface implementation
     */
    private final class SwitchFragListener implements
            SwitchFragInterface {

        SwitchFragListener() {}

        public void onSwitchToNextFragment() {

            pagerAdapterPosChanged = POSITION_NONE;
            notifyDataSetChanged();
        }
    }

    /**
     * Interface to switch frags
     */
    private interface SwitchFragInterface{
        void onSwitchToNextFragment();
    }
}

Enlace de demostración aquí .. https://youtu.be/l_62uhKkLyM

Para fines de demostración, usé 2 fragmentos TabReplaceFragmenty DemoTab2Fragmenten la posición dos. En todos los demás casos estoy usando DemoTabFragmentinstancias.

Explicación:

Estoy pasando Switchde Actividad a DemoCollectionPagerAdapter. Según el estado de este interruptor, mostraremos el fragmento correcto. Cuando se cambia la verificación del interruptor, estoy llamando al método SwitchFragListener's onSwitchToNextFragment, donde estoy cambiando el valor de la pagerAdapterPosChangedvariable a POSITION_NONE. Vea más sobre POSITION_NONE . Esto invalidará getItem y tengo lógicas para instanciar el fragmento correcto allí. Lo siento, si la explicación es un poco desordenada.

Una vez más, muchas gracias a @wize y @mdelolmo por la idea original.

Espero que esto sea útil. :)

Avíseme si esta implementación tiene algún defecto. Eso será de gran ayuda para mi proyecto.

sha
fuente
0

Después de la investigación, encontré una solución con código corto. en primer lugar, cree una instancia pública en fragmento y simplemente elimine su fragmento en onSaveInstanceState si el fragmento no se recrea en el cambio de orientación.

 @Override
public void onSaveInstanceState(Bundle outState) {
    if (null != mCalFragment) {
        FragmentTransaction bt = getChildFragmentManager().beginTransaction();
        bt.remove(mFragment);
        bt.commit();
    }
    super.onSaveInstanceState(outState);
}
JosephM
fuente