Unterstützung FragmentPagerAdapter enthält Verweise auf alte Fragmente

109

NEUESTE INFO:

Ich habe mein Problem auf ein Problem eingegrenzt, bei dem der fragmentManager Instanzen alter Fragmente beibehält und mein Viewpager nicht mit meinem FragmentManager synchronisiert ist. Siehe dieses Problem ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Ich habe immer noch keine Ahnung, wie ich das lösen soll. Irgendwelche Vorschläge...

Ich habe lange versucht, dies zu debuggen, und jede Hilfe wäre sehr dankbar. Ich verwende einen FragmentPagerAdapter, der eine Liste von Fragmenten wie folgt akzeptiert:

List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName())); 
...
new PagerAdapter(getSupportFragmentManager(), fragments);

Die Implementierung ist Standard. Ich verwende ActionBarSherlock und die v4-Berechenbarkeitsbibliothek für Fragmente.

Mein Problem ist, dass die Fragmente nach dem Verlassen der App und dem Öffnen mehrerer anderer Anwendungen und dem Zurückkehren ihre Referenz auf die FragmentActivity (dh getActivity() == null) verlieren . Ich kann nicht herausfinden, warum dies geschieht. Ich habe versucht, manuell einzustellen, setRetainInstance(true);aber das hilft nicht. Ich habe mir gedacht, dass dies passiert, wenn meine FragmentActivity zerstört wird. Dies passiert jedoch immer noch, wenn ich die App öffne, bevor ich die Protokollnachricht erhalte. Gibt es irgendwelche Ideen?

@Override
protected void onDestroy(){
    Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
    super.onDestroy();
}

Der Adapter:

public class PagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> fragments;

    public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);

        this.fragments = fragments;

    }

    @Override
    public Fragment getItem(int position) {

        return this.fragments.get(position);

    }

    @Override
    public int getCount() {

        return this.fragments.size();

    }

}

Eines meiner Fragmente wurde entfernt, aber ich habe alles herausgeholt, was entfernt wurde und immer noch nicht funktioniert ...

public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    handler = new Handler();    
    setHasOptionsMenu(true);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
    context = activity;
    if(context== null){
        Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
    }else{
        Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
    }

}

@Override
public void onActivityCreated(Bundle savedState) {
    super.onActivityCreated(savedState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.my_fragment,container, false);

    return v;
}


@Override
public void onResume(){
    super.onResume();
}

private void callService(){
    // do not call another service is already running
    if(startLoad || !canSet) return;
    // set flag
    startLoad = true;
    canSet = false;
    // show the bottom spinner
    addFooter();
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
    context.startService(intent);
}

private ResultReceiver resultReceiver = new ResultReceiver(null) {
    @Override
    protected void onReceiveResult(int resultCode, final Bundle resultData) {
        boolean isSet = false;
        if(resultData!=null)
        if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
            if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
                removeFooter();
                startLoad = false;
                isSet = true;
            }
        }

        switch(resultCode){
        case MyService.STATUS_FINISHED: 
            stopSpinning();
            break;
        case SyncService.STATUS_RUNNING:
            break;
        case SyncService.STATUS_ERROR:
            break;
        }
    }
};

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    menu.clear();
    inflater.inflate(R.menu.activity, menu);
}

@Override
public void onPause(){
    super.onPause();
}

public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
    boolean loadMore = /* maybe add a padding */
        firstVisible + visibleCount >= totalCount;

    boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;

    if(away){
        // startLoad can now be set again
        canSet = true;
    }

    if(loadMore) 

}

public void onScrollStateChanged(AbsListView arg0, int state) {
    switch(state){
    case OnScrollListener.SCROLL_STATE_FLING: 
        adapter.setLoad(false); 
        lastState = OnScrollListener.SCROLL_STATE_FLING;
        break;
    case OnScrollListener.SCROLL_STATE_IDLE: 
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_IDLE;
        break;
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
        break;
    }
}

@Override
public void onDetach(){
    super.onDetach();
    if(this.adapter!=null)
        this.adapter.clearContext();

    Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}

public void update(final int id, String name) {
    if(name!=null){
        getActivity().getSupportActionBar().setTitle(name);
    }

}

}}

Die Aktualisierungsmethode wird aufgerufen, wenn ein Benutzer mit einem anderen Fragment interagiert und getActivity null zurückgibt. Hier ist die Methode, die das andere Fragment aufruft ...

