android - with - supporto FragmentPagerAdapter detiene il riferimento ai vecchi frammenti




view pager adapter android (8)

Dopo alcune ore di ricerca di un problema simile, penso che abbia un'altra soluzione. Questo almeno ha funzionato per me e devo solo cambiare un paio di righe.

Questo è il problema che ho avuto, ho un'attività con un cercapersone che utilizza un FragmentStatePagerAdapter con due frammenti. Tutto funziona bene fino a quando non costringo l'attività a essere distrutta (opzioni sviluppatore) o ruoto lo schermo. Mantengo un riferimento ai due frammenti dopo che sono stati creati all'interno del metodo getItem.

A quel punto l'attività verrà ricreata e tutto funziona a questo punto, ma ho perso il riferimento ai miei frammenti perché getItem non viene richiamato.

Ecco come ho risolto il problema, all'interno di FragmentStatePagerAdapter:

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

Non riceverai più una chiamata su getItem se l'adattatore ha già un riferimento interno e non dovresti cambiarlo. Invece è possibile ottenere il frammento che viene utilizzato osservando questo altro metodo instantiateItem () che verrà chiamato per ciascuno dei tuoi frammenti.

Spero che aiuti chiunque.

https://code.i-harness.com

ULTIME INFO:

Ho ridotto il mio problema a un problema con frammentManager che conserva istanze di vecchi frammenti e il mio viewpager non è sincronizzato con il mio FragmentManager. Vedi questo problema ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Non ho ancora idea di come risolvere questo problema. Eventuali suggerimenti...

Ho provato a eseguire il debug di questo per molto tempo e qualsiasi aiuto sarebbe molto apprezzato. Sto usando un FragmentPagerAdapter che accetta un elenco di frammenti in questo modo:

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

L'implementazione è standard. Sto usando ActionBarSherlock e la libreria di computabilità v4 per Fragments.

Il mio problema è che dopo aver abbandonato l'app e aver aperto diverse altre applicazioni e aver fatto ritorno, i frammenti perdono il loro riferimento a FragmentActivity (ad esempio getActivity() == null ). Non riesco a capire perché questo sta accadendo. Ho provato a impostare manualmente setRetainInstance(true); ma questo non aiuta. Ho capito che questo succede quando il mio FragmentActivity viene distrutto, tuttavia questo succede ancora se apro l'app prima di ricevere il messaggio di log. Ci sono idee?

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

L'adattatore:

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();

    }

}

Uno dei miei frammenti è stato rimosso, ma ho commesso tutto ciò che è stato rimosso e ancora non funziona ...

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

}

}

Il metodo di aggiornamento viene chiamato quando un utente interagisce con un diverso frammento e getActivity restituisce null. Ecco il metodo che l'altro frammento sta chiamando ...

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

Credo che quando l'app viene distrutta e poi creata di nuovo invece di avviare l'applicazione fino al frammento predefinito, l'app si avvia e quindi viewpager naviga fino all'ultima pagina conosciuta. Sembra strano, l'app non dovrebbe caricare solo il frammento predefinito?


Ho affrontato lo stesso problema ma il mio ViewPager era all'interno di una TopFragment che creava e setAdapter(new FragmentPagerAdapter(getChildFragmentManager())) un adattatore usando setAdapter(new FragmentPagerAdapter(getChildFragmentManager())) .

Ho risolto il problema sovrascrivendo onAttachFragment(Fragment childFragment) in TopFragment in questo modo:

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

    super.onAttachFragment(childFragment);
}

Come già noto (vedere le risposte sopra), quando childFragmentManager si ricrea, crea anche i frammenti che si trovavano all'interno di viewPager.
La parte importante è che dopo, chiama su AttackFragment e ora abbiamo un riferimento al nuovo frammento ricreato!

Spero che questo possa aiutare chiunque a ricevere questa vecchia Q come me :)


Ho risolto questo problema accedendo ai miei frammenti direttamente attraverso FragmentManager invece che tramite FragmentPagerAdapter in questo modo. Per prima cosa ho bisogno di capire il tag del frammento generato automaticamente da FragmentPagerAdapter ...

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

Quindi ottengo semplicemente un riferimento a quel frammento e faccio quello che mi serve in questo modo ...

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

All'interno dei miei frammenti ho impostato setRetainInstance(false); in modo che io possa aggiungere manualmente valori al pacchetto savedInstanceState.

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

    super.onSaveInstanceState(outState);
}

e poi nell'OnCreate afferro quella chiave e ripristino lo stato del frammento secondo necessità. Una soluzione facile che era difficile (almeno per me) capire.


Ho trovato una soluzione semplice che ha funzionato per me.

Crea l'adattatore del tuo frammento estende FragmentStatePagerAdapter invece di FragmentPagerAdapter e sostituisci il metodo onSave per restituire null

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

Questo impedisce ad Android di ricreare frammenti

Un giorno dopo ho trovato un'altra soluzione migliore.

Chiama setRetainInstance(true) per tutti i tuoi frammenti e salva i riferimenti ad essi da qualche parte. L'ho fatto in variabili statiche nella mia attività, perché è dichiarato come singleTask e i frammenti possono rimanere sempre lo stesso.

In questo modo Android non ricrea i frammenti ma usa le stesse istanze.


