Wiederverwenden von Fragmenten in einem Fragmentpageradapter

70

Ich habe einen Viewpager, der durch Fragmente blättert. Meine FragmentPagerAdapterUnterklasse erstellt ein neues Fragment in der getItemMethode, das verschwenderisch erscheint. Gibt es ein FragmentPagerAdapterÄquivalent zu dem convertViewin listAdapter, mit dem ich bereits erstellte Fragmente wiederverwenden kann? Mein Code ist unten.

public class ProfilePagerAdapter extends FragmentPagerAdapter {

    ArrayList<Profile> mProfiles = new ArrayList<Profile>();

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

    /**
     * Adding a new profile object created a new page in the pager this adapter is set to.
     * @param profile
     */
    public void addProfile(Profile profile){
        mProfiles.add(profile);
    }

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

    @Override
    public Fragment getItem(int position) {
        return new ProfileFragment(mProfiles.get(position));
    }
}
jwanga
quelle

Antworten:

102

Das FragmentPagerAdapterzwischenspeichert das schon Fragmentsfür dich. Jedem Fragment wird ein Tag zugewiesen, und dann wird FragmentPagerAdapterversucht, es aufzurufen findFragmentByTag. Es wird nur aufgerufen, getItemwenn das Ergebnis von findFragmentByTagist null. Sie sollten die Fragmente also nicht selbst zwischenspeichern müssen.

Geoff
quelle
4
Bat Ich denke, dass dies keine Lösung ist. Ich habe ein ähnliches Problem. Ich habe 10 Bildschirme in ViewPager, die alle das gleiche Fragment nur mit unterschiedlichen Argumenten enthalten. Ich brauche keine 10 Instanzen. 3 ist genug. Wenn der Benutzer nach rechts scrollt, kann das meiste linke Fragment wiederverwendet werden.
Am
@ATom - Wenn Sie scrollen, werden Fragmente, die weiter als +/- 1 Index sind, zerstört und neu erstellt, wenn Sie trotzdem zu ihnen navigieren. Versuchen Sie, eine Protokollausgabe in die onCreateViewMethode einzufügen, um zu sehen, wann jedes Fragment instanziiert wird
Matt Taylor
2
@ MattTaylor Wenn Sie einen ViewPager verwenden, können Sie setOffscreenPageLimit verwenden, um die gleichen Instanzen zu behalten
An-Droide
@ MattTaylor - das ist etwas falsch. Die Fragmente selbst sollten nicht zerstört werden (es sei denn, Ihre App verfügt nicht über genügend verfügbaren Speicher). Dies unterscheidet sich jedoch von den zerstörten Fragmentansichten (was passieren wird, sobald sich das betreffende Fragment außerhalb von OffscreenPageLimint befindet). Wenn Sie dem Fragment onCreate Ihres Fragments eine zweite Protokollanweisung hinzufügen, wird diese nicht jedes Mal aufgerufen, wenn onCreateView aufgerufen wird.
Geoff
1
Es gibt immer noch keine Lösung, oder? Jede Lösung enthält immer noch 10 Fragmente im Speicher. Statt nur 2 oder 3.
Matthias
67

Anhang zu Geoffs Beitrag:

Sie können Bezug auf Ihre bekommen Fragmentin FragmentPagerAdapterVerwendung findFragmentByTag(). Der Name des Tags wird folgendermaßen generiert:

private static String makeFragmentName(int viewId, int index)
{
     return "android:switcher:" + viewId + ":" + index;
}

Dabei ist viewId die ID von ViewPager

Schauen Sie sich diesen Link an: http://code.google.com/p/openintents/source/browse/trunk/compatibility/AndroidSupportV2/src/android/support/v2/app/FragmentPagerAdapter.java#104

petrnohejl
quelle
19
Hinweis für Leute wie mich: viewId ist die ID des ViewPager
nspo
@Kopfgeldjaeger Ich habe dich Code gesehen getFragmentByPosition. Wie ist die Position des Fragments?
Gkiko
Seconding @GregEnnis funktioniert nicht mit State Pager, irgendwelche Gedanken?
stolperte
1
@trippedout @GregEnnis versuche meine Antwort unten. Sie können Verweise auf die von Fragmentserstellten speichern FragmentStatePagerAdapterund diese verwenden, anstatt zu versuchen, sie mit zu finden tags(was FragmentStatePagerAdapternicht verwendet wird).
Tony Chan
Erstens hängt der Code in dieser Antwort von der Kompatibilität mit der internen Benennung ab: Wenn sich dieser Mechanismus ändert, funktioniert dieser Code nicht mehr. Zweitens gibt es wirklich keinen Grund, Verweise auf Fragmente per Tag zu erhalten, da Sie sie einfach speichern können, wenn Sie instantiateItemIhre onCreatewie hier beschrieben anrufen
morgwai
23

Es scheint, dass viele Leute, die diese Frage sehen, nach einer Möglichkeit suchen, auf das Fragmentsvon FragmentPagerAdapter/ erstellte zu verweisen FragmentStatePagerAdapter. Ich möchte meine Lösung dafür anbieten, ohne mich auf das intern Geschaffene zu verlassentags , die die anderen Antworten hier verwenden.

Als Bonus sollte diese Methode auch funktionieren FragmentStatePagerAdapter. Weitere Informationen finden Sie in den nachstehenden Hinweisen.


Problem mit aktuellen Lösungen: Verlassen Sie sich auf internen Code

