webentwicklung-frage-antwort-db.com.de

Wie gehe ich mit einem ViewPager und verschachtelten Fragmenten richtig mit der Bildschirmdrehung um?

Ich habe diese Aktivität, die ein Fragment enthält. Dieses Fragment-Layout besteht aus einem View-Pager mit mehreren Fragmenten (eigentlich zwei).

Wenn der View-Pager erstellt wird, wird sein Adapter erstellt,getItem wird aufgerufen und meine Unterfragmente werden erstellt. Großartig.

Wenn ich nun den Bildschirm drehe, übernimmt das Framework die Neuerstellung des Fragments. Der Adapter wird erneut in meiner onCreate aus dem Hauptfragment erstellt, abergetItem wird nie aufgerufen , sodass mein Adapter falsche Referenzen enthält (tatsächlich Nullen) ) anstelle der beiden Fragmente.

Was ich gefunden habe, ist, dass der Fragment-Manager (das heißt der untergeordnete Fragment-Manager) ein Array von Fragmenten mit dem Namen mActive enthält, auf das natürlich nicht über Code zugegriffen werden kann. Es gibt jedoch diese getFragment Methode:

@Override
public Fragment getFragment(Bundle bundle, String key) {
    int index = bundle.getInt(key, -1);
    if (index == -1) {
        return null;
    }
    if (index >= mActive.size()) {
        throwException(new IllegalStateException("Fragement no longer exists for key "
                + key + ": index " + index));
    }
    Fragment f = mActive.get(index);
    if (f == null) {
        throwException(new IllegalStateException("Fragement no longer exists for key "
                + key + ": index " + index));
    }
    return f;
}

Ich werde den Tippfehler nicht kommentieren :)
Dies ist der Hack, den ich implementiert habe, um die Referenzen auf meine Fragmente in meinem Adapterkonstruktor zu aktualisieren:

// fm holds a reference to a FragmentManager
Bundle hack = new Bundle();
try {
    for (int i = 0; i < mFragments.length; i++) {
        hack.putInt("hack", i);
        mFragments[i] = fm.getFragment(hack, "hack");
    }
} catch (Exception e) {
    // No need to fail here, likely because it's the first creation and mActive is empty
}

Ich bin nicht stolz Das funktioniert, aber es ist hässlich. Wie kann man nach einer Bildschirmdrehung einen gültigen Adapter haben?

PS: hier ist der vollständige Code

27
Benoit Duffez

Ich hatte das gleiche Problem - ich gehe davon aus, dass Sie FragmentPagerAdapter für Ihren Pageradapter subklassifizieren (da getItem() für FragmentPagerAdapter spezifisch ist).

Meine Lösung bestand darin, stattdessen PagerAdapter die Unterklasse zu erstellen und die Fragmenterstellung/-löschung selbst zu übernehmen (einige der FragmentPagerAdapter-Codes neu implementieren):

public class ListPagerAdapter extends PagerAdapter {
    FragmentManager fragmentManager;
    Fragment[] fragments;

    public ListPagerAdapter(FragmentManager fm){
        fragmentManager = fm;
        fragments = new Fragment[5];
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        assert(0 <= position && position < fragments.length);
        FragmentTransaction trans = fragmentManager.beginTransaction();
        trans.remove(fragments[position]);
        trans.commit();
        fragments[position] = null;
}

    @Override
    public Fragment instantiateItem(ViewGroup container, int position){
        Fragment fragment = getItem(position);
        FragmentTransaction trans = fragmentManager.beginTransaction();
        trans.add(container.getId(),fragment,"fragment:"+position);
        trans.commit();
        return fragment;
    }

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

    @Override
    public boolean isViewFromObject(View view, Object fragment) {
        return ((Fragment) fragment).getView() == view;
    }

    public Fragment getItem(int position){
        assert(0 <= position && position < fragments.length);
        if(fragments[position] == null){
            fragments[position] = ; //make your fragment here
        }
        return fragments[position];
    }
}

Hoffe das hilft.

28
Josh Hunt

Meine Antwort ähnelt der von Joshua Hunt, aber durch Festschreiben der Transaktion in der finishUpdate-Methode erhalten Sie eine viel bessere Leistung. Eine Transaktion statt zwei Transaktionen pro Update. Hier ist der Code:

private class SuchPagerAdapter extends PagerAdapter{

    private final FragmentManager mFragmentManager;
    private SparseArray<Fragment> mFragments;
    private FragmentTransaction mCurTransaction;

    private SuchPagerAdapter(FragmentManager fragmentManager) {
        mFragmentManager = fragmentManager;
        mFragments = new SparseArray<>();
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = getItem(position);
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        mCurTransaction.add(container.getId(),fragment,"fragment:"+position);
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        mCurTransaction.detach(mFragments.get(position));
        mFragments.remove(position);
    }

    @Override
    public boolean isViewFromObject(View view, Object fragment) {
        return ((Fragment) fragment).getView() == view;
    }

    public Fragment getItem(int position) {         
        return YoursVeryFragment.instantiate();
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitAllowingStateLoss();
            mCurTransaction = null;
            mFragmentManager.executePendingTransactions();
        }
    }


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

}
6
simekadam

Warum so komplexe Lösungen? Sieht aus wie ein Overkill. Ich löse es, indem ich den alten Verweis auf new in der von FragmentPagerAdapter erweiterten Klasse ersetzt

 @Override