((MyFragment) pagerAdapter.getItem(1)).update(id, name);

Ich glaube, wenn die App zerstört und dann neu erstellt wird, anstatt sie nur bis zum Standardfragment zu starten, wird die App gestartet und der Viewpager navigiert dann zur letzten bekannten Seite. Das scheint seltsam, sollte die App nicht einfach auf das Standardfragment geladen werden?

Maurycy
quelle
27
Das Fragment von Android ist scheiße!
Hamidreza Sadegh
13
Gott, ich liebe deine Protokollnachrichten: D
oli.G

Antworten:

120

Sie stoßen auf ein Problem, weil Sie Verweise auf Ihre Fragmente außerhalb von PagerAdapter.getItemund instanziieren und versuchen, diese Verweise unabhängig vom ViewPager zu verwenden. Wie Seraph sagt, haben Sie Garantien, dass ein Fragment zu einem bestimmten Zeitpunkt in einem ViewPager instanziiert / hinzugefügt wurde - dies sollte als Implementierungsdetail betrachtet werden. Ein ViewPager lädt seine Seiten verzögert. Standardmäßig wird nur die aktuelle Seite und die linke und rechte Seite geladen.

Wenn Sie Ihre App in den Hintergrund stellen, werden die Fragmente, die dem Fragmentmanager hinzugefügt wurden, automatisch gespeichert. Selbst wenn Ihre App beendet wird, werden diese Informationen beim Neustart Ihrer App wiederhergestellt.

Stellen Sie sich nun vor, Sie haben einige Seiten, Fragmente A, B und C, angezeigt. Sie wissen, dass diese dem Fragmentmanager hinzugefügt wurden. Da Sie diese Fragmente verwenden FragmentPagerAdapterund nicht FragmentStatePagerAdapter, werden sie weiterhin hinzugefügt (aber möglicherweise getrennt), wenn Sie zu anderen Seiten scrollen.

Bedenken Sie, dass Sie Ihre Anwendung dann als Hintergrund verwenden und sie dann beendet wird. Wenn Sie zurückkommen, wird sich Android daran erinnern, dass Sie früher Fragmente A, B und C im Fragment-Manager hatten. Daher werden sie für Sie neu erstellt und dann hinzugefügt. Diejenigen, die jetzt zum Fragmentmanager hinzugefügt werden, sind jedoch NICHT diejenigen, die Sie in Ihrer Fragmentliste in Ihrer Aktivität haben.

Der FragmentPagerAdapter versucht nicht aufzurufen, getPositionwenn für diese bestimmte Seitenposition bereits ein Fragment hinzugefügt wurde. Da das von Android neu erstellte Fragment niemals entfernt wird, haben Sie keine Hoffnung, es durch einen Aufruf von zu ersetzen getPosition. Es ist auch ziemlich schwierig, einen Verweis darauf zu bekommen, da es mit einem Tag hinzugefügt wurde, das Ihnen unbekannt ist. Dies ist beabsichtigt; Sie werden davon abgehalten, mit den Fragmenten zu spielen, die der View Pager verwaltet. Sie sollten alle Ihre Aktionen innerhalb eines Fragments ausführen, mit der Aktivität kommunizieren und gegebenenfalls den Wechsel zu einer bestimmten Seite anfordern.

Nun zurück zu Ihrem Problem mit der fehlenden Aktivität. Wenn Sie pagerAdapter.getItem(1)).update(id, name)nach all dem aufrufen, erhalten Sie das Fragment in Ihrer Liste zurück, das noch nicht zum Fragmentmanager hinzugefügt wurde , sodass es keine Aktivitätsreferenz enthält. Ich würde vorschlagen, dass Ihre Aktualisierungsmethode eine gemeinsam genutzte Datenstruktur (möglicherweise von der Aktivität verwaltet) ändert. Wenn Sie dann zu einer bestimmten Seite wechseln, kann sie sich basierend auf diesen aktualisierten Daten selbst zeichnen.

