ViewPager y fragmentos: ¿cuál es la forma correcta de almacenar el estado de los fragmentos?

Los fragmentos parecen ser muy buenos para la separación de la lógica de la interfaz de usuario en algunos módulos. Pero junto con ViewPager su ciclo de vida todavía es confuso para mí. ¡Así que los pensamientos de Guru son muy necesarios!

Editar

Vea la solución tonta a continuación ;-)

Alcance

La actividad principal tiene un ViewPager con fragmentos. Esos fragmentos podrían implementar una lógica un poco diferente para otras actividades (secundarias), por lo que los datos de los fragmentos se completan a través de una interfaz de devolución de llamada dentro de la actividad. Y todo funciona bien en el primer lanzamiento, ¡pero! ...

Problema

Cuando la actividad se recrea (por ejemplo, en el cambio de orientación) también lo hacen ViewPagerfragmentos de. El código (lo encontrará a continuación) dice que cada vez que se crea la actividad trato de crear una nueva ViewPager El adaptador de fragmentos es el mismo que el de los fragmentos (tal vez este sea el problema) pero FragmentManager ya tiene todos estos fragmentos almacenados en algún lugar (¿dónde?) e inicia el mecanismo de recreación para esos. Entonces, el mecanismo de recreación llama a onAttach, onCreateView, etc. del fragmento "antiguo" con mi llamada a la interfaz de devolución de llamada para iniciar datos a través del método implementado de la Actividad. Pero este método apunta al fragmento recién creado que se crea a través del método onCreate de la actividad.

Inconveniente

Tal vez esté usando patrones incorrectos, pero incluso el libro de Android 3 Pro no tiene mucho al respecto. Entonces, Por favor, dame un puñetazo uno-dos y señala cómo hacerlo de la manera correcta. ¡Muchas gracias!

Código

Actividad principal

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {

private MessagesFragment mMessagesFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);

    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();

    // set titles and fragments for view pager
    Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);

    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);

}

/* set of fragments callback interface implementations */

@Override
public void onMessageInitialisation() {

    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}

@Override
public void onMessageSelected(Message selectedMessage) {

    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

BasePagerActivity también conocido como helper

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

adaptador

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {

    super(fm);
    this.mScreens = screenMap;
}

@Override
public Fragment getItem(int position) {

    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}

@Override
public int getCount() {

    return mScreens.size();
}

@Override
public String getTitle(int position) {

    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}

// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {

    // TODO Auto-generated method stub
}

}

fragmento

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;

private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;

// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}

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

    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();

    // instantiate list of messages
    mMessagesList = new ArrayList<Message>();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}

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

public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}

/* public methods to load messages from host acitivity, etc... */
}

Solución

La solución tonta es guardar los fragmentos dentro de onSaveInstanceState (de la actividad del host) con putFragment y ponerlos dentro de onCreate a través de getFragment. Pero todavía tengo la extraña sensación de que las cosas no deberían funcionar así ... Vea el código a continuación:

    @Override
protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}

protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}

preguntado Oct 31 '11, 05:10

Me pregunto ahora: ¿debería usar un enfoque muy diferente o intentar guardar fragmentos de la actividad principal (Tablero) a través de onSavedInstancestate para usarlos en onCreate (). ¿Existe una forma adecuada de guardar esos fragmentos y obtenerlos del paquete en onCreate? No parecen parcelables ... -

El segundo enfoque funciona - ver "Sulución". Pero parece ser un código feo, ¿no es así? -

En aras del esfuerzo de limpiar la etiqueta de Android (detalles aquí: meta.stackexchange.com/questions/100529/… ), ¿le importaría publicar su solución como respuesta y marcarla como la seleccionada? De esa manera, no aparecerá como una pregunta sin respuesta :) -

sí, creo que está bien. Esperaba algo mejor que el mío ... -

¿Funciona la solución tonta? Me da una excepción de puntero nulo .. -

11 Respuestas

