Entfernen Sie die Fragmentseite von ViewPager in Android

Lesezeit: 11 Minuten

Entfernen Sie die Fragmentseite von ViewPager in Android
astütz

Ich versuche, Fragmente dynamisch zu einem ViewPager hinzuzufügen und zu entfernen, das Hinzufügen funktioniert ohne Probleme, aber das Entfernen funktioniert nicht wie erwartet.

Jedes Mal, wenn ich das aktuelle Element entfernen möchte, wird das letzte entfernt.

Ich habe auch versucht, einen FragmentStatePagerAdapter zu verwenden oder POSITION_NONE in der getItemPosition-Methode des Adapters zurückzugeben.

Was mache ich falsch?

Hier ist ein einfaches Beispiel:

MainActivity.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();
        }

    }

}

TextProvider.java

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

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

}

activity_main.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>

fragment.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>

  • +1 für den Quellcode

    – Zauberer

    5. Juli 2014 um 20:44 Uhr

  • ist es richtig, den nicht standardmäßigen Konstruktor in zu verwenden MyFragment.java??

    – CodeKiller

    13. Juli 2017 um 6:59 Uhr

1646322191 558 Entfernen Sie die Fragmentseite von ViewPager in Android
Laut

Der ViewPager entfernt Ihre Fragmente nicht mit dem obigen Code, da er mehrere Ansichten (oder in Ihrem Fall Fragmente) in den Speicher lädt. Zusätzlich zur sichtbaren Ansicht lädt es auch die Ansicht zu beiden Seiten der sichtbaren Ansicht. Dies ermöglicht das reibungslose Scrollen von Ansicht zu Ansicht, das den ViewPager so cool macht.

Um den gewünschten Effekt zu erzielen, müssen Sie einige Dinge tun.

  1. Ändern Sie den FragmentPagerAdapter in einen FragmentStatePagerAdapter. Der Grund dafür ist, dass der FragmentPagerAdapter alle Ansichten behält, die er für immer in den Speicher lädt. Wo der FragmentStatePagerAdapter über Ansichten verfügt, die außerhalb der aktuellen und durchquerbaren Ansichten liegen.

  2. Überschreiben Sie die Adaptermethode getItemPosition (siehe unten). Wenn wir anrufen mAdapter.notifyDataSetChanged(); der ViewPager fragt den Adapter ab, um festzustellen, was sich in Bezug auf die Positionierung geändert hat. Wir verwenden diese Methode, um zu sagen, dass sich alles geändert hat, also verarbeiten Sie Ihre gesamte Ansichtspositionierung neu.

Und hier ist der Code…

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

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

}

  • Ok, dies scheint die einfachste Problemumgehung für dieses Problem zu sein. Obwohl einige andere Problemumgehungen in den Fehlerberichten hier diskutiert werden: code.google.com/p/android/issues/detail?id=19110 und hier : code.google.com/p/android/issues/detail?id=19001

    – C–

    6. November 2012 um 4:06 Uhr


  • @Louth: Ich verwende ViewPager mit TabsAdapter, erweitert von FragmentStatePagerAdapter (als Beispiel auf der Google-Referenzseite: developer.android.com/reference/android/support/v4/view/…). Meine Folgefrage lautet: Wie löscht man überhaupt einen Tab? Danke.

    – gcl1

    12. Februar 2013 um 1:14 Uhr

  • @Louth Wenn wir notificationDataSetChanged() aufrufen, erstellt der Viewpager neue Fragmente, aber die älteren sind noch im Speicher. Und mein Problem ist; Sie erhalten einen Aufruf für ihr onOptionsItemSelected-Ereignis. Wie kann ich die alten Fragmente loswerden?

    – syloc

    25. April 2013 um 8:30 Uhr

  • Vielen Dank, das Ersetzen von FragmentPagerAdapter durch einen FragmentStatePagerAdapter hat mein Problem gelöst. Danach entferne ich einfach AllViews() auf ViewPager und lade Fragmente dynamisch neu.

    – Michael

    12. Juni 2013 um 19:40 Uhr

  • Warum geben Sie immer POSITION_NONE zurück, anstatt wirklich zu schauen, ob eine bestimmte Ansicht/Fragment/Objekt noch existiert und gültig ist? Diese Methode soll die Frage beantworten “Hey, kann ich diese wiederverwenden?” – und immer “Nein!” bedeutet nur Erholung von allem! Dies ist in vielen Fällen unnötig.

    – Zordid

    26. Februar 2014 um 17:11 Uhr

