onCreateOptionsMenu在ActionBar中使用选项卡被调用了太多次

Mik*_*Dev 14 android actionbarsherlock android-actionbar

这是我的问题.我有一个应用程序,我正在使用带有选项卡的ActionBar Sherlock,带有选项菜单的片段.每次我旋转模拟器时,都会为所有碎片添加菜单,甚至是那些被隐藏/删除的碎片(我都试过了).

这是设置: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);
Run Code Online (Sandbox Code Playgroud)

选项卡都使用相同的Listener:

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) {
  }
}
Run Code Online (Sandbox Code Playgroud)

FragmentListBase的每个子类都有自己的菜单,因此所有3个子类都有:

  setHasOptionsMenu(true);
Run Code Online (Sandbox Code Playgroud)

和适当的

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

  inflater.inflate(R.menu.il_options_menu, menu);
}
Run Code Online (Sandbox Code Playgroud)

当我运行应用程序时,我可以看到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
Run Code Online (Sandbox Code Playgroud)

[编辑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);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

问题是mAdded确实在其中有多个FragmentList1实例,因此onCreateOptionsMenu()方法被"正确"调用3次,但是对于FragmentList1类的不同实例.我不明白为什么这个课程被多次添加......但这是一个很好的领导.

Mik*_*Dev 7

我似乎找到了问题.我说问题是因为在众多菜单之上,现在还有一个例外.

1)致电

  bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
Run Code Online (Sandbox Code Playgroud)

这是的调用addTab()具有主叫onTabSelected的副作用().然后我的TabListener将FragmentList1添加到FragmentManager

2)旋转设备会按预期破坏Activity,但不会破坏Fragments.在旋转后创建新的Activity时,它会做两件事:

  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);
     }
    
    Run Code Online (Sandbox Code Playgroud)

基本上,如果片段已经在FragmentManager中,则无需添加它,只需显示它即可.但问题就在于此.这不是片段!它是由早期Activity活动创建的Fragment.所以它会尝试附加并显示这个新创建的Fragment,这会导致异常

解决方案.

为了解决所有这些问题,有几件事要做.

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)));
Run Code Online (Sandbox Code Playgroud)

因此,在创建Activity时,我必须检查片段是否已经存在于FragmentManager中.如果他们是我使用那些实例,如果没有,那么我创建新实例.这是针对所有三个选项卡完成的.

您可能已经注意到有两个类似的标签:m_fragment.LIST_TAG和FragmentList1.LIST_TAG_STATIC.啊,这很可爱......(< - sarcasm)

在ordrer中以多态方式使用我的TagListener我在基类中声明了以下非静态变量:

public class FragmentListBase extends Fragment {
   public String LIST_TAG = null;
}
Run Code Online (Sandbox Code Playgroud)

它是从后代内部分配的,允许我在FragmentManager中查找FragmentListBase的不同后代.

但我也需要才创建搜索特定的后代(因为我需要知道,如果我必须创建与否),所以我也要申报以下静态变量.

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

   public FragmentList1() {
      LIST_TAG = LIST_TAG_STATIC;
   };
}
Run Code Online (Sandbox Code Playgroud)

我只想说没有人想出这个简单而优雅的解决方案(< - 更多讽刺)我感到沮丧

非常感谢Jake Wharton花时间为我看这个:)


Jak*_*ton 6

public FragmentListBase() {
    setRetainInstance(true);
    setHasOptionsMenu(true);
}
Run Code Online (Sandbox Code Playgroud)

这将在旋转时保存/恢复每个片段的各个状态.


您可能想要进行的另一个简单更改是调用transaction.replace(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG)选项卡选择的回调并删除未选择的回调中的内容.