Подтвердить что ты не робот

OnCreateOptionsMenu вызывается слишком много раз в ActionBar, используя вкладки

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

Это параметр: One FragmentActivity, который имеет ActionBar с

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

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

4b9b3361

Ответ 1

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

1) вызов

  bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

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

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

  • создайте еще один набор фрагментов, которые он добавит к FragmentManager. Это то, что вызывало множество меню.
  • вызвать 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() s.

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. Ах, это мило... (< - сарказм)

В ordrer для использования моего 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;
   };
}

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

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

Ответ 2

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

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


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

Ответ 3

У меня были очень похожие проблемы с "штабелируемыми" меню при вращении. Я не использую вкладки, но я использую ViewPager с FragmentStatePagerAdapter, поэтому я не могу повторно использовать свои фрагменты. После того, как я ударил головой в течение 2 дней, я нашел очень простое решение. В самом деле проблема, кажется, связана с 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;
    }
}

Ответ 4

Что сработало для меня, было перемещение 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();
    }

Ответ 5

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

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

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;

Ответ 6

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

android:configChanges="orientation"

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