support FragmentPagerAdapter contiene referencia a fragmentos antiguos

109

ÚLTIMA INFORMACIÓN:

He reducido mi problema a ser un problema con fragmentManager que conserva instancias de fragmentos antiguos y mi visor no está sincronizado con mi FragmentManager. Ver este problema ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Todavía no tengo ni idea de cómo solucionar esto. Alguna sugerencia...

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

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 usando ActionBarSherlock y la biblioteca de computabilidad v4 para Fragments.

Mi problema es que después de salir de la aplicación y abrir varias otras aplicaciones y regresar, los fragmentos pierden su referencia a FragmentActivity (es decir getActivity() == null). No puedo entender por qué está sucediendo esto. Traté de configurar manualmente setRetainInstance(true);pero esto no ayuda. Supuse que esto sucede cuando se destruye mi FragmentActivity, sin embargo, esto aún sucede 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 me encontré con todo eso está quitado 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);
    }

}

}

Se llama al método de actualización cuando un usuario interactúa con un fragmento diferente y getActivity devuelve un valor nulo. Aquí está el método al que llama el otro fragmento ...

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

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

Maurycy
fuente
27
¡Fragmento de Android apesta!
Hamidreza Sadegh
13
Dios, amo tus mensajes de registro: D
oli.G

Respuestas:

120

Se encuentra con un problema porque está creando instancias y manteniendo referencias a sus fragmentos fuera de PagerAdapter.getItem, y está tratando de usar esas referencias independientemente de ViewPager. Como dice Seraph, tiene garantías de que se ha creado una instancia / agregado de un fragmento en un ViewPager en un momento particular; esto debe considerarse un detalle de implementación. Un ViewPager realiza una carga diferida de sus páginas; de forma predeterminada, solo carga la página actual y la de la izquierda y la derecha.

Si coloca su aplicación en segundo plano, los fragmentos que se han agregado al administrador de fragmentos se guardan automáticamente. Incluso si se mata su aplicación, esta información se restaura cuando reinicia su aplicación.

Ahora considere que ha visto algunas páginas, Fragmentos A, B y C. Sabe que se han agregado al administrador de fragmentos. Porque estás usando FragmentPagerAdaptery noFragmentStatePagerAdapter , estos fragmentos se agregarán (pero potencialmente se separarán) cuando se desplace a otras páginas.

Considere que luego pone en segundo plano su aplicación y luego se mata. Cuando regrese, Android recordará que solía tener Fragmentos A, B y C en el administrador de fragmentos, por lo que los recrea para usted y luego los agrega. Sin embargo, los que se agregan al administrador de fragmentos ahora NO son los que tiene en su lista de fragmentos en su Actividad.

El FragmentPagerAdapter no intentará llamar getPositionsi ya hay un fragmento agregado para esa posición de página en particular. De hecho, dado que el fragmento recreado por Android nunca se eliminará, no tiene esperanzas de reemplazarlo con una llamada agetPosition . Controlarlo también es bastante difícil obtener una referencia porque se agregó con una etiqueta que usted desconoce. Esto es por diseño; se le desaconseja jugar con los fragmentos que administra el buscapersonas de vista. Debe realizar todas sus acciones dentro de un fragmento, comunicarse con la actividad y solicitar cambiar a una página en particular, si es necesario.

Ahora, volvamos a su problema con la actividad faltante. Llamar pagerAdapter.getItem(1)).update(id, name)después de que todo esto haya sucedido le devuelve el fragmento en su lista, que aún no se ha agregado al administrador de fragmentos , por lo que no tendrá una referencia de actividad. Sugeriría que su método de actualización debería modificar alguna estructura de datos compartidos (posiblemente administrada por la actividad), y luego, cuando se mueva a una página en particular, puede dibujarse a sí mismo en función de estos datos actualizados.