antonyt
quelle
1
Ich mag Ihre Lösung, da sie sehr elegant ist und wahrscheinlich meinen Code umgestalten wird, aber wie Sie sagten, wollte ich 100% der Logik und Daten innerhalb des Fragments behalten. Ihre Lösung würde ziemlich viel erfordern, alle Daten in der FragmentActivity zu behalten und dann jedes Fragment einfach zu verwenden, um die Anzeigelogik zu handhaben. Wie gesagt, ich bevorzuge dies, aber die Interaktion zwischen Fragmenten ist super schwer und es könnte ärgerlich werden, damit umzugehen. Trotzdem vielen Dank für Ihre ausführliche Erklärung. Du hast das viel besser erklärt als ich.
Maurycy
28
Kurz: Halten Sie niemals einen Verweis auf ein Fragment außerhalb des Adapters
passsy
2
Wie greift eine Aktivitätsinstanz auf die FragmentPagerAdapter-Instanz zu, wenn sie den FragmentPagerAdapter nicht instanziiert hat? Werden zukünftige Instanzen nicht einfach den FragmentPagerAdapter und alle seine Fragmentinstanzen wiederherstellen? Sollte der FragmentPagerAdapter alle Fragmentschnittstellen implementieren, um die Kommunikation zwischen Fragmenten zu verwalten?
Eric H.
Die Aussage "Es ist auch ziemlich schwierig, einen Verweis darauf zu bekommen, da es mit einem Tag hinzugefügt wurde, das Ihnen unbekannt ist. Dies ist beabsichtigt. Sie werden davon abgehalten, mit den Fragmenten zu spielen, die der View Pager verwaltet . " ist falsch. Es ist sehr einfach, eine Referenz durch einen Anruf zu erhalten, instantiateItemund Sie sollten dies tatsächlich in onCreateIhrer Aktivität tun . Details finden Sie hier: stackoverflow.com/questions/14035090/…
Morgwai
Im Allgemeinen ist es zweifelhaft, Fragmente selbst zu instanziieren, ohne sie zu laden, da Sie in einem "Zerstören und Wiederherstellen"
-Szenario
108

Ich habe eine einfache Lösung gefunden, die für mich funktioniert hat.

Lassen Sie Ihren Fragmentadapter FragmentStatePagerAdapter anstelle von FragmentPagerAdapter erweitern und überschreiben Sie die Methode onSave, um null zurückzugeben

@Override
public Parcelable saveState()
{
    return null;
}

Dies verhindert, dass Android Fragmente neu erstellt


Einen Tag später fand ich eine andere und bessere Lösung.

Fordern Sie setRetainInstance(true)alle Ihre Fragmente an und speichern Sie Verweise darauf irgendwo. Ich habe das in meiner Aktivität in einer statischen Variablen gemacht, weil sie als singleTask deklariert ist und Fragmente die ganze Zeit gleich bleiben können.

Auf diese Weise erstellt Android keine Fragmente neu, sondern verwendet dieselben Instanzen.

mc.dev
quelle
4
Danke, danke, danke. Vielen Dank, ich habe dieses Problem in den letzten 10 Tagen verfolgt und so viele Methoden ausprobiert. Aber diese vier magischen Zeilen haben mir das Leben gerettet :)
7
Ein statischer Verweis auf Fragmente und / oder Aktivitäten ist sehr riskant, da dies sehr leicht zu Speicherverlusten führen kann. Wenn Sie vorsichtig sind, können Sie dies natürlich ganz einfach handhaben, indem Sie sie auf null setzen, wenn sie nicht mehr benötigt werden.
Android-Entwickler
Das hat bei mir funktioniert. Fragmente und ihre jeweiligen Ansichten behalten ihre Links, nachdem die App abgestürzt und neu gestartet wurde. Vielen Dank!
Swebal
Ich benutze setRetainInstance(true)mit FragmentPagerAdapter. Alles funktioniert gut. Aber wenn ich das Gerät drehe, hat der Adapter immer noch die Fragmente, aber die Fragmente werden nicht angezeigt. Die Lebenszyklusmethoden der Fragmente werden ebenfalls nicht aufgerufen. Kann jemand helfen?
Jonas
1
Du hast meinen Tag gerettet !!! Ich habe bisher 3 Tage nach diesem Fehler gesucht. Ich hatte einen Viewpager mit 2 Fragmenten innerhalb einer SingleTask-Aktivität und aktiviertem Flag "Aktivitäten nicht beibehalten". Vielen Dank!!!!
Matrix
29

