onCreateOptionsMenu вызывается слишком много раз в ActionBar с использованием вкладок - PullRequest
14 голосов
/ 29 августа 2011

Вот моя проблема. У меня есть приложение, в котором я использую ActionBar Sherlock с вкладками, фрагменты с опциями меню. Каждый раз, когда я поворачиваю эмулятор, добавляются меню для всех фрагментов, даже тех, которые скрыты / удалены (я пробовал оба).

Это настройка: One FragmentActivity, которая имеет панель действий с

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

Все вкладки используют один и тот же прослушиватель:

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

Каждый подкласс FragmentListBase имеет свое собственное меню, и поэтому все 3 подкласса имеют:

  setHasOptionsMenu(true);

и соответствующий

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

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

Когда я запускаю приложение, я вижу, что onCreateOptionsMenu вызывается несколько раз для всех разных фрагментов.

Я в полном замешательстве.

Я попытался опубликовать как можно больше кода без лишних хлопот, если вы обнаружите, что чего-то не хватает, пожалуйста, сообщите.

[Изменить] Я добавил больше логов, и оказалось, что фрагмент прикрепляется дважды (или больше) при ротации. Я заметил одну вещь: все вызывается несколько раз, кроме метода onCreate (), который вызывается только один раз.

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

[Изменить 2]

Хорошо, я начал прослеживать код Android и нашел эту часть здесь (которую я отредактировал, чтобы сократить этот пост).

/ com_actionbarsherlock / SRC / Android / поддержка / v4 / приложение / 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);
            }
        }
    }

Проблема в том, что в mAdded действительно есть несколько экземпляров FragmentList1, поэтому метод onCreateOptionsMenu () «правильно» вызывается 3 раза, но для разных экземпляров класса FragmentList1. Чего я не понимаю, так это того, почему этот класс добавляется несколько раз ... Но это чертовски хороший пример.

Ответы [ 6 ]

7 голосов
/ 30 августа 2011

Кажется, я нашел проблему (ы). Я говорю «проблема», потому что помимо множества меню теперь есть и исключение.

1) звонок на

  bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

, то есть после вызовов addTab () имеет побочный эффект вызова onTabSelected (). Мой TabListener затем добавил бы FragmentList1 к FragmentManager

2) вращение устройства разрушит активность, как и ожидалось, но не разрушит фрагменты. Когда новое действие создается после ротации, оно делает две вещи:

  1. создать еще один набор фрагментов, который он добавит в FragmentManager. Это то, что вызывало множество меню
  2. вызовите onTabSelected (через setNavigationMode ()), который будет выполнять следующий код:

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

Обычно, если фрагмент уже находится в FragmentManager, добавлять его не нужно, просто покажите его. Но в этом и заключается проблема. Это не тот же фрагмент! Это фрагмент, который был создан более ранним экземпляром Activity. Поэтому он попытается прикрепить и показать этот вновь созданный фрагмент, который вызовет исключение

Решение.

Нужно было сделать несколько вещей, чтобы все это исправить.

1) Я переместил setNavigationMode () над надстройками addTab ().

2) вот как я сейчас создаю свои вкладки:

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

Итак, при создании Активности я должен проверить, есть ли Фрагменты в FragmentManager. Если они есть, я использую эти экземпляры, если нет, то я создаю новые. Это делается для всех трех вкладок.

Возможно, вы заметили, что есть две схожие метки: m_fragment.LIST_TAG и FragmentList1.LIST_TAG_STATIC. Ах, это прекрасно ... (<- сарказм) </p>

Чтобы использовать мой TagListener полиморфно, я объявил следующую нестатическую переменную в базовом классе:

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

Он присваивается изнутри потомкам и позволяет мне просматривать в FragmentManager различные потомки FragmentListBase.

Но мне также нужно искать конкретных потомков ДО того, как они будут созданы (потому что мне нужно знать, должен ли я их создавать), поэтому я также должен объявить следующую статическую переменную.

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

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

Достаточно сказать, что я разочарован, что никто не придумал это простое и элегантное решение (<- больше сарказма) </p>

Большое спасибо Джейку Уортону, который нашел время, чтобы посмотреть на это для меня:)

6 голосов
/ 29 августа 2011
public FragmentListBase() {
    setRetainInstance(true);
    setHasOptionsMenu(true);
}

Это сохранит / восстановит отдельные состояния каждого из фрагментов при вращении.


Еще одно простое изменение, которое вы можете сделать, это вызвать transaction.replace(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG) на вкладке выбранный обратный вызов и избавиться от содержимого в невыбранном обратном вызове.

3 голосов
/ 15 ноября 2012

У меня были очень похожие проблемы с "наращиваемыми" меню при вращении. Я не использую вкладки, но я использую ViewPager с FragmentStatePagerAdapter, поэтому я не могу использовать мои фрагменты. После двухдневного удара головой я нашел очень простое решение. Действительно, похоже, проблема в том, что onCreateOptionsMenu вызывается несколько раз. Этот маленький фрагмент кода заботится (маскирует?) Обо всех проблемах:

/** 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;
    }
}
1 голос
/ 12 февраля 2016

Для меня работало перемещение setHasMenuOptions (true) в вызывающее действие, то есть в действие, в котором был объявлен фрагмент. У меня раньше это было в методе onCreate фрагмента.

Вот фрагмент кода:

@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 голосов
/ 08 июня 2012

Просто замечание о ваших проблемах с полиморфными тегами.

Объявите ваш базовый класс следующим образом:

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

Теперь объявите ваши подклассы примерно так:

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

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

Теперь полиморфный способ получить тег экземпляра выглядит следующим образом:

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

Получить тег статически так:

FragmentList1.LIST_TAG;
0 голосов
/ 03 октября 2011

По крайней мере в SDK, связанном с сотами, проблема решается добавлением

android:configChanges="orientation"

к объявлению Activity в вашем файле AndroidManifest.xml. Вы по-прежнему можете добавлять и удалять фрагменты, как показано в разделе «Добавление вкладок» http://developer.android.com/guide/topics/ui/actionbar.html

...