片段设计:通过在单个活动中显示/隐藏片段来适应多个屏幕布局?

38
我正在尝试理解如何使用Fragment创建适应多个屏幕和布局的应用程序。我研究了一些示例:
  1. Android开发者指南上的Fragments文档。
  2. Google IO app
  3. ActionBar Sherlock的Fragments示例。
所有这些都提倡采用多个Activity的方法:
  • 在大屏幕上,显示一个带有多个Fragment的Activity
  • 在较小的屏幕上,将Fragment分配到多个Activity中。
我想到了另一种方法 - 仅使用单个Activity:
  • 将所有的Fragment放入单个Activity中。
  • 根据屏幕大小和方向,使用FragmentTransaction.show() / FragmentTransaction.hide()显示/隐藏相应的Fragment

以Android开发者指南使用的相同的“新闻文章列表/文章内容”示例为例:

  • 创建包含ArticleListFragmentArticleReaderFragment两个Fragment的News活动。
  • 在标签上,这两个Fragment始终显示。
  • 在手机上,ArticleReaderFragment最初是隐藏的。当从列表中选择文章时,ArticleListFragment被隐藏,ArticleReaderFragment被显示。

有人使用过类似的方法吗?这种方法可能有什么实际的缺点?与多个Activity的方式相比,它看起来更好还是更差?例如,无法在XML中显示/隐藏片段-必须使用FragmentTransaction


编辑1:假设情景的描述

想象一个应用程序,可以在屏幕上同时显示最多三个“窗格”。此外,需要考虑以下因素:

  • 手机一次只能显示一个窗格(无论是纵向还是横向)
  • 7英寸的平板电脑可以在纵向模式下分割成两个竖向窗格,在横向模式下分割成两个横向窗格。
  • 10英寸以上的平板电脑可以在纵向模式下分割成两个竖向窗格,在横向模式下分割成三个横向窗格。

为了简单起见,让我们将电视屏幕排除在讨论之外。

现在,将这个转化为设计:

  • 我们有三个片段:Frag1、Frag2和Frag3。
  • 在最简单的情况下,所有三个片段都在一个活动中(称为ActivityA)。这是10英寸的横向情况。
  • 另一个“简单”的情况是每个片段都在自己的活动中——ActivityA包含Frag1;ActivityB包含Frag2,ActivityC包含Frag3。

到目前为止,我们还没有考虑与Android开发人员指南中提供的新闻阅读器示例有显著不同的任何内容。唯一的主要区别是有三个片段而不是两个。

现在,考虑只能容纳2个片段的7英寸标签的情况。这会怎样工作?请注意,这里有两种组合可能:
1.显示Frag1和Frag2。 2.显示Frag2和Frag3。
我只是无法理解这个问题。我应该在ActivityA内完成所有操作吗?还是创建一个全新的ActivityD?我需要创建多少个布局(我数了大约8个)?这不是太多的排列组合了吗?
我确实意识到,我上面提出的单活动方法可能也不适合这种情况——因为显示/隐藏片段本身就不容易。
有没有建议如何处理这个问题,而不会被过多的布局和组合所压倒?

我个人会尽可能少地使用Activity,因为这个问题恰好暴露了Activities/Fragments API的混乱。Android开发者不是将活动转换并允许同时显示多个活动,而是注入了这些名为“片段”的“伪活动”,它们有自己的生命周期“与一个活动有点绑定”(但在所有情况下都不是真的)。因此,处理较少的活动可以让您更接近想象一个美好世界,在那里您只依赖于Fragment(s)。 - Martin Marconcini
@MartínMarconcini 这是我最近倾向的观点 :-). 这很好用,直到你遇到嵌套片段和一些陷阱。 - curioustechizen
嗯,嵌套片段是在一个hack的基础上又进行了一次hack,但它们“工作”,有点。 :) - Martin Marconcini
我要说的一件事是,片段比活动加载得快得多。 - theblang
4个回答

16

这个由@Taylor Clark提供的回答非常详细。然而,它并不是使用单 Activity 方法的实际经验分享,这也是我在原问题中所询问的。我试图修改 Android 开发者指南中的新闻阅读器示例,以使用单 Activity 方法,并得出了一个可行的解决方案。

有待观察的是,什么情况下单 Activity 方法比多 Activity 方法更可取(或者是否存在这样的情况)。此外,我还没有详细了解 Edit 1 中提到的 3 面板场景。

我希望不久后能够发布我的整个项目,但是这里是我完成它的简要概述:

  • Activity:NewsActivity
  • 两个片段:TitlesListFragmentDetailsFragment
  • 两个片段始终都存在于NewsActivity中。根据当前的双面板状态,我会显示/隐藏适当的片段。

我遇到的一些问题:

将布局指定为双面板或非双面板:

在原始的 News Reader 示例中,双面板布局具有用于容纳新闻详细信息的FrameLayout。通过测试该框架布局是否存在,我们可以确定当前是否处于双面板布局中。

然而,在我的解决方案中,所有布局都始终包含两个片段。我通过在我想要成为双面板布局的布局中包含一个具有 id 为 dualPaneandroid:visibility="gone"View,并在单面板布局中省略此视图来实现这一点。然后,只需要使用:

mDualPane = findViewById(R.id.dualPane)!=null;

编辑:

指定双面板状态的更好方法是创建一个布尔资源。例如,我有一个如下的config.xml文件:

<resources>
    <bool name="dual_pane">false</bool>
</resources>
我可以在像values-xlarge-landvalues-portvalues-sw600dp等文件夹中放置其他的config.xml文件,并根据需要将布尔值调整为truefalse
然后,在代码中只需要使用getResources().getBoolean(R.bool.dual_pane);即可。
关闭详细信息片段:
这是区分Activity关闭和Fragment关闭的问题。最终,我不得不重写onBackPressed()如下:
  • 在双面板模式下,只需调用super.onBackPressed();
  • 在单面板模式下,如果我们在 TitlesListFragment 中,则调用 super.onBackPressed();
  • 在单面板模式下,如果我们在 DetailsFragment 中,则将其视为关闭片段。这意味着隐藏它并显示TitlesListFragment
这不是理想的方式,但这是我能想到的最好的方式。
编辑:
基于@SherifelKhatib在评论中的建议,有一种更简洁的处理返回按钮按下的方法:在片段回退堆栈中添加显示/隐藏详细信息片段的事务。这样,当您按下返回按钮时,片段事务就会被反转。您也可以在其他按钮点击时手动弹出回退堆栈。

1
隐藏/显示片段不能成为事务吗?这样,就可以排除处理 onBackPressed() 的需要了吧? - Sherif elKhatib
@SherifelKhatib 从我的角度来看,我找不到你的提议有任何问题 :) ..但我想我应该尝试一下-只有这样我们才会知道。 - curioustechizen
这种方法如何处理意图和捆绑包?如果应用程序在多个活动之间分割,那么编程调用某个列表元素的意图就很容易。你的multiplePaneActivity是否适用于这些情况?如果是,那么是如何实现的? - Rekin
@Rekin,将基于意图的活动通信转换为基于包的片段通信很容易。但我猜这不是你所问的。你能否在某个地方放置一些示例代码,更详细地描述你的问题? - curioustechizen

10

一般而言,应用程序被分成活动(Activity),因为每个活动都代表了用户可以执行的特定“事情”。例如,在电子邮件应用程序中,一个定义好的操作集包括:

  1. 查看消息列表
  2. 查看消息详情
  3. 回复邮件。

每个操作都可以是自己的活动(Activity),并显示单个(或一组)片段(Fragment)。但是,如果你有足够的屏幕空间,将消息列表的查看和消息详细信息合并到单个活动(Activity)中,这将是有意义的,该活动(Activity)可以显示/隐藏“详细视图”片段(Fragment)。 实质上,片段允许您同时向用户显示多个“活动”。

在做出决策时需要考虑以下问题:

  1. 在xml中指定的片段(Fragment)不能使用参数。
  2. 如果你的决策基于屏幕方向,你可以始终使用资源限定符(resource qualifiers)来指向另一个具有更多/更少片段(Fragment)的布局(ex. layout-land-large)
  3. 片段(Fragment)不能维护活动(Activity)
  4. 片段(Fragment)是否协同工作,为用户提供流畅的体验?
  5. 是否有某个时刻,其中一个片段(Fragment)将永久消失?如果是,也许到了使用新活动(Activity)的时候了。
  6. 根据直觉来决定。如果你的应用程序自然而然地切换片段(Fragment),那就去做吧。只要记住,如果你发现自己经常这样做,也许单独的活动(Activity)是更合适的解决方案。

我通常在平板电脑版本的应用程序中采用“多个片段(Fragment)方法”,在手机上则采用“每个活动(Activity)一个片段(Fragment)”的方法,这听起来符合你列出的第一种方法。虽然第二种方法也有其时间和场合,但我可以看出实现起来可能会很混乱!

抱歉回复有点冗长!也许你可以更多地告诉我关于你特定用例的信息?希望这可以帮助你!


对问题编辑一的回复


这是我想象中你的应用程序项目设置方式:

源文件:

yourapp.package.phone: NewsActivity1, NewsActivity2, NewsActivity3

yourapp.package.tablet: NewsMultipaneActivity

资源

layout/

activity_news.xml- phone version, only includes Fragment1
activity_news_detail.xml- phone version, only includes Fragment2
activity_news_<something>.xml- phone version, only includes Fragment3

layout-large/

activity_news.xml- 7" tablet version, includes Fragment2 and an empty fragment container. Split vertically

大尺寸横屏布局/

activity_news.xml- same as layout-large, but with the split being horizontally

大屏横向布局/

activity_news.xml- 10"+ tablet version, contains all three fragments split horizontally

那么这里会发生什么?

  • 如果你的应用正在手机上运行,则启动NewsActivity1
  • 如果您的应用程序在平板电脑上运行,则启动NewsMultipaneActivity

只要文件名相同,Android就会根据屏幕大小和方向为您交换布局。因为Fragment2始终会显示,所以您可以在平板电脑布局中将其“硬编码”。然后,您可以使用FragmentTransactions在容器中根据需要在Fragment1和Fragment3之间切换。

一定要查看http://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources,了解如何利用资源限定符来缓解不同屏幕和方向的一些问题。


这个问题背后的初衷是 - “单独活动”方法有多“可扩展性”?想象一个文件浏览器应用程序。在手机上,我一次显示一个文件夹的内容。在横向7英寸平板电脑上,我可以显示两个窗格。在10英寸的平板电脑上:3个窗格。那么电视屏幕呢?可能会并排钻取5-6个窗格吧?我意识到我的单活动方法对解决这个可扩展性问题几乎没有帮助。但是,在切换活动时保存活动状态会带来麻烦。如果所有操作都在单个“Activity”中完成,则可以缓解这种情况。 - curioustechizen
关键在于你的应用程序如何运作。请记住,活动(Activities)应该代表用户可以执行的单个、专注的事情。 - Taylor Clark
我倾向于认为,每个活动最多有5个片段的多个活动可能是最可扩展的解决方案。在某些时候,一次向用户呈现更多数据会出现收益递减的情况。以Windows资源管理器为例。您可以将每个打开的窗口视为一个片段。是的,您可以同时打开10个文件浏览器,但很快就会变得混乱和令人困惑。 - Taylor Clark
我理解你所说的收益递减的观点。但我仍然不知道如何在处理多个活动和片段时避免遇到过多的组合,以便能够可预测地工作。我已经更新了我的问题,并提供了一个假设的三窗格场景。 - curioustechizen
我已经授予了这个答案的悬赏,因为它在整理我的思路方面非常有帮助。然而,我并没有“接受”这个答案 - 希望我们能得到更多关于单活动与所有片段方法的回答或想法。 - curioustechizen

1
在这篇文章中http://developer.android.com/guide/practices/tablets-and-handsets.html#Fragments,Google说:
“你选择的方法取决于你的设计和个人偏好。”
并且
“然而,在某些情况下,为了适应手持设备的设计动态交换片段可能会使你的代码更加复杂,因为你必须在活动的代码中管理所有片段组合(而不是使用替代布局资源来定义片段组合),并且自己管理片段的返回栈(而不是允许常规活动堆栈处理后退导航)。”

1
感谢您的提问和见解。在我遇到问题之前,我使用了多活动方法。我的应用程序类似于文档示例新闻阅读器。考虑以下情况:
1.(初始状态)我在横向模式下使用平板电脑(双窗格),活动A在左侧显示文章列表,右侧显示一些文章。 2. 我旋转设备,现在活动A处于单窗格模式,并显示文章列表。 3. 我点击列表项,活动B启动并打开文章。 4. 现在我将设备旋转回横向模式。
发生了什么?活动B显示全尺寸的文章,而我当然想回到双窗格模式。我不确定如何优雅地解决这个问题。当然,活动B可以检查多窗格模式并完成自身,活动A可以检查上次显示的文章是什么......那看起来很丑陋。你有什么想法吗?
PS 我怀疑GMail应用程序使用单活动方法。当我从列表中选择电子邮件时,我没有看到任何活动转换。它在我描述的情况下也表现得很好。

你的问题是谷歌工程师所称的“旋转稳定性”。在我看来,ActivityB只能以肖像模式显示,不应在双面板配置中显示。因此,你描述的解决方案基本上就是正确的。它绝对不会丑陋。 - curioustechizen
哦,你在哪里看到“旋转稳定性”这个术语的? 我能理解你关于“从未显示活动”的观点,但问题是Android平台不允许我以美观的方式强制执行该规则 - 当我在B活动中切换到横向模式时,活动会被重新创建。在这种情况下,任何解决方案对我来说都像是一个hack。 - smok
我记得它在Google IO演讲或Android Design in Action中被使用过(不太记得是哪一个)。我同意Activity B的“如果不再需要则自行完成”的行为可能看起来像是一种hack。但这正是谷歌提供的一些示例的工作方式。 - curioustechizen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接