Ich habe dieses Problem gelöst, indem ich direkt über den FragmentManager auf meine Fragmente zugegriffen habe, anstatt wie folgt über den FragmentPagerAdapter. Zuerst muss ich das Tag des Fragments herausfinden, das vom FragmentPagerAdapter automatisch generiert wird ...

private String getFragmentTag(int pos){
    return "android:switcher:"+R.id.viewpager+":"+pos;
}

Dann bekomme ich einfach einen Verweis auf dieses Fragment und mache, was ich brauche ...

Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);

In meinen Fragmenten habe ich das setRetainInstance(false);so eingestellt, dass ich dem savedInstanceState-Bundle manuell Werte hinzufügen kann.

@Override
public void onSaveInstanceState(Bundle outState) {
    if(this.my !=null)
        outState.putInt("myId", this.my.getId());

    super.onSaveInstanceState(outState);
}

und dann greife ich im OnCreate nach diesem Schlüssel und stelle den Status des Fragments nach Bedarf wieder her. Eine einfache Lösung, die (zumindest für mich) schwer herauszufinden war.

Maurycy
quelle
Ich habe ein ähnliches Problem wie Sie, aber ich verstehe Ihre Erklärung nicht ganz. Können Sie weitere Details angeben? Ich habe auch einen Adapter, der Fragmente in der Liste speichert, aber wenn ich meine App von den letzten Apps wieder aufnehme, stürzen Apps ab, weil es ein abgetrenntes Fragment gibt. Problem ist hier stackoverflow.com/questions/11631408/…
Georgy Gobozov
3
Was ist dein "Mein" in der Fragmentreferenzierung?
Josh
Wir alle wissen, dass diese Lösung kein guter Ansatz ist, aber die einfachste Möglichkeit, sie FragmentByTagin ViewPager zu verwenden.
Youngjae
Hier ist eine Möglichkeit, auf Fragmente zuzugreifen, ohne auf die Kompatibilität mit der internen Methode zum Zuweisen von Tags angewiesen zu sein
morgwai
26

Global funktionierende getestete Lösung.

getSupportFragmentManager()behält die Nullreferenz einige Male bei und der View-Pager erstellt keine neuen, da er einen Verweis auf dasselbe Fragment findet. Um dies zu überwinden, getChildFragmentManager()löst diese Verwendung das Problem auf einfache Weise.

Tu das nicht:

new PagerAdapter(getSupportFragmentManager(), fragments);

Mach das:

new PagerAdapter(getChildFragmentManager() , fragments);

Vinayak
quelle
6
Dies wäre nur möglich, wenn Sie den pagerAdapter in einem Fragment und nicht in Ihrer Aktivität hosten (und instanziieren). In diesem Fall haben Sie Recht, Sie sollten standardmäßig den childFragmentManager verwenden. Die Verwendung des SupportFragmentManager wäre standardmäßig falsch
Klitos G.
Sehr präzise. Ich habe mein Problem ohne Hacks gelöst. Vielen Dank! Meine Kotlin-Version wurde von FragmentStatePagerAdapter(activity!!.supportFragmentManager)einer leichter zu betrachtenden FragmentStatePagerAdapter(childFragmentManager):)
ecth
7

Versuchen Sie nicht, zwischen Fragmenten in ViewPager zu interagieren. Sie können nicht garantieren, dass ein anderes Fragment angehängt ist oder sogar existiert. Anstatt den Titel der Aktionsleiste aus dem Fragment zu ändern, können Sie dies aus Ihrer Aktivität heraus tun. Verwenden Sie dazu das Standardschnittstellenmuster:

public interface UpdateCallback
{
    void update(String name);
}

public class MyActivity extends FragmentActivity implements UpdateCallback
{
    @Override
    public void update(String name)
    {
        getSupportActionBar().setTitle(name);
    }

}

public class MyFragment extends Fragment
{
    private UpdateCallback callback;

    @Override
    public void onAttach(SupportActivity activity)
    {
        super.onAttach(activity);
        callback = (UpdateCallback) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        callback = null;
    }