Cuando el FragmentPagerAdapter agrega un fragmento al FragmentManager, utiliza una etiqueta especial basada en la posición particular en la que se colocará el fragmento. FragmentPagerAdapter.getItem(int position) solo se llama cuando no existe un fragmento para esa posición. Después de rotar, Android notará que ya creó / guardó un fragmento para esta posición en particular y, por lo tanto, simplemente intenta volver a conectarse con él con FragmentManager.findFragmentByTag(), en lugar de crear uno nuevo. Todo esto es gratis cuando se usa el FragmentPagerAdapter y es por eso que es habitual tener el código de inicialización de su fragmento dentro del getItem(int) método.

Incluso si no estuviéramos usando un FragmentPagerAdapter, no es una buena idea crear un nuevo fragmento cada vez en Activity.onCreate(Bundle). Como habrá notado, cuando se agrega un fragmento al FragmentManager, se volverá a crear para usted después de rotarlo y no es necesario agregarlo nuevamente. Hacerlo es una causa común de errores al trabajar con fragmentos.

Un enfoque habitual cuando se trabaja con fragmentos es este:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    CustomFragment fragment;
    if (savedInstanceState != null) {
        fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
    } else {
        fragment = new CustomFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit(); 
    }

    ...

}

Cuando se usa un FragmentPagerAdapter, cedemos la administración de fragmentos al adaptador y no tenemos que realizar los pasos anteriores. De forma predeterminada, solo precargará un Fragmento delante y detrás de la posición actual (aunque no los destruirá a menos que esté usando FragmentStatePagerAdapter). Esto está controlado por ViewPager.setOffscreenPageLimit (int). Debido a esto, no se garantiza que sea válido llamar directamente a métodos en los fragmentos fuera del adaptador, porque es posible que ni siquiera estén activos.

Para abreviar una larga historia, su solución a utilizar putFragment poder obtener una referencia después no es tan loco, y no tan diferente de la forma normal de usar fragmentos de todos modos (arriba). De lo contrario, es difícil obtener una referencia porque el fragmento lo agrega el adaptador y no usted personalmente. Solo asegúrate de que el offscreenPageLimit es lo suficientemente alto como para cargar los fragmentos deseados en todo momento, ya que confía en que esté presente. Esto evita las capacidades de carga diferida de ViewPager, pero parece ser lo que desea para su aplicación.

Otro enfoque es anular FragmentPageAdapter.instantiateItem(View, int) y guarde una referencia al fragmento devuelto por la super llamada antes de devolverlo (tiene la lógica para encontrar el fragmento, si ya está presente).

