15

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

这是设置:一个 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 类的不同实例。我不明白为什么要多次添加该课程……但这是一个很好的线索。

4

5 回答 5

7

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

1) 调用

  bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

在对 addTab() 的调用具有调用 onTabSelected() 的副作用之后然后我的 TabListener 会将 FragmentList1 添加到 FragmentManager

2)旋转设备会按预期破坏活动,但不会破坏片段。在轮换后创建新 Activity 时,它会做两件事:

  1. 创建另一组将添加到 FragmentManager 的 Fragment。这就是导致众多菜单的原因
  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 的早期实例创建的 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)));

因此,在创建 Activity 时,我必须检查 Fragment 是否已经在 FragmentManager 中。如果他们是我使用这些实例,如果不是那么我创建新的。这是为所有三个选项卡完成的。

您可能已经注意到有两个相似的标签:m_fragment.LIST_TAG 和 FragmentList1.LIST_TAG_STATIC。啊,这太可爱了……(<-讽刺)

为了以多态方式使用我的 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;
   };
}

可以说我很失望没有人想出这个简单而优雅的解决方案(<-更多讽刺)

非常感谢杰克沃顿花时间帮我看这个:)

于 2011-08-29T21:32:03.477 回答
6
public FragmentListBase() {
    setRetainInstance(true);
    setHasOptionsMenu(true);
}

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


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

于 2011-08-29T01:32:33.350 回答
3

我在旋转时遇到了与“可堆叠”菜单非常相似的问题。我不使用选项卡,但我确实将 ViewPager 与 FragmentStatePagerAdapter 一起使用,所以我不能真正重用我的 Fragments。在敲了我的头两天后,我找到了非常简单的解决方案。确实,问题似乎与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;
    }
}
于 2012-11-14T21:29:07.197 回答
1

对我有用的是将 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();
    }
于 2016-02-12T16:07:32.570 回答
0

只是对你的多态标签挫折的一个相当注意。

像这样声明你的基类:

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;
于 2012-06-08T17:16:50.847 回答