Eliminar página de fragmentos de ViewPager en Android

Resuelto astuetz asked hace 54 años • 17 respuestas

Estoy intentando agregar y eliminar fragmentos dinámicamente de un ViewPager; agregarlos funciona sin ningún problema, pero eliminarlos no funciona como se esperaba.

Cada vez que quiero eliminar el elemento actual, se elimina el último.

También intenté usar un FragmentStatePagerAdapter o devolver POSITION_NONE en el método getItemPosition del adaptador.

¿Qué estoy haciendo mal?

Aquí hay un ejemplo básico:

Actividad principal.java

public class MainActivity extends FragmentActivity implements TextProvider {

    private Button mAdd;
    private Button mRemove;
    private ViewPager mPager;

    private MyPagerAdapter mAdapter;

    private ArrayList<String> mEntries = new ArrayList<String>();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mEntries.add("pos 1");
        mEntries.add("pos 2");
        mEntries.add("pos 3");
        mEntries.add("pos 4");
        mEntries.add("pos 5");

        mAdd = (Button) findViewById(R.id.add);
        mRemove = (Button) findViewById(R.id.remove);
        mPager = (ViewPager) findViewById(R.id.pager);

        mAdd.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                addNewItem();
            }
        });

        mRemove.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                removeCurrentItem();
            }
        });

        mAdapter = new MyPagerAdapter(this.getSupportFragmentManager(), this);

        mPager.setAdapter(mAdapter);

    }

    private void addNewItem() {
        mEntries.add("new item");
        mAdapter.notifyDataSetChanged();
    }

    private void removeCurrentItem() {
        int position = mPager.getCurrentItem();
        mEntries.remove(position);
        mAdapter.notifyDataSetChanged();
    }

    @Override
    public String getTextForPosition(int position) {
        return mEntries.get(position);
    }
    @Override
    public int getCount() {
        return mEntries.size();
    }


    private class MyPagerAdapter extends FragmentPagerAdapter {

        private TextProvider mProvider;

        public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
            super(fm);
            this.mProvider = provider;
        }

        @Override
        public Fragment getItem(int position) {
            return MyFragment.newInstance(mProvider.getTextForPosition(position));
        }

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

    }

}

Proveedor de texto.java

public interface TextProvider {
    public String getTextForPosition(int position);
    public int getCount();
}

MiFragmento.java

public class MyFragment extends Fragment {

    private String mText;

    public static MyFragment newInstance(String text) {
        MyFragment f = new MyFragment(text);
        return f;
    }

    public MyFragment() {
    }

    public MyFragment(String text) {
        this.mText = text;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        View root = inflater.inflate(R.layout.fragment, container, false);

        ((TextView) root.findViewById(R.id.position)).setText(mText);

        return root;
    }

}

actividad_principal.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="add new item" />

    <Button
        android:id="@+id/remove"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="remove current item" />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1" />

</LinearLayout>

fragmento.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/position"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="35sp" />

</LinearLayout>
astuetz avatar Jan 01 '70 08:01 astuetz
Aceptado

ViewPager no elimina sus fragmentos con el código anterior porque carga varias vistas (o fragmentos en su caso) en la memoria. Además de la vista visible, también carga la vista a ambos lados de la visible. Esto proporciona un desplazamiento suave de una vista a otra que hace que ViewPager sea tan atractivo.

Para lograr el efecto que desea, debe hacer un par de cosas.

  1. Cambie FragmentPagerAdapter a FragmentStatePagerAdapter. La razón de esto es que FragmentPagerAdapter mantendrá todas las vistas que cargue en la memoria para siempre. Donde FragmentStatePagerAdapter elimina las vistas que quedan fuera de las vistas actuales y transitables.

  2. Anule el método del adaptador getItemPosition (que se muestra a continuación). Cuando llamamos, mAdapter.notifyDataSetChanged();ViewPager interroga al adaptador para determinar qué ha cambiado en términos de posicionamiento. Usamos este método para decir que todo ha cambiado, así que reprocese todas las posiciones de su vista.

Y aquí está el código...

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

    @Override
    public int getItemPosition(Object object){
        return PagerAdapter.POSITION_NONE;
    }

}
Louth avatar May 01 '2012 14:05 Louth