Para obtener una imagen más completa, eche un vistazo a algunas de las fuentes de FragmentPagerAdapterFragmentPagerAdapter (corto y Ver paginador (largo).

respondido 28 mar '13, 17:03

Me encantó la última parte. Tenía un caché para los fragmentos y movió la lógica puesta en el caché dentro del FragmentPageAdapter.instantiateItem(View, int). Finalmente se solucionó un error de larga duración que solo aparece en el cambio de rotación / configuración y me estaba volviendo loco ... - Carlos Sobrinho

Esto solucionó un problema que estaba teniendo con el cambio de rotación. Es posible que simplemente no pueda ver por mirar, pero ¿está esto documentado? es decir, ¿usar la etiqueta para recuperarse de un estado anterior? Quizás sea una respuesta obvia, pero soy bastante nuevo en el desarrollo de Android. - usuario484261

@ Industrial-antidepressant Es la identificación del contenedor (por ejemplo, un FrameLayout) al que se agregará el Fragmento. - Antonio

por cierto, para mi fue FragmentPageAdapter.instantiateItem(ViewGroup, int) más bien que FragmentPageAdapter.instantiateItem(View, int). - Nombre que se ve en la pagina

Por cierto, ¿sabe qué método de ciclo de vida de fragmentos (si corresponde) se llama cuando el fragmento se quita de la pantalla? Lo es onDetach(), ¿o algo mas? - Ygesher

Quiero ofrecer una solución que amplíe antonyt's maravillosa respuesta y mención de anular FragmentPageAdapter.instantiateItem(View, int) para guardar referencias a creado Fragments para que pueda trabajar en ellos más tarde. Esto también debería funcionar con FragmentStatePagerAdapter; consulte las notas para obtener más detalles.


Aquí hay un ejemplo simple de cómo obtener una referencia al Fragments devuelto por FragmentPagerAdapter que no depende de lo interno tags puesto en el Fragments. La clave es anular instantiateItem() y guardar referencias allí de en getItem().

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

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

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

or si prefieres trabajar con tags en lugar de variables de miembro de clase / referencias a la Fragments también puedes agarrar el tags establecido por FragmentPagerAdapter de la misma manera: NOTA: esto no se aplica a FragmentStatePagerAdapter ya que no se fija tags al crear su Fragments.

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

Tenga en cuenta que este método NO se basa en imitar el tag establecido por FragmentPagerAdapter y en su lugar utiliza las API adecuadas para recuperarlas. De esta manera incluso si el tag cambios en futuras versiones del SupportLibrary todavía estarás a salvo.


No lo olvides que dependiendo del diseño de tu Activity, la Fragments en el que está tratando de trabajar puede que exista o no todavía, por lo que debe tener en cuenta eso al hacer null verificaciones antes de usar sus referencias.

Además, si en cambio estás trabajando con FragmentStatePagerAdapter, entonces no querrás mantener referencias duras a tu Fragments porque es posible que tenga muchos de ellos y las referencias duras los guardarían innecesariamente en la memoria. En su lugar, guarde el Fragment referencias en WeakReference variables en lugar de las estándar. Como esto:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}

contestado el 23 de mayo de 17 a las 13:05

en la escasez de memoria, Android podría pedirle al FragmentManager que destruya los fragmentos no utilizados, ¿verdad? Si estoy en lo cierto, su segunda versión funcionará, la primera podría fallar. (No sé si las versiones actuales de Android hacen esto, pero parece que tenemos que esperar eso). Moritz Ambos

¿qué hay de crear un FragmetPagerAdapter en onCreate of Activity en cada rotación de pantalla. ¿Es esto incorrecto porque puede omitir la reutilización de fragmentos ya agregados en FragmentPagerAdapter - yggy

Primordial instantiateItem() es el camino a seguir; esto me ayudó a manejar las rotaciones de pantalla y recuperar mis instancias de Fragmento existentes una vez que se reanudaron la Actividad y el Adaptador; Me dejé comentarios en el código como recordatorio: después de la rotación, getItem() NO se invoca; solo este método instantiateItem() se llama. La súper implementación para instantiateItem() en realidad, vuelve a adjuntar fragmentos después de la rotación (según sea necesario), en lugar de crear instancias nuevas. - HolaImKevo

Obtengo un puntero nulo en Fragment createdFragment = (Fragment) super.instantiateItem.. en primera solución. - Alexs

Después de buscar entre todas las diversas iteraciones duplicadas / variantes de este problema, esta fue la mejor solución. (Referencia cruzada de otra Q con un título más relevante, ¡así que gracias por eso!) - MandisaW

Encontré otra solución relativamente fácil para tu pregunta.

Como se puede ver en la Código fuente de FragmentPagerAdapter, los fragmentos gestionados por FragmentPagerAdapter almacenar en el FragmentManager debajo de la etiqueta generada usando:

String tag="android:switcher:" + viewId + ":" + index;

Los programas viewId es la container.getId(), la container es tuyo ViewPager ejemplo. La index es la posición del fragmento. Por lo tanto, puede guardar la identificación del objeto en el outState:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("viewpagerid" , mViewPager.getId() );
}

@Override
    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null)
        viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );  

    MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);        
    mViewPager = (ViewPager) findViewById(R.id.pager);
    if (viewpagerid != -1 ){
        mViewPager.setId(viewpagerid);
    }else{
        viewpagerid=mViewPager.getId();
    }
    mViewPager.setAdapter(titleAdapter);

