这是我的问题。我有一个应用程序,我正在使用带有选项卡的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);
选项卡都使用相同的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) {
}
}
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类的不同实例。我不明白为什么这个课程被多次添加......但这是一个很好的领导。
答案 0 :(得分:7)
我好像发现了问题。我说问题是因为在众多菜单之上,现在还有一个例外。
1)致电
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
在之后对addTab()的调用具有调用onTabSelected()的副作用。然后我的TabListener会将FragmentList1添加到FragmentManager
2)旋转设备会按预期破坏Activity,但不会破坏Fragments。在轮换后创建新的Activity时,它会做两件事:
调用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。所以它会尝试附加并显示这个新创建的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时,我必须检查片段是否已经存在于FragmentManager中。如果他们是我使用那些实例,如果没有,那么我创建新实例。这是针对所有三个选项卡完成的。
您可能已经注意到有两个相似的标签:m_fragment.LIST_TAG和FragmentList1.LIST_TAG_STATIC。啊,这很可爱......(&lt;-sarcasm)
在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;
};
}
我只想说我没有人想出这个简单而优雅的解决方案(&lt; - more sarcasm)
非常感谢Jake Wharton花时间为我看这个:)
答案 1 :(得分:6)
public FragmentListBase() {
setRetainInstance(true);
setHasOptionsMenu(true);
}
这将在旋转时保存/恢复每个片段的各个状态。
您可能想要做的另一个简单的更改是在选项卡选择的回调中调用transaction.replace(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG)
并删除未选择的回调中的内容。
答案 2 :(得分:3)
旋转时“可堆叠”菜单出现了类似的问题。我不使用制表符,但我使用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;
}
}
答案 3 :(得分:1)
对我来说有用的是将setHasMenuOptions(true)移动到调用活动,即声明片段的活动。我以前在片段的onCreate方法中有它。
以下是代码段:
Each frame (§2.6) contains an array of variables known as its local variables.
[...]
Local variables are addressed by indexing.
答案 4 :(得分: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;
答案 5 :(得分:-4)
至少在与Honeycomb相关的SDK上,通过添加
解决了这个问题android:configChanges="orientation"
到AndroidManifest.xml文件中的Activity声明。 您仍然可以添加和删除片段,如http://developer.android.com/guide/topics/ui/actionbar.html
的“添加标签”部分所示