antonyt
fuente
1
Me gusta su solución ya que es muy elegante y probablemente refactorizará mi código, sin embargo, como dijo, quería mantener el 100% de la lógica y los datos dentro del fragmento. Su solución requeriría prácticamente mantener todos los datos en FragmentActivity y luego usar cada Fragment simplemente para manejar la lógica de visualización. Como dije, prefiero esto, pero la interacción entre los fragmentos es muy pesada y puede resultar molesto de manejar. De todos modos, gracias por su explicación detallada. Explicaste esto mucho mejor que yo.
Maurycy
28
Breve: nunca guarde una referencia a un fragmento fuera del adaptador
pase
2
Entonces, ¿cómo accede una instancia de Activity a la instancia de FragmentPagerAdapter si no creó una instancia del FragmentPagerAdapter? ¿No restablecerán las instancias futuras el FragmentPagerAdapter y todas sus instancias de fragmentos? ¿Debería FragmentPagerAdapter implementar todas las interfaces de fragmentos para gestionar la comunicación entre fragmentos?
Eric H.
la declaración "Obtener un control sobre él también es bastante difícil obtener una referencia porque se agregó con una etiqueta que usted desconoce. Esto es por diseño; no se recomienda jugar con los fragmentos que administra el buscapersonas de la vista . " Es falso. Es muy fácil obtener una referencia llamando instantiateItemy debería hacerlo dentro onCreatede su actividad. vea los detalles aquí: stackoverflow.com/questions/14035090/…
morgwai
en general, es complicado crear una instancia de los fragmentos sin cargarlos porque en un escenario de "destruir y recrear" podría terminar manejando un fragmento separado del que realmente se recrea y se muestra
hmac
108

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

Haga que su adaptador de fragmentos extienda FragmentStatePagerAdapter en lugar de FragmentPagerAdapter y anule el método onSave para devolver nulo

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

Esto evita que Android vuelva a crear fragmentos


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

Solicite setRetainInstance(true)todos sus fragmentos y guarde las referencias a ellos en algún lugar. Hice eso en una variable estática en mi actividad, porque se declara como singleTask y los fragmentos pueden permanecer iguales todo el tiempo.

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

mc.dev
fuente
4
Gracias, gracias, muchas gracias. Realmente no tengo palabras para agradecerte Mik, estuve persiguiendo este problema de los últimos 10 días y probé tantos
7
tener una referencia estática a fragmentos y / o actividades es algo muy arriesgado, ya que puede causar pérdidas de memoria con mucha facilidad. Por supuesto, si tiene cuidado, puede manejarlo con bastante facilidad configurándolos como nulos cuando ya no los necesite.
desarrollador de Android
Esto funcionó para mí. Los fragmentos y sus respectivas vistas mantienen sus enlaces después de que la aplicación se bloquea y se reinicia. ¡Gracias!
Swebal
Estoy usando setRetainInstance(true)con FragmentPagerAdapter. Todo funciona bien. Pero cuando giro el dispositivo, el adaptador todavía tiene los fragmentos, pero los fragmentos no se muestran. Los métodos de ciclo de vida de los fragmentos tampoco se llaman. ¿Alguien puede ayudar?
Jonas
1
me has salvado el dia !!! He estado buscando este error durante 3 días hasta ahora. Tenía un Viewpager con 2 fragmentos dentro de una actividad de SingleTask y con el indicador "No mantener actividades" habilitado. ¡¡¡¡Muchas gracias!!!!
matriz
29

Resolví este problema accediendo a mis fragmentos directamente a través del FragmentManager en lugar de a través del FragmentPagerAdapter como tal. Primero necesito averiguar la etiqueta del fragmento generado automáticamente por FragmentPagerAdapter ...

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

Luego simplemente obtengo una referencia a ese fragmento y hago lo que necesito así ...

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

Dentro de mis fragmentos configuré el setRetainInstance(false);para poder agregar manualmente valores al paquete SavedInstanceState.

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

    super.onSaveInstanceState(outState);
}

y luego en OnCreate agarro esa clave y restauro el estado del fragmento según sea necesario. Una solución fácil que fue difícil (al menos para mí) de encontrar.