Si desea comunicarse con este fragmento, puede obtener si desde FragmentManager, como:

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")

Respondido 10 Feb 17, 17:02

hmmm No creo que este sea un buen camino a seguir, ya que su respuesta a una convención interna de nomenclatura de etiquetas que de ninguna manera será la misma para siempre. - Dori

Para aquellos que buscan una solución que no dependa de lo interno tag, considere intentarlo mi respuesta. - Tony Chan

Quiero ofrecer una solución alternativa para quizás un caso ligeramente diferente, ya que muchas de mis búsquedas de respuestas me llevaron a este hilo.

Mi caso - Estoy creando / agregando páginas dinámicamente y deslizándolas en un ViewPager, pero cuando se rota (onConfigurationChange) termino con una nueva página porque, por supuesto, se llama a OnCreate nuevamente. Pero quiero mantener la referencia a todas las páginas que se crearon antes de la rotación.

Problema - No tengo identificadores únicos para cada fragmento que creo, por lo que la única forma de hacer referencia era almacenar de alguna manera las referencias en una matriz para restaurar después del cambio de rotación / configuración.

Solución - El concepto clave fue que la Actividad (que muestra los Fragmentos) también administre la matriz de referencias a los Fragmentos existentes, ya que esta actividad puede utilizar Bundles en onSaveInstanceState

public class MainActivity extends FragmentActivity

Entonces, dentro de esta actividad, declaro un miembro privado para rastrear las páginas abiertas

private List<Fragment> retainedPages = new ArrayList<Fragment>();

Esto se actualiza cada vez que se llama a onSaveInstanceState y se restaura en onCreate

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

... así que una vez almacenado, se puede recuperar ...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

Estos fueron los cambios necesarios en la actividad principal, por lo que necesitaba los miembros y métodos dentro de mi FragmentPagerAdapter para que esto funcione, así que dentro de

public class ViewPagerAdapter extends FragmentPagerAdapter

una construcción idéntica (como se muestra arriba en MainActivity)

private List<Fragment> _pages = new ArrayList<Fragment>();

y esta sincronización (como se usó anteriormente en onSaveInstanceState) es compatible específicamente con los métodos

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

Y luego, finalmente, en la clase de fragmentos

public class CustomFragment extends Fragment

Para que todo esto funcione, hubo dos cambios, primero

public class CustomFragment extends Fragment implements Serializable

y luego agregar esto a onCreate para que los fragmentos no se destruyan

setRetainInstance(true);

Todavía estoy en el proceso de envolver mi cabeza en Fragmentos y el ciclo de vida de Android, así que una advertencia aquí es que puede haber redundancias / ineficiencias en este método. Pero funciona para mí y espero que pueda ser útil para otras personas con casos similares al mío.

Respondido el 04 de diciembre de 12 a las 04:12

+1 para setRetainInstance (verdadero): ¡la magia negra que estaba buscando! - declinación

Fue incluso más fácil usando FragmentStatePagerAdapter (v13). Que trata para ti con la restauración y liberación del estado. - El carnicero

Descripción clara y muy buena solución. ¡Gracias! - bruno bieri

Mi solución es muy grosera pero funciona: al ser mis fragmentos creados dinámicamente a partir de datos retenidos, simplemente elimino todos los fragmentos del PageAdapter antes de llamar super.onSaveInstanceState() y luego recrearlos en la creación de la actividad:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
    mSectionsPagerAdapter.removeAllfragments();
    super.onSaveInstanceState(outState);
}

No puedes eliminarlos en onDestroy(), de lo contrario, obtienes esta excepción:

java.lang.IllegalStateException: No se puede realizar esta acción después onSaveInstanceState

Aquí el código en el adaptador de página:

public void removeAllfragments()
{
    if ( mFragmentList != null ) {
        for ( Fragment fragment : mFragmentList ) {
            mFm.beginTransaction().remove(fragment).commit();
        }
        mFragmentList.clear();
        notifyDataSetChanged();
    }
}

