2011-08-28 17 views
14

Aquí está mi problema. Tengo una aplicación donde estoy usando ActionBar Sherlock con pestañas, fragmentos con menús de opciones. Cada vez que giro el emulador, se agregan menús para todos los fragmentos, incluso los que están ocultos/eliminados (probé ambos).onCreateOptionsMenu se está llamando demasiadas veces en ActionBar usando las pestañas

Este es el escenario: Una FragmentActivity, que tiene una Barra de acciones con

final ActionBar bar = getSupportActionBar(); 

    bar.addTab(bar.newTab() 
     .setText("1") 
     .setTabListener(new MyTabListener(new FragmentList1()))); 

    bar.addTab(bar.newTab() 
     .setText("2") 
     .setTabListener(new MyTabListener(new FragmentList2()))); 

    bar.addTab(bar.newTab() 
     .setText("3") 
     .setTabListener(new MyTabListener(new FragmentList3()))); 

    bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 
    bar.setDisplayShowHomeEnabled(true); 
    bar.setDisplayShowTitleEnabled(true); 

Las pestañas todos utilizan el mismo Oyente:

private class MyTabListener implements ActionBar.TabListener { 
    private final FragmentListBase m_fragment; 


    public MyTabListener(FragmentListBase fragment) { 
    m_fragment = fragment; 
    } 


    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { 
    FragmentManager fragmentMgr = ActivityList.this.getSupportFragmentManager(); 
    FragmentTransaction transaction = fragmentMgr.beginTransaction(); 

     transaction.add(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG); 

    transaction.commit(); 
    } 


    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { 
    FragmentManager fragmentMgr = ActivityList.this.getSupportFragmentManager(); 
    FragmentTransaction transaction = fragmentMgr.beginTransaction(); 

    transaction.remove(m_fragment); 
    transaction.commit(); 
    } 


    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { 
    } 
} 

Cada subclase de FragmentListBase tiene su propio menú y por lo tanto todos 3 subclases tienen:

setHasOptionsMenu(true); 

y el apropiado

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 
    Log.d(TAG, "OnCreateOptionsMenu"); 

    inflater.inflate(R.menu.il_options_menu, menu); 
} 

Cuando ejecuto la aplicación puedo ver que se llama al onCreateOptionsMenu varias veces, para todos los diferentes fragmentos.

Estoy totalmente perplejo.

Intenté publicar la mayor cantidad de código posible sin ser abrumador, si encuentra que falta algo, por favor avise.

[Editar] He añadido más registro, y resulta que el fragmento se adjunta dos (o más) veces en la rotación. Una cosa que noto es que todo se llama varias veces a excepción del método onCreate() que se llama una sola vez.

06.704:/WindowManager(72): Setting rotation to 0, animFlags=0 
06.926:/ActivityManager(72): Config changed: { scale=1.0 imsi=310/260 loc=en_US touch=3 keys=1/1/2 nav=1/2 orien=L layout=0x10000014 uiMode=0x11 seq=35} 
07.374:/FragmentList1(6880): onAttach 
07.524:/FragmentList1(6880): onCreateView 
07.564:/FragmentList1(6880): onAttach 
07.564:/FragmentListBase(6880): onCreate 
07.564:/FragmentList1(6880): OnCreateOptionsMenu 
07.574:/FragmentList1(6880): OnCreateOptionsMenu 
07.604:/FragmentList1(6880): onCreateView 

[Editar 2]

Ok, empecé a trazar de nuevo en código de Android y encontré esta parte aquí (que he editado para acortar este post).

/com_actionbarsherlock/src/android/support/v4/app/FragmentManager.java

public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) { 
    if (mActive != null) { 
     for (int i=0; i<mAdded.size(); i++) { 
      Fragment f = mAdded.get(i); 
      if (f != null && !f.mHidden && f.mHasMenu) { 
       f.onCreateOptionsMenu(menu, inflater); 
      } 
     } 
    } 

El problema es que mAdded tiene de hecho varias instancias de FragmentList1 en ella, por lo que el método onCreateOptionsMenu() es " correctamente "siendo llamado 3 veces, pero para diferentes instancias de la clase FragmentList1. Lo que no entiendo es por qué esa clase se está agregando varias veces ... Pero eso es una gran ventaja.

Respuesta

7

Parece que he encontrado el problema (s). Digo problema (s) porque encima de la multitud de menús, ahora también hay una excepción.

1) el llamado a

bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 

que es después de las llamadas a addTab() tiene un efecto secundario de llamar onTabSelected(). Mi TabListener agregaría un FragmentList1 al FragmentManager

2) al rotar el dispositivo se destruiría la Actividad como se esperaba, pero no se destruirían los Fragmentos. Cuando la nueva actividad se crea después de la rotación, haría dos cosas:

  1. crear otro conjunto de fragmentos que se agregaría a FragmentManager. Esto es lo que estaba causando la multitud de menús
  2. llamada onTabSelected (a través de setNavigationMode()) que realice el siguiente código:

    if (null != fragmentMgr.findFragmentByTag(m_fragment.LIST_TAG)) { 
        transaction.attach(m_fragment); 
        transaction.show(m_fragment); 
    } 
    else { 
        transaction.add(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG); 
    } 
    

Básicamente, si el fragmento ya está en la FragmentManager no hay necesito agregarlo, solo muéstralo. Pero ahí radica el problema. ¡No es el mismo Fragmento! Es el Fragmento que fue creado por la instancia anterior de la Actividad. Por lo tanto, intentaría adjuntar y mostrar este Fragmento recién creado que causaría una Excepción

La Solución.