Maurycy
fuente
Tengo un problema similar al tuyo, pero no entiendo bien tu explicación, ¿puedes proporcionar más detalles? También tengo un adaptador que almacena fragmentos en la lista, pero cuando estoy reanudando mi aplicación desde aplicaciones recientes, las aplicaciones se bloquean porque hay algún fragmento separado. El problema está aquí stackoverflow.com/questions/11631408/…
Georgy Gobozov
3
¿Cuál es tu 'mi' en la referencia del fragmento?
Josh
Todos sabemos que esta solución no es un buen enfoque, pero es la forma más fácil de usar FragmentByTagen ViewPager.
Youngjae
aquí hay una forma de acceder a los fragmentos sin depender de la compatibilidad con la forma interna de asignar etiquetas: stackoverflow.com/questions/14035090/…
morgwai
26

Solución probada de trabajo global.

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

No hagas esto:

new PagerAdapter(getSupportFragmentManager(), fragments);

Hacer esto:

new PagerAdapter(getChildFragmentManager() , fragments);

Vinayak
fuente
6
esto solo sería posible si aloja (y crea instancias) el pagerAdapter dentro de un Fragmento y no dentro de su actividad. En este caso, tiene razón, debe usar childFragmentManager por defecto. Usar SupportFragmentManager sería incorrecto de forma predeterminada
Klitos G.
Muy preciso. Resolví mi problema sin usar hacks. ¡Gracias! Mi versión de Kotlin pasó de FragmentStatePagerAdapter(activity!!.supportFragmentManager)ser más fácil de ver FragmentStatePagerAdapter(childFragmentManager):)
ecth
7

No intente interactuar entre fragmentos en ViewPager. No puede garantizar que exista otro fragmento adjunto o incluso. En lugar de cambiar el título de la barra de acción por un fragmento, puede hacerlo desde su actividad. Utilice el patrón de interfaz estándar para esto:

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);
    }
}
Serafín
fuente
Seguro que incluirá el código ahora ... Ya estoy registrando esos métodos. Se llama a onDetach cuando se llama a onStop en fragmentActivity. onAttach se llama justo antes de onCreate de FragmentActivity.
Maurycy
Mmm, gracias, pero el problema persiste. En su lugar, hice que configurar el nombre fuera una devolución de llamada. ¿No puedo poner lógica en el Fragmento? Tengo mis fragmentos disparando servicios y otros elementos que requieren una referencia a la actividad. Tendría que mover toda la lógica de mi aplicación a la actividad principal para evitar el problema que estoy teniendo. Esto parece un poco innecesario.
Maurycy
Ok, probé esto completamente y comenté todo mi código ... excepto la devolución de llamada para cambiar el título. Cuando la aplicación se inicia por primera vez y me muevo a esa pantalla, el nombre del título se cambia np. Después de cargar varias aplicaciones y luego volver, el título ya no cambia. Parece que la devolución de llamada se recibe en una instancia de actividad anterior. No puedo pensar en otra explicación
Maurycy
Consideré que esto es un problema con el fragmentManager y este problema ... code.google.com/p/android/issues/detail?id=19211 . Todavía no tengo ni idea de cómo resolver esto
Maurycy
5

Puede eliminar los fragmentos cuando destruya el visor, en mi caso, los eliminé onDestroyView()de mi fragmento:

@Override
public void onDestroyView() {

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

    super.onDestroyView();
}
Raoni Novellino
fuente
Gracias, tu solución funciona. Se requiere que ViewPagertambién se base en childFragmentManageradaptador (no fragmentManager). También funciona otra variante: no usar onDestroyView, pero eliminar fragmentos secundarios antes de la ViewPagercreación del adaptador.
CoolMind
4

Después de unas horas de buscar un problema similar, creo que tengo otra solución. Este 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 paginador de vista que usa un FragmentStatePagerAdapter con dos Fragmentos. Todo funciona bien hasta que fuerzo la actividad a ser destruida (opciones de desarrollador) o giro la pantalla. Mantengo una referencia a los dos fragmentos después de que se crean dentro del método getItem.

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