Solo guardo la página actual y la restauro en onCreate(), después de que se hayan creado los fragmentos.

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );  

Respondido el 27 de Septiembre de 16 a las 11:09

No sé por qué, pero notifyDataSetChanged (); hace que la aplicación se bloquee - Malaquiasz

Que es eso BasePagerAdapter? Debe utilizar uno de los adaptadores de buscapersonas estándar, ya sea FragmentPagerAdapter or FragmentStatePagerAdapter, dependiendo de si desea Fragmentos que ya no son necesarios para el ViewPager para mantenerse alrededor (el primero) o para que su estado se guarde (el último) y se vuelva a crear si es necesario nuevamente.

Código de muestra para usar ViewPager puede ser encontrado aquí

Es cierto que la gestión de fragmentos en un buscapersonas de vista a través de instancias de actividad es un poco complicada, porque la FragmentManager en el marco se encarga de salvar el estado y restaurar cualquier fragmento activo que haya creado el buscapersonas. Todo esto realmente significa que el adaptador, al inicializar, debe asegurarse de que se vuelva a conectar con los fragmentos restaurados que haya. Puedes mirar el código para FragmentPagerAdapter or FragmentStatePagerAdapter Para ver cómo se hace esto.

Respondido 10 Feb 17, 17:02

El código BasePagerAdapter está disponible en mi pregunta. Como se puede ver, simplemente extiende FragmentPagerAdapter con el propósito de implementar Proveedor de títulos. Así que todo ya ha estado funcionando con el enfoque sugerido por el desarrollador de Android. - Oleksii Malovanyi

No conocía "FragmentStatePagerAdapter". Me salvaste literalmente horas. Gracias. - Knossos

Si alguien tiene problemas con su FragmentStatePagerAdapter que no restaura correctamente el estado de sus fragmentos ... es decir ... FragmentStatePagerAdapter está creando nuevos Fragmentos en lugar de restaurarlos desde el estado ...

Asegúrate de llamar ViewPager.setOffscreenPageLimit() ANTES de llamar ViewPager.setAdapter(fragmentStatePagerAdapter)

Al llamar ViewPager.setOffscreenPageLimit()... el ViewPager inmediatamente mire su adaptador e intente obtener sus fragmentos. Esto podría suceder antes de que ViewPager tenga la oportunidad de restaurar los Fragmentos de SavedInstanceState (creando así nuevos Fragmentos que no se pueden reinicializar desde SavedInstanceState porque son nuevos).

respondido 25 mar '20, 07:03

Se me ocurrió esta solución simple y elegante. Asume que la actividad es responsable de crear los Fragmentos, y el Adaptador simplemente los sirve.

Este es el código del adaptador (nada raro aquí, excepto por el hecho de que mFragments es una lista de fragmentos mantenidos por la Actividad)

class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {

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

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        TabFragment fragment = (TabFragment)mFragments.get(position);
        return fragment.getTitle();
    }
} 

Todo el problema de este hilo es obtener una referencia de los fragmentos "antiguos", así que utilizo este código en la actividad onCreate.

    if (savedInstanceState!=null) {
        if (getSupportFragmentManager().getFragments()!=null) {
            for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                mFragments.add(fragment);
            }
        }
    }

Por supuesto, puede ajustar aún más este código si es necesario, por ejemplo, asegurándose de que los fragmentos sean instancias de una clase en particular.

Respondido 19 Jul 16, 17:07

Para obtener los fragmentos después del cambio de orientación, debe usar .getTag ().

    getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)

Para un poco más de manejo, escribí mi propia ArrayList para que mi PageAdapter obtenga el fragmento por viewPagerId y FragmentClass en cualquier posición:

public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";

private ArrayList<MyPageBuilder> fragmentPages;

public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
    super(fm);
    fragmentPages = fragments;
}

@Override
public Fragment getItem(int position) {
    return this.fragmentPages.get(position).getFragment();
}