    public void updateActionbar(String name)
    {
        if(callback != null)
            callback.update(name);
    }
}
Seraph
quelle
Sicher wird jetzt Code enthalten ... Ich protokolliere diese Methoden bereits. Der onDetach wird aufgerufen, wenn der onStop in der fragmentActivity aufgerufen wird. Der onAttach wird unmittelbar vor dem onCreate der FragmentActivity aufgerufen.
Maurycy
Hmm, danke, aber das Problem besteht weiterhin. Ich habe stattdessen das Setzen des Namens zu einem Rückruf gemacht. Kann ich dem Fragment keine Logik hinzufügen? Ich habe meine Fragmente Dienste und andere Elemente abfeuern lassen, die einen Verweis auf die Aktivität erfordern. Ich müsste meine gesamte Anwendungslogik auf die Hauptaktivität verschieben, um das Problem zu vermeiden, das ich habe. Dies scheint ein wenig unnötig.
Maurycy
Ok, also habe ich das gerade vollständig getestet und meinen gesamten Code auskommentiert ... bis auf den Rückruf, um den Titel zu ändern. Wenn die App zum ersten Mal gestartet wird und ich zu diesem Bildschirm gehe, wird der Titelname np geändert. Nach dem Laden mehrerer Anwendungen ändert sich der Titel nicht mehr. Es sieht so aus, als würde der Rückruf auf einer alten Aktivitätsinstanz empfangen. Ich kann mir keine andere Erklärung
vorstellen
Ich habe dies auf ein Problem mit dem fragmentManager und diesem Problem reduziert ... code.google.com/p/android/issues/detail?id=19211 . Ich habe immer noch keine Ahnung, wie ich das lösen soll
Maurycy
5

Sie können die Fragmente entfernen, wenn Sie den Viewpager zerstören. In meinem Fall habe ich sie entfernt onDestroyView() von meinem Fragment entfernt:

@Override
public void onDestroyView() {

    if (getChildFragmentManager().getFragments() != null) {
        for (Fragment fragment : getChildFragmentManager().getFragments()) {
            getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
        }
    }

    super.onDestroyView();
}
Raoni Novellino
quelle
Danke, Ihre Lösung funktioniert. Es ist erforderlich, dass ViewPagerauch auf childFragmentManagerAdapter basiert (nicht fragmentManager). Auch eine andere Variante funktioniert: Nicht verwenden onDestroyView, sondern untergeordnete Fragmente vor der ViewPagerAdaptererstellung entfernen .
CoolMind
4

Nach ein paar Stunden Suche nach einem ähnlichen Problem denke ich, dass ich eine andere Lösung habe. Zumindest dieses hat bei mir funktioniert und ich muss nur ein paar Zeilen ändern.

Dies ist das Problem, das ich hatte. Ich habe eine Aktivität mit einem Ansichtspager, der einen FragmentStatePagerAdapter mit zwei Fragmenten verwendet. Alles funktioniert einwandfrei, bis ich erzwinge, dass die Aktivität zerstört wird (Entwickleroptionen) oder den Bildschirm drehe. Ich behalte einen Verweis auf die beiden Fragmente, nachdem sie in der Methode getItem erstellt wurden.

Zu diesem Zeitpunkt wird die Aktivität erneut erstellt und an diesem Punkt funktioniert alles einwandfrei, aber ich habe den Verweis auf meine Fragmente verloren, da getItem nicht erneut aufgerufen wird.

So habe ich dieses Problem im FragmentStatePagerAdapter behoben:

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object aux = super.instantiateItem(container, position);

        //Update the references to the Fragments we have on the view pager
        if(position==0){
            fragTabOne = (FragOffersList)aux;
        }
        else{
            fragTabTwo = (FragOffersList) aux;
        }

        return aux;
    }

Sie erhalten keinen erneuten Aufruf von getItem, wenn der Adapter bereits intern einen Verweis darauf hat, und Sie sollten dies nicht ändern. Stattdessen können Sie das Fragment, das es verwendet, erhalten, indem Sie sich diese andere Methode instantiateItem () ansehen, die für jedes Ihrer Fragmente aufgerufen wird.

Hoffe das hilft jedem.

Lancelot
quelle
Was ist, wenn Sie viele Fragmente haben? Möchten Sie wirklich lieber für jeden eine Referenz speichern? Ist es nicht schlecht für das Gedächtnis?
Android-Entwickler
Das hängt alles davon ab, was genau Sie erreichen wollen. Normalerweise halten Sie mit diesen Ansichts-Pagern nicht mehr als 3 Fragmente gleichzeitig. Der in der Mitte und der auf jeder Seite. Für das, was ich will, muss ich wirklich einen Verweis auf die Fragmente haben, aber ich weiß, dass ich mich nur mit zwei befassen muss.
Lancelot
0