Así es como solucioné ese problema, dentro del 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 volverá a recibir una llamada en getItem si el adaptador ya tiene una referencia internamente, y no debería cambiar eso. En su lugar, puede obtener el fragmento que se está utilizando mirando este otro método instantiateItem () que se llamará para cada uno de sus fragmentos.

Espero que ayude a alguien.

Lancelot
fuente
¿Qué pasa si tienes muchos fragmentos? ¿De verdad preferiría guardar una referencia para cada uno de ellos? ¿No es malo para la memoria?
desarrollador de Android
Todo eso depende de lo que esté tratando de lograr exactamente. Normalmente, con estos buscapersonas de vista no se pueden almacenar más de 3 fragmentos a la vez. El del medio y el de cada lado. Para lo que quiero, realmente necesito tener una referencia a los fragmentos, pero sé que solo necesito tratar con dos.
Lancelot
0

Dado que FragmentManager se encargará de restaurar sus Fragmentos por usted tan pronto como se llame al método onResume (), hago que el fragmento llame a la actividad y se agregue 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 un ViewPager personalizado que hará crecer el fragmento a medida que se mueve hacia el foco y lo reduce a medida que se desenfoca. Además de las clases Adaptador y Fragmento que tengo aquí, todo lo que necesita es que la actividad principal pueda hacer referencia a la variable del adaptador y ya está.

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;
}
}
saulpower
fuente
0

Mi solución: configuré casi todas las vistas como static. Ahora mi aplicación interactúa perfectamente. Ser capaz de llamar a los métodos estáticos desde cualquier lugar tal vez no sea un buen estilo, pero ¿por qué jugar con un código que no funciona? Leí muchas preguntas y sus respuestas aquí en SO y ninguna solución trajo éxito (para mí).

Sé que puede perder la memoria y desperdiciar el montón, y mi código no se ajustará a otros proyectos, pero no me asusta esto: probé la aplicación en diferentes dispositivos y condiciones, sin ningún problema, el Android La plataforma parece poder manejar esto. La interfaz de usuario se actualiza cada segundo e incluso en un dispositivo S2 ICS (4.0.3) la aplicación puede manejar miles de marcadores geográficos.

Martin Pfeffer
fuente
0

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

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

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

    super.onAttachFragment(childFragment);
}

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

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

Shirane85
fuente
0

Resolví el problema guardando los fragmentos en SparceArray:

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
fuente
0

Solo para que sepas...

Además de la letanía de problemas con estas clases, hay un error bastante interesante que vale la pena compartir.

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

El problema surge cuando empujo y saco fragmentos del final del 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 del fragmento guardados internamente cuando cambia el tamaño del adaptador. Entonces, cuando hace estallar un elemento y coloca uno nuevo al final, el fragmento del elemento nuevo obtiene el estado guardado del fragmento del elemento anterior, lo que causó un caos absoluto en mi código. Mis fragmentos contienen datos que pueden requerir mucho trabajo para recuperarlos de Internet, por lo que no guardar el 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 aún obtiene un paquete de estado guardado, pero al menos no contiene datos obsoletos.

Robin Davies
fuente
0

Dado que la gente no tiende a leer comentarios, aquí hay una respuesta que en su mayoría duplica lo que escribí aquí :

la causa raíz del problema es el hecho de que el sistema Android no llama getItempara obtener fragmentos que realmente se muestran, pero instantiateItem. Este método primero intenta buscar y reutilizar una instancia de fragmento para una pestaña determinada en FragmentManager. Solo si esta búsqueda falla (lo que ocurre solo la primera vez cuando FragmentManagerse crea nuevamente), getItemse llama. Es por razones obvias no recrear fragmentos (que pueden ser pesados), por ejemplo, cada vez que un usuario gira su dispositivo.
Para resolver esto, en lugar de crear fragmentos Fragment.instantiateen su actividad, debe hacerlo con pagerAdapter.instantiateItemy todas estas llamadas deben estar rodeadas por startUpdate/finishUpdatellamadas a métodos que inicien / confirmen la transacción del fragmento, respectivamente.getItem debería ser el lugar donde realmente se crean los fragmentos utilizando sus respectivos constructores.

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
fuente