1646322191 748 Entfernen Sie die Fragmentseite von ViewPager in Android
Tim Rae

Die Lösung von Louth reichte nicht aus, um die Dinge für mich zum Laufen zu bringen, da die vorhandenen Fragmente nicht zerstört wurden. Durch diese Antwort motiviert, stellte ich fest, dass die Lösung darin besteht, die zu überschreiben getItemId(int position) Methode von FragmentPagerAdapter um eine neue eindeutige ID zu vergeben, wenn sich die erwartete Position eines Fragments geändert hat.

Quellcode:

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

Wenn Sie jetzt zum Beispiel einen einzelnen Tab löschen oder etwas an der Reihenfolge ändern, sollten Sie anrufen notifyChangeInPosition(1) vor dem Anruf notifyDataSetChanged()wodurch sichergestellt wird, dass alle Fragmente neu erstellt werden.

Warum diese Lösung funktioniert

getItemPosition() überschreiben:

Wann notifyDataSetChanged() aufgerufen wird, ruft der Adapter die notifyChanged() Methode der ViewPager an dem es befestigt ist. Die ViewPager überprüft dann den vom Adapter zurückgegebenen Wert getItemPosition() für jeden Artikel, Entfernen der zurückgegebenen Artikel POSITION_NONE (siehe die Quellcode) und dann neu bevölkern.

getItemId() überschreiben:

Dies ist notwendig, um zu verhindern, dass der Adapter das alte Fragment neu lädt, wenn die ViewPager wird neu besiedelt. Sie können leicht verstehen, warum dies funktioniert, indem Sie sich ansehen der Quellcode für instantiateItem() in 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));
    }

Wie Sie sehen, ist die getItem() -Methode wird nur aufgerufen, wenn der Fragmentmanager keine vorhandenen Fragmente mit derselben ID findet. Für mich scheint es ein Fehler zu sein, dass die alten Fragmente auch danach noch angehängt sind notifyDataSetChanged() heißt, aber die Dokumentation für ViewPager sagt ganz klar:

Beachten Sie, dass sich diese Klasse derzeit in einem frühen Entwurfs- und Entwicklungsstadium befindet. Die API wird sich wahrscheinlich in späteren Updates der Kompatibilitätsbibliothek ändern und Änderungen am Quellcode von Apps erfordern, wenn sie mit der neueren Version kompiliert werden.

Hoffentlich wird die hier angegebene Problemumgehung in einer zukünftigen Version der Support-Bibliothek nicht mehr erforderlich sein.

  • Vielen Dank für diese Antwort! Es war anstrengend, dieses Problem ohne die Verwendung von FragmentStatePagerAdapter zu lösen. Tolle Erklärung auch. Haben Sie einen Weg gefunden, getItem auszulösen, wenn Sie zum Fragment zurückkehren? Ich habe an verschiedenen Stellen (z. B. onStop) ohne Erfolg versucht, Ihre clevere NotifyDataSetChanged() zu verwenden

    – u2tall

    28. November 2014 um 20:01 Uhr


  • Ich verstehe Ihre Frage nicht wirklich … Bitte posten Sie eine neue Frage einschließlich des Quellcodes, wenn Sie es nicht herausfinden können.

    – Tim Ree

    28. November 2014 um 22:19 Uhr

  • 2016 ist diese Problemumgehung noch erforderlich notifyDataSetChanged() arbeiten.

    – C–

    8. April 2016 um 3:17 Uhr

  • Beeindruckend! Erstaunliche Erklärung. Ich hatte einen ziemlich komplexen Viewpager, in dem ich Elemente dynamisch entferne und einfüge, und würde es nicht zum Laufen bringen, ohne das alles zu verstehen.

    – rupps

    27. Oktober 2016 um 19:34 Uhr

  • 2020 jetzt. Trotzdem funktioniert dies wie erwartet +1 Upvote

    – Fazal Hussain

    16. Februar 2020 um 8:04 Uhr

Entfernen Sie die Fragmentseite von ViewPager in Android
Wassil Waltschew

meine Arbeitslösung zum Entfernen von Fragmentseiten aus dem Ansichtspager

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

Und wenn ich eine Seite nach Index entfernen muss, mache ich das

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

Hier ist es meine Adapterinitialisierung