public Object instantiateItem(ViewGroup container, int position) {
    frags[position] = (Fragment) super.instantiateItem(container, position);
    return frags[position];
}

Der gesamte Code des Adapters sieht so aus

public class RelationsFragmentsAdapter extends FragmentPagerAdapter {

private final String titles[] = new String[3];
private final Fragment frags[] = new Fragment[titles.length];

public RelationsFragmentsAdapter(FragmentManager fm) {
    super(fm);
    frags[0] = new FriendsFragment();
    frags[1] = new FriendsRequestFragment();
    frags[2] = new FriendsDeclinedFragment();

    Resources resources = AppController.getAppContext().getResources();

    titles[0] = resources.getString(R.string.my_friends);
    titles[1] = resources.getString(R.string.my_new_friends);
    titles[2] = resources.getString(R.string.followers);
}

@Override
public CharSequence getPageTitle(int position) {
    return titles[position];
}

@Override
public Fragment getItem(int position) {
    return frags[position];
}

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

@Override
public Object instantiateItem(ViewGroup container, int position) {
    frags[position] = (Fragment) super.instantiateItem(container, position);
    return frags[position];
}

}

5
F0RIS

Das Problem ist, dass getItem() in FragmentPageAdapter einen falschen Namen hat. Es sollte createItem(). Heißen. Aufgrund seiner Funktionsweise ist getItem() zum Erstellen von Fragmenten gedacht, und es ist nicht sicher, es für das Abfragen/Finden eines Fragments zu verwenden.

Ich empfehle, eine Kopie des aktuellen FragmentPagerAdapter zu erstellen und es so zu ändern:

Hinzufügen:

    public abstract Fragment createFragment(int position);

Und ändern Sie getItem in:

public Fragment getItem(int position) {
    if(containerId!=null) {
        final long itemId = getItemId(position);
        String name = makeFragmentName(containerId, itemId);
        return mFragmentManager.findFragmentByTag(name);
    } else {
        return null;
    }
}

Fügen Sie schließlich das zu instantiateItem hinzu:

    if(containerId==null)
        containerId = container.getId();
    else if(containerId!=container.getId())
        throw new RuntimeException("Container id not expected to change");

Vollständiger Code bei this Gist

Ich denke, dass diese Implementierung sicherer und benutzerfreundlicher ist und auch die gleiche Leistung wie der ursprüngliche Adapter von Google-Ingenieuren aufweist.

0
lujop

Mein Code:

    public class SampleAdapter extends FragmentStatePagerAdapter {

    private Fragment mFragmentAtPos2;
    private FragmentManager mFragmentManager;
    private Fragment[] mFragments = new Fragment[3];


    public SampleAdapter(FragmentManager mgr) {
        super(mgr);
        mFragmentManager = mgr;
        Bundle hack = new Bundle();
        try {
            for (int i = 0; i < mFragments.length; i++) {
                hack.putInt("hack", i);
                mFragments[i] = mFragmentManager.getFragment(hack, "hack");
            }
        } catch (Exception e) {
            // No need to fail here, likely because it's the first creation and mActive is empty
        }

    }

    public void switchFrag(Fragment frag) {

        if (frag == null) {
            Dbg.e(TAG, "- switch(frag) frag is NULL");
            return;
        } else Dbg.v(TAG, "- switch(frag) - frag is " + frag.getClass());
        // We have to check for mFragmentAtPos2 null in case of first time (only Mytrips fragment being instatiante).
        if (mFragmentAtPos2!= null) 
            mFragmentManager.beginTransaction()  
                .remove(mFragmentAtPos2)
                .commit();
        mFragmentAtPos2 = frag;
        notifyDataSetChanged();
    }

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

    @Override
    public int getItemPosition(Object object) {
        Dbg.v(TAG,"getItemPosition : "+object.getClass());
        if (object instanceof MyTripsFragment
                || object instanceof FindingDriverFragment
                || object instanceof BookingAcceptedFragment
                || object instanceof RideStartedFragment
                || object instanceof RideEndedFragment
                || object instanceof ContactUsFragment
                )
            return POSITION_NONE;
        else return POSITION_UNCHANGED;

    }

    @Override
    public Fragment getItem(int position) {
        Dbg.v("SampleAdapter", "getItem called on: "+position);
        switch (position) {
        case 0: 
            if (snapbookFrag==null) {
                snapbookFrag = new SnapBookingFragment();
                Dbg.e(TAG, "snapbookFrag created");
            }
            return snapbookFrag;
        case 1: 
            if(bookingFormFrag==null) {
                bookingFormFrag = new BookingFormFragment();
                Dbg.e(TAG, "bookingFormFrag created");
            }
            return bookingFormFrag;
        case 2: 
            if (mFragmentAtPos2 == null) {
                myTripsFrag = new MyTripsFragment();
                mFragmentAtPos2 = myTripsFrag;
                return mFragmentAtPos2;
            }
            return mFragmentAtPos2;
        default:
            return(new SnapBookingFragment());
        }
    }
}
0
Poutrathor

In Bezug auf simekadams - Lösung wird mFragments nicht in instantiateItem aufgefüllt und benötigt mFragments.put(position, fragment); oder Sie erhalten den folgenden Fehler: Wenn Sie versuchen, ein Fragment aus der Ansicht zu entfernen, erhalten Sie eine NullPointerException bei mNextAnim .

0
mitenko