Da der FragmentManager sich darum kümmert, Ihre Fragmente für Sie wiederherzustellen, sobald die onResume () -Methode aufgerufen wird, muss das Fragment die Aktivität aufrufen und sich selbst zu einer Liste hinzufügen. In meinem Fall speichere ich all dies in meiner PagerAdapter-Implementierung. Jedes Fragment kennt seine Position, da es bei der Erstellung zu den Fragmentargumenten hinzugefügt wird. Wenn ich jetzt ein Fragment an einem bestimmten Index bearbeiten muss, muss ich nur noch die Liste meines Adapters verwenden.

Das folgende Beispiel zeigt einen Adapter für einen benutzerdefinierten ViewPager, der das Fragment vergrößert, wenn es in den Fokus gerät, und verkleinert, wenn es unscharf wird. Neben den Adapter- und Fragmentklassen, die ich hier habe, ist alles, was Sie brauchen, damit die übergeordnete Aktivität auf die Adaptervariable verweisen kann und Sie festgelegt sind.

Adapter

public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {

public final String TAG = this.getClass().getSimpleName();

private final int COUNT = 4;

public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;

private int mCurrentPage = 0;
private boolean mScrollingLeft;

private List<SummaryTabletFragment> mFragments;

public int getCurrentPage() {
    return mCurrentPage;
}

public void addFragment(SummaryTabletFragment fragment) {
    mFragments.add(fragment.getPosition(), fragment);
}

public GrowPagerAdapter(FragmentManager fm) {
    super(fm);

    mFragments = new ArrayList<SummaryTabletFragment>();
}

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

@Override
public Fragment getItem(int position) {
    return SummaryTabletFragment.newInstance(position);
}

@Override
public void onPageScrollStateChanged(int state) {}

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

    adjustSize(position, positionOffset);
}

@Override
public void onPageSelected(int position) {
    mCurrentPage = position;
}

/**
 * Used to adjust the size of each view in the viewpager as the user
 * scrolls.  This provides the effect of children scaling down as they
 * are moved out and back to full size as they come into focus.
 * 
 * @param position
 * @param percent
 */
private void adjustSize(int position, float percent) {

    position += (mScrollingLeft ? 1 : 0);
    int secondary = position + (mScrollingLeft ? -1 : 1);
    int tertiary = position + (mScrollingLeft ? 1 : -1);

    float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
    float scaleDown = mScrollingLeft ? 1.0f - percent : percent;

    float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
    float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;

    if (scaleUp < BASE_SIZE)
        scaleUp = BASE_SIZE;

    if (scaleDown < BASE_SIZE)
        scaleDown = BASE_SIZE;

    // Adjust the fragments that are, or will be, on screen
    SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
    SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
    SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;

    if (current != null && next != null) {

        // Apply the adjustments to each fragment
        current.transitionFragment(percentIn, scaleUp);
        next.transitionFragment(percentOut, scaleDown);

        if (afterNext != null) {
            afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
        }
    }
}

@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {

    // Keep track of which direction we are scrolling
    mScrollingLeft = (oldl - l) < 0;
}
}

Fragment

public class SummaryTabletFragment extends BaseTabletFragment {

public final String TAG = this.getClass().getSimpleName();

private final float SCALE_SIZE = 0.8f;

private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;

private String mTitleText;
private Integer mColor;

private boolean mInit = false;
private Float mScale, mPercent;

private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;

public String getTitleText() {
    return mTitleText;
}

public void setTitleText(String titleText) {
    this.mTitleText = titleText;
}

public static SummaryTabletFragment newInstance(int position) {

    SummaryTabletFragment fragment = new SummaryTabletFragment();
    fragment.setRetainInstance(true);

    Bundle args = new Bundle();
    args.putInt("position", position);
    fragment.setArguments(args);

    return fragment;
}

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

    mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);

    setupViews();
    configureView();

    return mRoot;
}

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

    if (savedInstanceState != null) {
        mColor = savedInstanceState.getInt("color", Color.BLACK);
    }

    configureView();
}

@Override
public void onSaveInstanceState(Bundle outState)  {

    outState.putInt("color", mColor);

    super.onSaveInstanceState(outState);
}

@Override
public int getPosition() {
    return getArguments().getInt("position", -1);
}