Hubo algunas cosas que hacer para arreglar todo esto.

1) Moví el setNavigationMode() sobre el addTab() s.

2) así es como ahora creo mis pestañas:

FragmentListBase fragment = (FragmentListBase)fragmentMgr.findFragmentByTag(FragmentList1.LIST_TAG_STATIC); 
    if (null == fragment) { 
    fragment = new FragmentList1(); 
    } 
    bar.addTab(bar.newTab() 
     .setText("1") 
     .setTabListener(new MyTabListener(fragment))); 

Así que tras la creación de actividad que tengo que comprobar para ver si los fragmentos ya están en el FragmentManager. Si son, yo uso esas instancias, si no, entonces creo nuevas. Esto se hace para las tres pestañas.

Puede haber notado que hay dos etiquetas similares: m_fragment.LIST_TAG y FragmentList1.LIST_TAG_STATIC. Ah, esto es precioso ... (< - sarcasmo)

Y para hacer uso Mi TagListener polimórfica he declarado lo siguiente variable estática no en la clase base:

public class FragmentListBase extends Fragment { 
    public String LIST_TAG = null; 
} 

Se asigna desde el interior del descendientes y me permite buscar en FragmentManager los diferentes descendientes de FragmentListBase.

Pero también necesito buscar descendientes específicos ANTES de que se creen (porque necesito saber si debo crearlos o no), así que también tengo que declarar la siguiente variable estática.

public class FragmentList1 extends FragmentListBase { 
    public final static String LIST_TAG_STATIC = "TAG_LIST_1"; 

    public FragmentList1() { 
     LIST_TAG = LIST_TAG_STATIC; 
    }; 
} 

Baste decir que estoy decepcionado de que nadie se le ocurrió esta solución simple y elegante (< - más sarcasmo)

Muchas gracias a Jake Wharton que se tomaron el tiempo para mirar esto para mí :)

+1

Esta respuesta es demasiado complicada. Simplemente moviendo la llamada a 'bar.setNavigationMode (ActionBar.NAVIGATION_MODE_TABS)' antes de agregar pestañas a * ActionBar * solucionó mi problema. – Phil

6
public FragmentListBase() { 
    setRetainInstance(true); 
    setHasOptionsMenu(true); 
} 

Esto guardará/restaurará los estados individuales de cada uno de los fragmentos al girarlos.


Otra simple cambio es posible que desee hacer es llamar transaction.replace(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG) en la ficha seleccionada de devolución de llamada y deshacerse del contenido de la devolución de llamada no seleccionada.

+0

Gracias por la respuesta Jake, sin embargo: nada. Agregué eso al código pero no cambio. Agregué más información a mi pregunta ... – MikeWallaceDev

-4

al menos en nido de abeja relacionados SDK es el problema se resuelve añadiendo

android:configChanges="orientation" 

a la declaración de actividad en su Andr archivo oidManifest.xml Aún puede agregar y eliminar fragmentos como se muestra en la sección Agregar pestañas de http://developer.android.com/guide/topics/ui/actionbar.html

0

Solo una nota sobre sus frustraciones de etiquetas polimórficas.

declarar una clase de base de este modo:

public abstract class ListFragmentBase { 
    protected abstract String getListTag(); 
} 

Ahora declaran su subclases algo como esto:

public class FragmentList1 extends ListFragmentBase { 
    public static final String LIST_TAG = "TAG_LIST_1"; 

    @Override 
    protected String getListTag() { 
     return LIST_TAG; 
    } 
} 

Ahora, la forma polimórfica para obtener la etiqueta de instancia es la siguiente:

ListFragmentBase frag = new FragmentList1(); 
frag.getListTag(); 

Recibe la etiqueta estáticamente así:

FragmentList1.LIST_TAG; 
+1

Incluso puede establecer la cadena LIST_TAG estática en privado para evitar confusiones sobre qué propiedad usar. –

+0

@DominikvonWeber Puede establecer LIST_TAG en privado, pero luego no puede acceder a él estáticamente. Dependería de tus necesidades, supongo. En la solución, el usuario accede de forma estática al – Dave

3

Tuve problemas similares con los menús "apilables" en rotación. No uso pestañas, pero sí uso ViewPager con FragmentStatePagerAdapter, así que realmente no puedo reutilizar mis Fragmentos. Después de golpearme la cabeza durante 2 días, encontré una solución muy simple. De hecho, el problema parece ser onCreateOptionsMenu llamado varias veces. Este pequeño fragmento de código se encarga máscaras (?) De todos los problemas:

/** to prevent multiple calls to inflate menu */ 
private boolean menuIsInflated; 

@Override 
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { 
    if (!menuIsInflated) { 
     inflater.inflate(R.menu.job_details_fragment_menu, menu); 
     menuIsInflated = true; 
    } 
} 
+0

, apesta que tuvimos que hacer esto ... Juro que este es un error de su parte. – reidisaki

1

lo que funcionó para mí estaba moviendo los setHasMenuOptions (verdadero) para la actividad de llamadas es decir, la actividad en la que se declaró el fragmento. Anteriormente lo tenía en el método onCreate del fragmento.

Aquí es el fragmento de código:

@Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 
     FragmentManager fragmentManager = getFragmentManager(); 
     FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); 

     ForecastFragment forecastFragment = new ForecastFragment(); 
     forecastFragment.setHasOptionsMenu(true); 
     fragmentTransaction.add(R.id.fragment, forecastFragment); 
     fragmentTransaction.commit(); 
    } 
+0

Ninguna de las respuestas anteriores funcionó para mí, pero tu respuesta sí. ¡Gracias! –

Cuestiones relacionadas