Non provare a interagire tra i frammenti in ViewPager. Non è possibile garantire che un altro frammento sia allegato o addirittura esistente. Prima di cambiare il titolo della barra delle azioni da un frammento, puoi farlo dalla tua attività. Usa uno schema di interfaccia standard per questo:

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

Poiché FragmentManager si prenderà cura di ripristinare i frammenti per te non appena viene chiamato il metodo onResume (), il frammento richiama l'attività e si aggiunge a un elenco. Nel mio caso sto memorizzando tutto questo nella mia implementazione di PagerAdapter. Ogni frammento conosce la sua posizione perché viene aggiunto agli argomenti del frammento alla creazione. Ora ogni volta che ho bisogno di manipolare un frammento con un indice specifico tutto quello che devo fare è usare la lista dal mio adattatore.

Il seguente è un esempio di un adattatore per un ViewPager personalizzato che ingrandirà il frammento mentre si sposta a fuoco e lo ridimensiona man mano che si sposta fuori fuoco. Oltre alle classi Adapter e Fragment che ho qui tutto ciò di cui hai bisogno è che l'attività parent sia in grado di fare riferimento alla variabile dell'adattatore e che tu sia impostata.

Adattatore

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

Frammento

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

Solo così sai ...

Aggiungendo alla litania di guai con queste classi, c'è un bug piuttosto interessante che vale la pena condividere.

Sto usando un ViewPager per navigare in un albero di elementi (selezionare un elemento e il pager della vista si anima scorrendo verso destra, e il ramo successivo appare, torna indietro, e il ViewPager scorre nella direzione opposta per tornare al nodo precedente) .

Il problema sorge quando spingo e sposto frammenti dalla parte finale di FragmentStatePagerAdapter. È abbastanza intelligente da notare che gli oggetti cambiano e abbastanza intelligente da creare e sostituire un frammento quando l'oggetto è cambiato. Ma non abbastanza intelligente da scartare lo stato dei frammenti, o abbastanza intelligente da tagliare gli stati dei frammenti salvati internamente quando cambia la dimensione dell'adattatore. Quindi, quando si inserisce un elemento e ne si inserisce uno nuovo alla fine, il frammento del nuovo oggetto ottiene lo stato salvato del frammento per il vecchio oggetto, causando il caos assoluto nel mio codice. I miei frammenti contengono dati che potrebbero richiedere un sacco di lavoro per il recupero da Internet, quindi non salvare lo stato in realtà non era un'opzione.

Non ho una soluzione pulita. Ho usato qualcosa del genere:

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

Una soluzione imperfetta perché la nuova istanza di frammento ottiene ancora un pacchetto statico salvato, ma almeno non contiene dati obsoleti.


Stai incontrando un problema perché stai PagerAdapter.getItem un'istanza e mantenendo i riferimenti ai tuoi frammenti al di fuori di PagerAdapter.getItem e stai tentando di utilizzare tali riferimenti indipendentemente dal ViewPager. Come dice Seraph, hai la garanzia che un frammento è stato istanziato / aggiunto in un ViewPager in un determinato momento - questo dovrebbe essere considerato un dettaglio di implementazione. Un ViewPager fa il lazy caricamento delle sue pagine; per impostazione predefinita carica solo la pagina corrente e quella a sinistra ea destra.

Se metti la tua app in background, i frammenti che sono stati aggiunti al gestore dei frammenti vengono salvati automaticamente. Anche se la tua app viene uccisa, queste informazioni vengono ripristinate al riavvio della tua app.

Ora considera di aver visto alcune pagine, Frammenti A, B e C. Sai che questi sono stati aggiunti al gestore dei frammenti. Poiché stai utilizzando FragmentPagerAdapter e non FragmentStatePagerAdapter , questi frammenti verranno comunque aggiunti (ma potenzialmente distaccati) quando scorri verso altre pagine.

Considera che quindi esegui lo sfondo della tua applicazione e poi viene ucciso. Quando torni, Android ricorderà che hai usato Frammenti A, B e C nel gestore dei frammenti e quindi li ricrea per te e poi li aggiunge. Tuttavia, quelli che vengono aggiunti al gestore dei frammenti ora NON sono quelli presenti nell'elenco dei frammenti nella tua attività.

FragmentPagerAdapter non tenterà di chiamare getPosition se esiste già un frammento aggiunto per quella particolare posizione della pagina. Infatti, poiché il frammento ricreato da Android non verrà mai rimosso, non hai alcuna speranza di sostituirlo con una chiamata a getPosition . Ottenere un controllo su di esso è anche piuttosto difficile da ottenere un riferimento perché è stato aggiunto con un tag sconosciuto. Questo è di design; sei scoraggiato dall'avere a che fare con i frammenti gestiti dal visualizzatore di pagine. Dovresti eseguire tutte le azioni all'interno di un frammento, comunicare con l'attività e richiedere di passare a una determinata pagina, se necessario.

Ora, torna al tuo problema con l'attività mancante. Chiamando pagerAdapter.getItem(1)).update(id, name) dopo che tutto ciò è accaduto, si restituisce il frammento nella lista, che deve ancora essere aggiunto al gestore dei frammenti , e quindi non avrà un riferimento di attività. Vorrei suggerire che il tuo metodo di aggiornamento dovrebbe modificare alcune strutture di dati condivise (eventualmente gestite dall'attività), quindi quando ti sposti su una determinata pagina può disegnare se stesso sulla base di questi dati aggiornati.





android-fragments