[Android] soporte FragmentPagerAdapter contiene referencia a fragmentos antiguos


Answers

Encontré una solución simple que funcionó para mí.

Haga que su fragmento de adaptador amplíe FragmentStatePagerAdapter en lugar de FragmentPagerAdapter y anule el método onSave para devolver null

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

Esto evita que Android reproduzca fragmentos

Un día después encontré otra solución mejor.

Llame a setRetainInstance(true) para todos sus fragmentos y guarde referencias a ellos en alguna parte. Lo hice en variable estática en mi actividad, porque está declarado como singleTask y los fragmentos pueden permanecer igual todo el tiempo.

De esta forma, Android no recrea fragmentos, sino que usa las mismas instancias.

Question

ÚLTIMA INFORMACIÓN:

reduje mi problema a ser un problema con fragmentManager conservando instancias de fragmentos antiguos y mi viewpager no está sincronizado con mi FragmentManager. Consulte este problema ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Todavía no tengo ni idea de cómo resolver esto. Alguna sugerencia...

Intenté depurar esto durante mucho tiempo y cualquier ayuda sería muy apreciada. Estoy usando un FragmentPagerAdapter que acepta una lista de fragmentos como esta:

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

La implementación es estándar. Estoy utilizando ActionBarSherlock y v4 computability library para Fragments.

Mi problema es que después de salir de la aplicación, abrir varias aplicaciones y regresar, los fragmentos pierden su referencia a FragmentActivity (es decir, getActivity() == null ). No puedo entender por qué está pasando esto. Traté de establecer manualmente setRetainInstance(true); pero esto no ayuda. Pensé que esto sucedería cuando mi FragmentActivity se destruyera; sin embargo, esto todavía ocurre si abro la aplicación antes de recibir el mensaje de registro. ¿Hay alguna idea?

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

El adaptador:

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 de mis Fragmentos se quitó, pero cometned todo lo que se quitó y todavía no funciona ...

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

}

}

El método de actualización se invoca cuando un usuario interactúa con un fragmento diferente y getActivity devuelve nulo. Este es el método que el otro fragmento llama ...

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

Creo que cuando la aplicación se destruye, se crea nuevamente en lugar de simplemente iniciar la aplicación hasta el fragmento predeterminado, la aplicación se inicia y luego el visor de navegación navega a la última página conocida. Esto parece extraño, ¿no debería la aplicación cargar solo el fragmento predeterminado?




Dado que FragmentManager se encargará de restaurar tus Fragmentos tan pronto como se llame al método onResume () tengo el fragmento llamado a la actividad y se agrega a una lista. En mi caso, estoy almacenando todo esto en mi implementación de PagerAdapter. Cada fragmento conoce su posición porque se agrega a los argumentos del fragmento en la creación. Ahora, cada vez que necesito manipular un fragmento en un índice específico, todo lo que tengo que hacer es usar la lista de mi adaptador.

El siguiente es un ejemplo de un Adaptador para una ViewPager personalizada que hará crecer el fragmento a medida que se mueve al foco, y lo reducirá a medida que se desenfoque. Además de las clases de Adaptador y Fragmento que tengo aquí, todo lo que necesita es que la actividad principal pueda hacer referencia a la variable del adaptador y que esté configurado.

Adaptador

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

Fragmento

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 para que sepas...

Añadiendo a la letanía de problemas con estas clases, hay un error bastante interesante que vale la pena compartir.

Estoy usando una ViewPager para navegar por un árbol de elementos (seleccione un elemento y el paginador de vistas anima a desplazarse hacia la derecha, y aparece la siguiente rama, navegue hacia atrás, y la ViewPager se desplaza en la dirección opuesta para regresar al nodo anterior) .

El problema surge cuando presiono y saco fragmentos del final de FragmentStatePagerAdapter. Es lo suficientemente inteligente como para notar que los elementos cambian, y lo suficientemente inteligente como para crear y reemplazar un fragmento cuando el elemento ha cambiado. Pero no lo suficientemente inteligente como para descartar el estado del fragmento, o lo suficientemente inteligente como para recortar los estados de fragmentos guardados internamente cuando cambia el tamaño del adaptador. Entonces, cuando saca un elemento y lo inserta uno nuevo, el fragmento del nuevo elemento obtiene el estado guardado del fragmento del artículo anterior, lo que causó estragos en mi código. Mis fragmentos contienen datos que pueden requerir mucho trabajo para recuperarlos de Internet, por lo que no guardar estado realmente no era una opción.

No tengo una solución limpia. Usé algo como esto:

  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 solución imperfecta porque la nueva instancia de fragmento todavía obtiene un paquete de estado guardado, pero al menos no contiene datos obsoletos.




Después de unas horas de buscar un problema similar, creo que tengo otra solución. Al menos me funcionó y solo tengo que cambiar un par de líneas.

Este es el problema que tuve, tengo una actividad con un buscapersonas que usa un FragmentStatePagerAdapter con dos Fragmentos. Todo funciona bien hasta que obligo a la actividad a ser destruida (opciones de desarrollador) o giro la pantalla. Guardo una referencia a los dos fragmentos después de que se crean dentro del método getItem.

En ese momento, la actividad se creará de nuevo y todo funciona bien en este punto, pero he perdido la referencia a mis fragmentos ya que getItem no se vuelve a llamar.

Así es como solucioné ese problema, dentro de 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;
    }

No recibirá una llamada en getItem nuevamente si el adaptador ya tiene una referencia interna, y no debe cambiar eso. En su lugar, puede obtener el fragmento que está utilizando al mirar este otro método, instanciatearItem (), que será invocado para cada uno de sus fragmentos.

Espero que ayude a cualquiera.




Me enfrenté al mismo problema, pero mi ViewPager estaba dentro de un TopFragment que creó y configuró un adaptador usando setAdapter(new FragmentPagerAdapter(getChildFragmentManager())) .

onAttachFragment(Fragment childFragment) este problema anulando onAttachFragment(Fragment childFragment) en TopFragment de esta manera:

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

    super.onAttachFragment(childFragment);
}

Como ya se sabe (ver respuestas arriba), cuando childFragmentManager se recrea a sí mismo, también crea los fragmentos que estaban dentro de viewPager.
La parte importante es que después de eso, llama a AttachFragment y ahora tenemos una referencia al nuevo fragmento recreado.

Espero que esto ayude a cualquiera a obtener esta vieja Q como yo :)




Solución probada de trabajo global.

getSupportFragmentManager() mantiene la referencia nula algunas veces y View pager no crea nuevas, ya que encuentra referencia al mismo fragmento. Así que para superar este uso getChildFragmentManager() resuelve el problema de una manera simple.

No hagas esto:

new PagerAdapter(getSupportFragmentManager(), fragments);

Hacer esto:

new PagerAdapter(getChildFragmentManager() , fragments);






Links