@Override
public void setPosition(int position) {
    getArguments().putInt("position", position);
}

public void onResume() {
    super.onResume();

    mAdapter = mActivity.getPagerAdapter();
    mAdapter.addFragment(this);
    mCurrentPosition = mAdapter.getCurrentPage();

    if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
        mInit = true;
        transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
        return;
    }

    if (getPosition() == mCurrentPosition && !mInit) {
        mInit = true;
        transitionFragment(0.00f, 1.0f);
    }
}

private void setupViews() {

    mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
    mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
    mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
    mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
    mTitle = (TextView) mRoot.findViewById(R.id.title);
}

private void configureView() {

    Fonts.applyPrimaryBoldFont(mLeft, 15);
    Fonts.applyPrimaryBoldFont(mRight, 15);

    float[] size = UiUtils.getScreenMeasurements(mActivity);
    int width = (int) (size[0] * SCALE_SIZE);
    int height = (int) (size[1] * SCALE_SIZE);

    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
    mBackground.setLayoutParams(params);

    if (mScale != null)
        transitionFragment(mPercent, mScale);

    setRandomBackground();

    setTitleText("Fragment " + getPosition());

    mTitle.setText(getTitleText().toUpperCase());
    mLeft.setText(getTitleText().toUpperCase());
    mRight.setText(getTitleText().toUpperCase());

    mLeft.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showNextPage();
        }
    });

    mRight.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showPrevPage();
        }
    });
}

private void setRandomBackground() {

    if (mColor == null) {
        Random r = new Random();
        mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
    }

    mBackground.setBackgroundColor(mColor);
}

public void transitionFragment(float percent, float scale) {

    this.mScale = scale;
    this.mPercent = percent;

    if (getView() != null && mCover != null) {

        getView().setScaleX(scale);
        getView().setScaleY(scale);

        mCover.setAlpha(percent);
        mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
    }
}

@Override
public String getFragmentTitle() {
    return null;
}
}
saulpower
quelle
0

Meine Lösung: Ich setze fast jede Ansicht als static . Jetzt interagiert meine App perfekt. Die statischen Methoden von überall aufrufen zu können, ist vielleicht kein guter Stil, aber warum mit Code herumspielen, der nicht funktioniert? Ich habe viele Fragen und ihre Antworten hier auf SO gelesen und keine Lösung brachte Erfolg (für mich).

Ich weiß, dass es den Speicher verlieren und Haufen verschwenden kann, und mein Code wird nicht in andere Projekte passen, aber ich habe keine Angst davor - ich habe die App auf verschiedenen Geräten und Bedingungen getestet, überhaupt keine Probleme, das Android Die Plattform scheint damit umgehen zu können. Die Benutzeroberfläche wird jede Sekunde aktualisiert und selbst auf einem S2 ICS (4.0.3) -Gerät kann die App Tausende von Geomarkern verarbeiten.

Martin Pfeffer
quelle
0

Ich hatte das gleiche Problem, aber mein ViewPager befand sich in einem TopFragment, mit dem ein Adapter erstellt und festgelegt wurde setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).

Ich habe dieses Problem behoben, indem ich onAttachFragment(Fragment childFragment)das TopFragment wie folgt überschrieben habe:

@Override
public void onAttachFragment(Fragment childFragment) {
    if (childFragment instanceof OnboardingDiamondsFragment) {
        mChildFragment = (ChildFragment) childFragment;
    }

    super.onAttachFragment(childFragment);
}

Wie bereits bekannt (siehe Antworten oben), erstellt der childFragmentManager beim erneuten Erstellen auch die Fragmente, die sich im viewPager befanden.
Der wichtige Teil ist, dass er danach AttachFragment aufruft und jetzt einen Verweis auf das neu erstellte Fragment hat!

Hoffe das hilft jedem, der dieses alte Q wie ich bekommt :)

Shirane85
quelle
0

Ich habe das Problem gelöst, indem ich die Fragmente in SparceArray gespeichert habe:

public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {

    SparseArray<Fragment> fragments = new SparseArray<>();

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

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        fragments.append(position, fragment);
        return fragment;
    }

    @Nullable
    public Fragment getFragmentByPosition(int position){
        return fragments.get(position);
    }

}
xaxtix
quelle
0

Nur damit du weißt ...