MyFragmentAdapter adapter = new MyFragmentAdapter(getSupportFragmentManager(), pages);
viewPager.setAdapter(adapter);

  • Würden Sie bitte den Code zum Hinzufügen des Fragments teilen?

    – VVB

    20. März 2017 um 13:30 Uhr

  • Aber beim Hinzufügen wird es nach zweimaligem/dreimaligem Wischen langsam reflektiert

    – VVB

    21. März 2017 um 9:11 Uhr


  • Wenn Sie keine Elemente entfernen, werden nur Elemente zurückgegeben, nicht mehr … testen Sie auf VM oder einem echten Gerät?

    – Wassil Waltschew

    21. März 2017 um 10:56 Uhr

  • Probieren Sie andere Lösungen aus, antworten Sie mit mehr Stimmen. Der View-Pager hat ein sehr komplexes Cashing-System für eine bessere Leistung beim Swip, daher werden diese Hacks nicht vom ursprünglichen View-Pager mit Zweck gehandhabt …

    – Wassil Waltschew

    21. März 2017 um 11:05 Uhr

Das Fragment muss bereits entfernt worden sein, aber das Problem war der Speicherstatus des Viewpagers

Versuchen

myViewPager.setSaveFromParentEnabled(false);

Nichts hat funktioniert, aber das hat das Problem gelöst!

Prost !

1646322192 799 Entfernen Sie die Fragmentseite von ViewPager in Android
Sven

Ich hatte die Idee, einfach den Quellcode aus zu kopieren android.support.v4.app.FragmentPagerAdpater in eine benutzerdefinierte Klasse namens
CustumFragmentPagerAdapter. Dies gab mir die Möglichkeit, die zu ändern instantiateItem(...) so dass es jedes Mal, wenn es aufgerufen wird, das aktuell angehängte Fragment entfernt / zerstört, bevor es das neue Fragment hinzufügt, von dem es empfangen wurde getItem() Methode.

Ändern Sie einfach die instantiateItem(...) auf die folgende Weise:

@Override
public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);

    // remove / destroy current fragment
    if (fragment != null) {
        mCurTransaction.remove(fragment);
    }

    // get new fragment and add it
    fragment = getItem(position);
    mCurTransaction.add(container.getId(), fragment,    makeFragmentName(container.getId(), itemId));

    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

1646322193 287 Entfernen Sie die Fragmentseite von ViewPager in Android
MSaudi

Sie können beides besser kombinieren:

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

    @Override
    public int getItemPosition(Object object){

      if(Any_Reason_You_WantTo_Update_Positions) //this includes deleting or adding pages
 return PagerAdapter.POSITION_NONE;
    }
else
return PagerAdapter.POSITION_UNCHANGED; //this ensures high performance in other operations such as editing list items.

}

Ich hatte einige Probleme mit FragmentStatePagerAdapter. Nach dem Entfernen eines Elements:

  • es wurde ein anderer Artikel für eine Position verwendet (ein Artikel, der nicht zu der Position gehörte, sondern zu einer Position daneben)
  • oder ein Fragment wurde nicht geladen (auf dieser Seite war nur ein leerer Hintergrund sichtbar)

Nach vielen Versuchen bin ich auf folgende Lösung gekommen.

public class SomeAdapter extends FragmentStatePagerAdapter {

    private List<Item> items = new ArrayList<Item>();

    private boolean removing;

    @Override
    public Fragment getItem(int position) {
        ItemFragment fragment = new ItemFragment();
        Bundle args = new Bundle();
        // use items.get(position) to configure fragment
        fragment.setArguments(args);
        return fragment;
    }

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

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

        Item item = getItemOfFragment(object);

        int index = items.indexOf(item);
        if (index == -1) {
            return POSITION_NONE;
        } else {
            return index;
        }
    }

   public void addItem(Item item) {
        items.add(item);
        notifyDataSetChanged();
    }

    public void removeItem(int position) {
        items.remove(position);

        removing = true;
        notifyDataSetChanged();
        removing = false;
    }
}

Diese Lösung verwendet nur einen Hack, wenn ein Element entfernt wird. Ansonsten (z. B. beim Hinzufügen eines Elements) behält es die Sauberkeit und Leistung eines ursprünglichen Codes bei.

Von außerhalb des Adapters rufen Sie natürlich nur addItem/removeItem auf, es ist nicht erforderlich, notificationDataSetChanged() aufzurufen.

924900cookie-checkEntfernen Sie die Fragmentseite von ViewPager in Android

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy