Android Honeycomb:如何更改 FrameLayout 中的片段,而不重新创建它们?

Posted

技术标签:

【中文标题】Android Honeycomb:如何更改 FrameLayout 中的片段,而不重新创建它们?【英文标题】:Android Honeycomb: How to change Fragments in a FrameLayout, without re-creating them? 【发布时间】:2011-09-05 07:54:55 【问题描述】:

是否可以在片段之间切换而无需一直重新创建它们?如果有,怎么做?

In the documentation 我找到了一个如何替换 Fragments 的示例。

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

但我不想每次需要时都从头开始创建片段。

我还发现了 this example 的隐藏/显示片段:

// The content view embeds two fragments; now retrieve them and attach
// their "hide" button.
FragmentManager fm = getFragmentManager();
addShowHideListener(R.id.frag1hide, fm.findFragmentById(R.id.fragment1));
addShowHideListener(R.id.frag2hide, fm.findFragmentById(R.id.fragment2));

但是如何在 XML 文件之外创建一个带有 ID 的片段?

我认为这可能与this question 有关,但没有答案。 :/

非常感谢您, 水母

编辑:

我现在就是这样:

Fragment shown = fragmentManager.findFragmentByTag(shownFragment);

//...

FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (shown != null) fragmentTransaction.hide(shown);

//switch statetement for menu selection, just one example:

SettingsFragment set = (SettingsFragment) fragmentManager.findFragmentByTag(SET);
Toast.makeText(this, "Settings:" + set, Toast.LENGTH_LONG).show();
if (set == null)

        set = new SettingsFragment();
        fragmentTransaction.add(R.id.framelayout_content, set, SET);

else fragmentTransaction.show(set);
shownFragment = SET;
fragmentTransaction.commit();

如果我调用设置,然后调用其他内容,然后返回设置,吐司首先给我“null”,然后给我“Settings:SettingsFragment40ef...”。

但是,如果我将 fragmentTransaction.add(R.id.framelayout_content, set, SET); 替换为 fragmentTransaction.replace(R.id.framelayout_content, set, SET);,我会不断收到“null”、“null”、“null”...所以它似乎无法通过标签找到 Fragment。

编辑2:

添加fragmentTransaction.addToBackStack(null); 成功了。 :) 这节省了整个隐藏/记忆哪个片段被显示的部分,所以我认为这是最优雅的解决方案。

我发现 this 教程对这个主题很有帮助。

编辑3:

看着我的代码,我意识到我可以去掉一些部分,所以我把它改成了:

FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (shown != null) fragmentTransaction.hide(shown);
Settings set = (Settings) fragmentManager.findFragmentByTag(SET);
if (set == null) set = new Settings();

fragmentTransaction.replace(R.id.framelayout_content, set, SET);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();

但是,这调用了 IllegalStateException: Fragment already added,与 here 非常相似。有没有简单的方法来防止这种情况?否则我想我可能会切换回隐藏/显示位。

【问题讨论】:

【参考方案1】:

这可能取决于您试图避免被重新创建的内容。

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

在您的示例示例中,当您从 newFragment 中点击后退按钮时,将显示前一个片段(您将获得 onCreateView、onActivityCreated 但没有 onCreate),因此该片段不会被重新创建。至于你的 newFragment,如果你打算再次使用它来更新任何内部状态,比如 onCreate 或 onActivityCreated,你仍然可以保留它。

编辑:

如果您只是有一个菜单列表,其中每个条目在右窗格中调用不同的片段,那么添加到后台堆栈不是您想要的。为此,您可以预先在每个片段上调用add(...) 并根据需要简单地隐藏/显示每个片段(我没有对此进行测试)。否则,我建议保留对每个片段的引用,在选择不同的菜单项时调用 replace(...),确保您不会添加到后台堆栈。

【讨论】:

我想根据菜单选择在 Fragments 之间切换。因此,如果您再次选择相同的菜单项,我也想再次显示相同的片段。使用“替换”,我再也无法通过标签找到片段了。 然后我会简单地保留内存中的片段并进行替换。鉴于您可以跟踪当前菜单选择并知道正在显示的片段,我不明白为什么需要找到片段。 However, when a new menu item is selected then I don't see why finding the fragment by tag shouldn't work (I've done something similar with a DialogFragment as does, if memory serves, the DialogFragment samples in API demos. 那么,您是否建议我应该在班级中为每个 Fragment 保留一个参考字段?对于另一部分,请参阅我的编辑。 我发现显示/隐藏机制有点混乱。如果我在 Edit3 中没有错,你可以找到一个非空的“设置”片段,它是活动状态的一部分,然后用它调用 replace(...),这样你就会得到已经添加的异常。我认为添加到后台堆栈可能是不必要的。如果您只是有一个菜单列表,其中每个条目在右窗格中调用不同的片段,那么您可能会在每个条目上调用“添加”(不要调用 addToBackStack)并根据需要简单地隐藏/显示。否则保持对每个片段的引用,调用替换并且不要添加到后台堆栈。 @jellyfish,请注意link,因为存在一个错误,这意味着如果替换之前弹出的片段,您不应该使用replace。请改用remove()add()【参考方案2】:

为了避免

IllegalStateException:片段 已经添加了

我找到了一个适合我的解决方法:在您的事务中使用 remove(AFrag)add(BFrag),而不是 replace()

看起来这是一个错误:4th comment in the accepted answer。

【讨论】:

PJL 在他的评论中提到了类似的东西。那么 remove/add 让 Fragment 保持活动状态并保存其内容? 是的,在删除/添加操作之后,我保留了对 mAFrag 和 mBFrag 的引用,并且 popBackStack mAFrag 保留了他的内容。此外,如果我再次前进,mBFrag 也会保留内容。我刚刚发现@PJL 评论,它被隐藏了。对不起:S 但是 remove() 也会删除片段的父视图,而 replace() 不会【参考方案3】:
fragmentTransactionOnClick.setTransition(FragmentTransaction.TRANSIT_EXIT_MASK);

如果你添加 .setTransition.exit transit_exit_mask 那么之前的视图就不会出现

【讨论】:

【参考方案4】:

我找到了一种使用“标签”功能的方法:

//...
fragmentTransaction.add(R.id.framelayout_content, fragment1, "foo");
fragmentTransaction.add(R.id.framelayout_content, fragment2, "bar");

//...

fragmentManager.findFragmentByTag("foo");
fragmentManager.findFragmentByTag("bar");

但是,这似乎有点异步。在commit 之后直接调用findFragmentByTag 将返回null。只是后来,在我的情况下,在 OnOptionsItemSelected 事件中,找到了片段。

还有一个叫findFragmentById(int)的函数,但是用处不大。 Id - 如果未在 XML 布局中指定 - 与容器的相同,因此在本例中为 R.id.framelayout_content。如果您稍后使用此 ID 调用该函数,则只能访问附加的 Fragment 之一。 (我猜这是最后一个,但还没有检查。不过,它似乎总是一样的。)

我没有快速一瞥找到从我的 FrameLayout 中获取“活动”片段的方法,所以我想我要将最后显示的片段的标签保存在某个地方。

【讨论】:

“在提交后直接调用 findFragmentByTag 会返回 null”可能是由于: > 调用 commit() 不会立即执行事务。相反,一旦线程能够这样做,它就会安排它在 Activity 的 UI 线程(“主”线程)上运行。”[android 开发人员]developer.android.com/guide/components/…)

以上是关于Android Honeycomb:如何更改 FrameLayout 中的片段,而不重新创建它们?的主要内容,如果未能解决你的问题,请参考以下文章

如何获取 android Honeycomb 系统的屏幕宽度和高度?

Android Honeycomb 中的加载程序

Android HoneyComb DatePicker 文本颜色

创建 Honeycomb 操作栏选项卡后更改选项卡文本

Android:BitmapFactory.nativeDecodeAsset 处的 OutOfMemoryError(Honeycomb 3.0 及更高版本)

ActionBar pre Honeycomb