Zusätzlich zur Litanei der Leiden mit diesen Klassen gibt es einen ziemlich interessanten Fehler, der es wert ist, geteilt zu werden.

Ich verwende einen ViewPager, um in einem Baum von Elementen zu navigieren (wählen Sie ein Element aus, und der Ansichtspager animiert das Scrollen nach rechts, und der nächste Zweig wird angezeigt. Navigieren Sie zurück, und der ViewPager scrollt in die entgegengesetzte Richtung, um zum vorherigen Knoten zurückzukehren.) .

Das Problem tritt auf, wenn ich Fragmente vom Ende des FragmentStatePagerAdapter schiebe und platziere. Es ist intelligent genug, um zu bemerken, dass sich die Elemente ändern, und intelligent genug, um ein Fragment zu erstellen und zu ersetzen, wenn sich das Element geändert hat. Aber nicht intelligent genug, um den Fragmentstatus zu verwerfen, oder nicht intelligent genug, um die intern gespeicherten Fragmentzustände zu kürzen, wenn sich die Adaptergröße ändert. Wenn Sie also ein Element einfügen und ein neues an das Ende schieben, erhält das Fragment für das neue Element den gespeicherten Status des Fragments für das alte Element, was in meinem Code zu absoluten Verwüstungen geführt hat. Meine Fragmente enthalten Daten, deren erneutes Abrufen aus dem Internet möglicherweise viel Arbeit erfordert. Daher war es keine Option, den Status nicht zu speichern.

Ich habe keine saubere Problemumgehung. Ich habe so etwas benutzt:

  public void onSaveInstanceState(Bundle outState) {
    IFragmentListener listener = (IFragmentListener)getActivity();
    if (listener!= null)
    {
        if (!listener.isStillInTheAdapter(this.getAdapterItem()))
        {
            return; // return empty state.
        }

    }
    super.onSaveInstanceState(outState);

    // normal saving of state for flips and 
    // paging out of the activity follows
    ....
  }

Eine unvollständige Lösung, da die neue Fragmentinstanz immer noch ein savedState-Bundle erhält, aber zumindest keine veralteten Daten enthält.

Robin Davies
quelle
0

Da die Leute nicht dazu neigen, Kommentare zu lesen, ist hier eine Antwort, die meistens das dupliziert, was ich hier geschrieben habe :

Die Hauptursache des Problems ist die Tatsache, dass Android-System nicht aufruft getItem, um Fragmente zu erhalten, die tatsächlich angezeigt werden, aber instantiateItem. Diese Methode versucht zunächst, eine Fragmentinstanz für eine bestimmte Registerkarte in zu suchen und wiederzuverwenden FragmentManager. Nur wenn diese Suche fehlschlägt (was nur beim ersten Erstellen beim ersten Erstellen geschieht FragmentManager), getItemwird aufgerufen. Es liegt auf der Hand, dass Fragmente (die möglicherweise schwer sind) nicht jedes Mal neu erstellt werden, wenn ein Benutzer sein Gerät dreht.
Um dies zu lösen, Fragment.instantiatesollten Sie nicht Fragmente in Ihrer Aktivität erstellen, sondern den Ort verwenden, an dem Fragmente mithilfe der jeweiligen Konstruktoren erstellt werden.pagerAdapter.instantiateItem und alle diese Aufrufe sollten von startUpdate/finishUpdateMethodenaufrufen umgeben sein , die jeweils Fragmenttransaktionen starten / festschreiben.getItem

List<Fragment> fragments = new Vector<Fragment>();

@Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myLayout);
        ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        ((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);

        adapter.startUpdate(viewPager);
        fragments.add(adapter.instantiateItem(viewPager, 0));
        fragments.add(adapter.instantiateItem(viewPager, 1));
        // and so on if you have more tabs...
        adapter.finishUpdate(viewPager);
}

class MyPagerAdapter extends FragmentPagerAdapter {

        public MyPagerAdapter(FragmentManager manager) {super(manager);}

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

        @Override public Fragment getItem(int position) {
            if (position == 0) return new Fragment0();
            if (position == 1) return new Fragment1();
            return null;  // or throw some exception
        }

        @Override public CharSequence getPageTitle(int position) {
            if (position == 0) return getString(R.string.tab0);
            if (position == 1) return getString(R.string.tab1);
            return null;  // or throw some exception
        }
}
Morgwai
quelle