@Override
public CharSequence getPageTitle(int position) {
    return this.fragmentPages.get(position).getPageTitle();
}

@Override
public int getCount() {
    return this.fragmentPages.size();
}


public int getItemPosition(Object object) {
    //benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden

    Log.d(logTAG, object.getClass().getName());
    return POSITION_NONE;
}

public Fragment getFragment(int position) {
    return getItem(position);
}

public String getTag(int position, int viewPagerId) {
    //getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())

    return "android:switcher:" + viewPagerId + ":" + position;
}

public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
    return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}


public static class MyPageBuilder {

    private Fragment fragment;

    public Fragment getFragment() {
        return fragment;
    }

    public void setFragment(Fragment fragment) {
        this.fragment = fragment;
    }

    private String pageTitle;

    public String getPageTitle() {
        return pageTitle;
    }

    public void setPageTitle(String pageTitle) {
        this.pageTitle = pageTitle;
    }

    private int icon;

    public int getIconUnselected() {
        return icon;
    }

    public void setIconUnselected(int iconUnselected) {
        this.icon = iconUnselected;
    }

    private int iconSelected;

    public int getIconSelected() {
        return iconSelected;
    }

    public void setIconSelected(int iconSelected) {
        this.iconSelected = iconSelected;
    }

    public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
        this.pageTitle = pageTitle;
        this.icon = icon;
        this.iconSelected = selectedIcon;
        this.fragment = frag;
    }
}

public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
    private final String logTAG = MyPageArrayList.class.getName() + ".";

    public MyPageBuilder get(Class cls) {
        // Fragment über FragmentClass holen
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return super.get(indexOf(item));
            }
        }
        return null;
    }

    public String getTag(int viewPagerId, Class cls) {
        // Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return "android:switcher:" + viewPagerId + ":" + indexOf(item);
            }
        }
        return null;
    }
}

Así que crea un MyPageArrayList con los fragmentos:

    myFragPages = new MyPageAdapter.MyPageArrayList();

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_data_frag),
            R.drawable.ic_sd_storage_24dp,
            R.drawable.ic_sd_storage_selected_24dp,
            new WidgetDataFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_color_frag),
            R.drawable.ic_color_24dp,
            R.drawable.ic_color_selected_24dp,
            new WidgetColorFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_textsize_frag),
            R.drawable.ic_settings_widget_24dp,
            R.drawable.ic_settings_selected_24dp,
            new WidgetTextSizeFrag()));

y agréguelos al viewPager:

    mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
    myViewPager.setAdapter(mAdapter);

después de esto, puede obtener después de la orientación cambiar el fragmento correcto usando su clase:

        WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
            .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));

Respondido el 28 de enero de 17 a las 23:01

Una opinión un poco diferente en lugar de almacenar los Fragmentos usted mismo, simplemente déjela en FragmentManager y cuando necesite hacer algo con los fragmentos, búsquelos en FragmentManager:

//make sure you have the right FragmentManager 
//getSupportFragmentManager or getChildFragmentManager depending on what you are using to manage this stack of fragments
List<Fragment> fragments = fragmentManager.getFragments();
if(fragments != null) {
   int count = fragments.size();
   for (int x = 0; x < count; x++) {
       Fragment fragment = fragments.get(x);
       //check if this is the fragment we want, 
       //it may be some other inspection, tag etc.
       if (fragment instanceof MyFragment) {
           //do whatever we need to do with it
       }
   }
}

Si tiene muchos Fragmentos y el costo de instancia de verificación puede no ser lo que desea, pero es bueno tener en cuenta que FragmentManager ya mantiene una cuenta de Fragmentos.

respondido 05 nov., 20:12

añadir:

   @SuppressLint("ValidFragment")

antes de tu clase.

si no funciona, haz algo como esto:

@SuppressLint({ "ValidFragment", "HandlerLeak" })

Respondido el 17 de diciembre de 15 a las 20:12

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.