Viele der Lösungen, die ich für diese und ähnliche Fragen gesehen habe, basieren darauf, einen Verweis auf das Vorhandene zu erhalten Fragment, FragmentManager.findFragmentByTag()indem das intern erstellte Tag"android:switcher:" + viewId + ":" + id aufgerufen und nachgeahmt wird : . Das Problem dabei ist, dass Sie sich auf internen Quellcode verlassen, der bekanntlich nicht für immer gleich bleibt. Die Android-Ingenieure bei Google könnten leicht entscheiden, die tagStruktur zu ändern, die Ihren Code beschädigen würde, sodass Sie keinen Verweis auf den vorhandenen finden können Fragments.

Alternative Lösung ohne interne tag

Hier ist ein einfaches Beispiel dafür, wie Sie einen Verweis auf das Fragmentszurückgegebene erhalten FragmentPagerAdapter, der nicht vom internen abhängttags Menge auf demFragments . Der Schlüssel besteht darin instantiateItem(), Referenzen dort zu überschreiben und zu speichern, anstatt in getItem().

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

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

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

oder wenn Sie arbeiten lieber mit tagsstatt Klasse Membervariablen / Verweis auf die FragmentsSie kann auch den greifen tagsSatz von FragmentPagerAdapterauf die gleiche Weise: HINWEIS: dies gilt nicht , FragmentStatePagerAdapterda es nicht gesetzt ist , tagswenn seine Erstellung Fragments.

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

Beachten Sie, dass diese Methode NICHT auf der Nachahmung des internen tagSatzes durch beruht FragmentPagerAdapterund stattdessen geeignete APIs zum Abrufen verwendet. Auf diese Weise sind Sie auch dann sicher, wenn die tagÄnderungen in zukünftigen Versionen von vorgenommen SupportLibrarywerden.


Vergessen Sie nicht, dass das Design Activity, an dem FragmentsSie arbeiten möchten , je nach Design möglicherweise noch vorhanden ist oder nicht. Sie müssen dies also berücksichtigennull Überprüfungen durchführen, bevor Sie Ihre Referenzen verwenden.

Wenn Sie stattdessen mit arbeiten FragmentStatePagerAdapter, möchten Sie keine harten Verweise auf Ihre behalten, Fragmentsda Sie möglicherweise viele davon haben und harte Verweise sie unnötigerweise im Speicher behalten würden. Speichern Sie stattdessen die FragmentReferenzen in WeakReferenceVariablen anstelle von Standardreferenzen. So was:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}
Tony Chan
quelle
Die erste Strategie, die er vorgeschlagen hat, m1stFragment = (FragmentA) createdFragment;hat für mich funktioniert und ich denke, es ist die beste Lösung hier. Aus irgendeinem Grund hat die alternative Strategie String firstTag = createdFragment.getTag();bei mir nicht funktioniert. Vielen Dank @Turbo, das hat mich gerettet.
Dick Lucas
1
RE: Probleme mit aktuellen Lösungen ... Wenn Sie die Support-Bibliothek für Fragmente verwenden, verlassen Sie sich dazu nicht auf den "internen Code" des Geräts. Sie würden sich auf den Code der Support-Bibliothek verlassen, der mit Ihrer App geliefert wird (und Sie können sogar die Quelle in Ihr Repo einchecken, wenn Sie paranoid sind oder Änderungen vornehmen müssen).
Geoff
1
@geoff guter Punkt, aber ist das überhaupt eine dokumentierte Funktion? Ganz zu schweigen davon, dass es schlecht aussieht - Sie haben nette, aussagekräftige funktionsbasierte Tags, die Sie selbst definieren ... und dann haben Sie diese.
Kaay
wie beschrieben in stackoverflow.com/questions/14035090/... gibt es wirklich keinen Grund außer Kraft setzen instantiateItemzu speichern Referenzen , wie Sie sollen Anruf instantiateItem in Ihrem onCreateso können Sie einen Verweis speichern , indem der Weg dorthin.
Morgwai
17

Wenn sich das Fragment noch im Speicher befindet, können Sie es mit dieser Funktion finden.

public Fragment findFragmentByPosition(int position) {
    FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
    return getSupportFragmentManager().findFragmentByTag(
            "android:switcher:" + getViewPager().getId() + ":"
                    + fragmentPagerAdapter.getItemId(position));
}

Beispielcode für die v4-Support-API.

Daniel De León
quelle
Erstens hängt der Code in dieser Antwort von der Kompatibilität mit der internen Benennung ab: Wenn sich dieser Mechanismus ändert, funktioniert dieser Code nicht mehr. Zweitens gibt es wirklich keinen Grund, Verweise auf Fragmente per Tag zu erhalten, da Sie sie einfach speichern können, wenn Sie instantiateItem in Ihrem onCreate aufrufen, wie hier beschrieben: stackoverflow.com/questions/14035090/…
morgwai
0

Ich weiß, dass dies (theoretisch) keine Antwort auf die Frage ist, sondern ein anderer Ansatz.

Ich hatte ein Problem, bei dem ich die sichtbaren Fragmente aktualisieren musste. Was auch immer ich versuchte, scheiterte und kläglich scheiterte ...

Nachdem ich so viele verschiedene Dinge ausprobiert habe, habe ich dies endlich mit beendet BroadCastReceiver. Senden Sie einfach eine Sendung, wenn Sie etwas mit den sichtbaren Fragmenten tun müssen, und erfassen Sie sie im Fragment.

Wenn Sie auch eine Antwort benötigen, können Sie diese auch per Broadcast senden.

Prost

Sabo
quelle