La solución de Louth no fue suficiente para que todo funcionara para mí, ya que los fragmentos existentes no se destruían. Motivado por esta respuesta , descubrí que la solución es anular el getItemId(int position)método para FragmentPagerAdapterproporcionar una nueva ID única cada vez que haya un cambio en la posición esperada de un Fragmento.

Código fuente:

private class MyPagerAdapter extends FragmentPagerAdapter {

    private TextProvider mProvider;
    private long baseId = 0;

    public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
        super(fm);
        this.mProvider = provider;
    }

    @Override
    public Fragment getItem(int position) {
        return MyFragment.newInstance(mProvider.getTextForPosition(position));
    }

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


    //this is called when notifyDataSetChanged() is called
    @Override
    public int getItemPosition(Object object) {
        // refresh all fragments when data set changed
        return PagerAdapter.POSITION_NONE;
    }


    @Override
    public long getItemId(int position) {
        // give an ID different from position when position has been changed
        return baseId + position;
    }

    /**
     * Notify that the position of a fragment has been changed.
     * Create a new ID for each position to force recreation of the fragment
     * @param n number of items which have been changed
     */
    public void notifyChangeInPosition(int n) {
        // shift the ID returned by getItemId outside the range of all previous fragments
        baseId += getCount() + n;
    }
}

Ahora, por ejemplo, si elimina una sola pestaña o realiza algún cambio en el orden, debe llamar notifyChangeInPosition(1)antes de llamar notifyDataSetChanged(), lo que garantizará que se recrearán todos los Fragmentos.

Por qué funciona esta solución

Anulando getItemPosition():

Cuando notifyDataSetChanged()se llama, el adaptador llama al notifyChanged()método al ViewPagerque está conectado. Luego ViewPagerverifica el valor devuelto por el adaptador getItemPosition()para cada elemento, eliminando los elementos que devuelven POSITION_NONE(consulte el código fuente ) y luego repoblando.

Anulando getItemId():

Esto es necesario para evitar que el adaptador recargue el fragmento antiguo cuando se ViewPagerestá repoblando. Puedes entender fácilmente por qué esto funciona mirando el código fuente de instantiateItem() en FragmentPagerAdapter.

    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    }

Como puede ver, el getItem()método solo se llama si el administrador de fragmentos no encuentra fragmentos existentes con el mismo ID. A mí me parece un error que los fragmentos antiguos todavía estén adjuntos incluso después de notifyDataSetChanged()llamarlos, pero la documentación ViewPagerindica claramente que:

Tenga en cuenta que esta clase se encuentra actualmente en fase inicial de diseño y desarrollo. Es probable que la API cambie en actualizaciones posteriores de la biblioteca de compatibilidad, lo que requerirá cambios en el código fuente de las aplicaciones cuando se compilan con la versión más reciente.

Es de esperar que la solución que se proporciona aquí no sea necesaria en una versión futura de la biblioteca de soporte.

Tim Rae avatar Nov 15 '2014 08:11 Tim Rae

mi solución de trabajo para eliminar la página fragmentada de la vista del buscapersonas

public class MyFragmentAdapter extends FragmentStatePagerAdapter {

    private ArrayList<ItemFragment> pages;

    public MyFragmentAdapter(FragmentManager fragmentManager, ArrayList<ItemFragment> pages) {
        super(fragmentManager);
        this.pages = pages;
    }

    @Override
    public Fragment getItem(int index) {
        return pages.get(index);
    }

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

    @Override
    public int getItemPosition(Object object) {
        int index = pages.indexOf (object);

        if (index == -1)
            return POSITION_NONE;
        else
            return index;
    }
}

Y cuando necesito eliminar alguna página por índice hago esto

pages.remove(position); // ArrayList<ItemFragment>
adapter.notifyDataSetChanged(); // MyFragmentAdapter

Aquí está la inicialización de mi adaptador.

MyFragmentAdapter adapter = new MyFragmentAdapter(getSupportFragmentManager(), pages);
viewPager.setAdapter(adapter);
Vasil Valchev avatar Apr 29 '2016 08:04 Vasil Valchev