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

Posted

技术标签:

【中文标题】片段设计:通过在单个 Activity 中显示/隐藏片段来适应多种屏幕布局?【英文标题】:Fragment design: Adapting to multiple screen layouts by showing/hiding fragments within a single Activity? 【发布时间】:2012-04-20 13:43:34 【问题描述】:

我正在尝试了解如何使用 Fragments 创建能够很好地适应多种屏幕和布局的应用程序。我研究了几个例子:

    android 开发者指南中的Fragments 文档。 Google IO app 来自ActionBar Sherlock 的片段样本。

所有这些都提倡多重Activity 方法:

在大屏幕上,显示一个Activity和多个Fragments 在较小的屏幕上,将Fragments 拆分为多个Activitys。

我想到了另一种方法——单个Activity一个:

有一个Activity,其中包含所有Fragment。 根据屏幕尺寸和方向,显示/隐藏适当的Fragment(s)(使用FragmentTransaction.show() / FragmentTransaction.hide())。

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

News 活动同时包含ArticleListFragmentArticleReaderFragment。 在选项卡上,始终显示两个片段。 在电话上,ArticleReaderFragment 最初是隐藏的。当从列表中选择一篇文章时,ArticleListFragment 被隐藏,ArticleReaderFragment 被显示。

有人用过类似的方法吗?这种方法可能有任何实际的缺点吗?与多活动方式相比,它看起来更好/更差吗?例如,片段不能在 XML 中显示/隐藏 - 为此必须使用FragmentTransaction


编辑 1:假设场景的描述

想象一个应用程序可以在屏幕上一次显示多达三个“窗格”。此外,这些是需要考虑的因素:

手机一次只能显示一个窗格(无论纵向/横向) 7 英寸平板电脑可以显示 2 个窗格,在纵向模式下垂直拆分,在横向模式下水平拆分。 10 英寸以上的平板电脑可以显示 2 个窗格,纵向拆分; 3 个窗格横向水平拆分。

为简单起见,让我们不要讨论电视屏幕。

现在,将其转化为设计:

我们有三个片段:Frag1、Frag2 和 Frag3。 在最简单的情况下,所有三个片段都在一个 Activity 中(我们称之为 ActivityA)。这是 10 英寸横向外壳。 另一种“简单”的情况是当每个 Fragment 都在自己的 Activity 中时 - ActivityA 包含 Frag1; ActivityB 包含 Frag2,ActivityC 包含 Frag3。

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

现在,7 英寸标签的情况下只能容纳 2 个碎片。这将如何工作?请注意,这里有两种可能的组合:

    Frag1 和 Frag2 正在显示。 Frag2 和 Frag3 正在显示。

我只是无法理解这一点。我是否在 ActivityA 中完成所有这些操作?我只是创建一个全新的 ActivityD 吗?我需要创建多少个布局(我数了大约 8 个)?是不是排列太多了?

我确实意识到我上面提出的单一活动方法也可能不适合这种情况 - 因为显示/隐藏片段本身并不简单。

关于如何在不被布局和组合淹没的情况下处理这个问题有什么建议吗?

【问题讨论】:

我个人会尽可能少地使用活动,因为这个问题完全暴露了活动/片段的 API 混乱。 Android 开发人员没有转换活动并允许同时显示多个活动,而是注入了这些称为 Fragments 的“伪活动”,它们有自己的生命周期“某种绑定”到一个活动(但并非在所有情况下都是如此) )。因此,在我看来,处理更少的活动可以让你最接近想象一个只依赖片段的美好世界。 @MartínMarconcini 这是我最近倾向于的观点:-)。这工作正常,直到您遇到嵌套片段和一些陷阱。 好吧,嵌套片段是在 hack 之上的 hack,但它们“工作”,有点。 :) 我要说的是,片段的加载速度很多比活动快。 【参考方案1】:

This@Taylor Clark 的回答非常有用。但是,这并不是我在原始问题中提出的使用单一活动方法的实际经验分享。我着手修改 Android 开发人员指南中的 News Reader 示例以使用单活动方法,并提出了一个可行的解决方案。

还有待观察的是,在哪些用例中这种方法比多活动方法更可取(或者是否有任何此类情况)。此外,我还没有详细了解我的问题的编辑 1 中描述的 3 窗格方案。

我希望尽快发布我的整个项目,但这里是我如何进行的快速概述:

Activity:NewsActivity 两个片段:TitlesListFragmentDetailsFragment 这两个片段始终存在于NewsActivity 中。根据当前的双窗格,我显示/隐藏适当的片段。

我遇到的一些问题:

是否将布局指定为双窗格:

在原始新闻阅读器示例中,双窗格布局有一个 FrameLayout 用于保存新闻详细信息。我们通过测试这个 Frame Layout 的存在来判断我们当前是否处于双窗格布局中。

但是,在我的解决方案中,两个片段始终存在于所有布局中。我通过在我想要成为双窗格的布局中包含一个 ID 为 dualPaneandroid:visibility="gone"View 来解决这个问题,并在单窗格布局中省略了这个视图。然后,这是一个问题

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

编辑:

有比使用虚拟视图更好的方法来指定双窗格。我更喜欢创建一个布尔资源。比如我有一个config.xml如下:

<resources>
    <bool name="dual_pane">false</bool>
</resources>

然后我可以将额外的config.xml 文件放在values-xlarge-landvalues-portvalues-sw600dp 等文件夹中,并根据需要将布尔值调整为truefalse

那么,在代码中是getResources().getBoolean(R.bool.dual_pane);的问题

关闭详细信息片段

这是区分Activity 关闭和Fragment 关闭的问题。最后,我不得不重写onBackPressed(),如下所示:

在双窗格模式下,只需拨打super.onBackPressed(); 在单窗格模式下,如果我们在TitlesListFragment,请致电super.onBackPressed(); 在单窗格模式下,如果我们在DetailsFragment,则将其视为关闭片段。这意味着隐藏它并显示TitlesListFragment

这并不理想,但这是我能想到的最好的。

编辑

根据 cmets 中 @SherifelKhatib 的建议,有一种更简洁的方式来处理后退按钮按下:只需将显示/隐藏细节片段的事务添加到 Fragment backstack。这样,当您按下后退按钮时,片段事务就会被反转。如果您希望在其他按钮单击时这样做,也可以手动弹出 backstack。

【讨论】:

不能隐藏/显示片段是事务吗?因此,这可能不需要处理onBackPressed()? @SherifelKhatib 从我的脑海中,我找不到你的提议有任何错误:) .. 但我想我应该尝试一下 - 只有这样我们才会知道。 这种方法如何处理 Intent 和 Bundle?如果应用程序在多个活动之间拆分,则很容易以编程方式调用某个列表元素的意图。您的 multiplePaneActivity 是否对这些场景起作用?如果是,那怎么办? @Rekin 很容易将活动之间基于意图的通信“转换”为片段之间基于包的通信。购买 我怀疑这不是你要问的。您能否在某处更详细地描述您的问题的示例代码?【参考方案2】:

通常应用程序被拆分为活动,因为每个活动都代表用户可以执行的特定“事情”。例如,在电子邮件应用程序中,定义的一组操作可以是:

    查看消息列表 查看消息的详细信息 撰写对电子邮件的回复。

每个动作都可以是它自己的 Activity,它显示一个(或一组)片段。但是,如果您拥有屏幕不动产,那么将消息列表的查看和消息详细信息合并到一个可以显示/隐藏“详细视图”片段的单个活动中是有意义的。从本质上讲,片段允许您一次向用户显示多个“活动”。

做出决定时需要考虑的事项:

    无法为 xml 中指定的片段提供参数 如果您的决定是基于屏幕方向,您始终可以使用资源限定符来指向具有更多/更少片段的另一个布局(例如 layout-land-large) 无法维护片段活动 Fragments 是否协同工作以向用户提供简化的体验? 你的碎片是否会永远消失?如果是这样,也许是时候进行新的活动了 凭直觉行事。如果您的应用程序“自然”地换出片段,那就去吧。请记住,如果您发现自己经常这样做,那么单独的 Activity 可能是更合适的解决方案

我通常对平板电脑版本的应用程序使用“多片段方法”,对手机使用“每个活动一个片段”,这听起来与您列出的第一种方法一致。并不是说第二个没有时间和地点,但我可以看到实现很快就会变得混乱!

对不起,冗长的回复!也许您可以详细介绍一下您的具体用例?希望这会有所帮助!

对问题编辑一的回复


我想你的应用程序项目可以这样设置:

源文件:

yourapp.package.phone:NewsActivity1、NewsActivity2、NewsActivity3

yourapp.package.tablet:NewsMultipaneActivity

资源

布局/

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

布局-大/

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

布局-xlarge-land/

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 内,则可以缓解这种情况 这里的关键是您的应用程序如何运行。请记住,活动应该代表用户可以做的单一的、专注的事情。 [意外提交评论] 我倾向于认为多个活动,每个活动最多包含 5 个片段,这将是最具可扩展性的解决方案。在某些时候,一次向用户呈现更多数据的回报会递减。以 Windows 资源管理器为例。您可以打开的每个窗口都可以视为一个片段。是的,您可以一次打开 10 个文件浏览器,但事情很快就会变得混乱和混乱。 我明白你关于收益递减的观点。我仍然看不到如何在不遇到太多组合的情况下以可预测的方式处理多个活动和片段。用假设的 3 窗格方案更新了我的问题。 我已将此答案授予赏金,因为它对整理我对该主题的想法非常有帮助。但是,我还没有“接受”这个答案——希望我们能在中心点上得到更多的答案或想法——单一活动和所有片段的方法。【参考方案3】:

感谢您提出这个问题和您的见解。在遇到问题之前,我一直使用多活动方法。我的应用就像 doc-example 新闻阅读器。考虑这种情况:

    (初始状态)我在横向模式(双窗格)下使用平板电脑,活动 A 在左侧显示文章列表,在右侧显示一些文章。 我旋转设备,现在活动 A 处于单窗格模式并显示文章列表。 我单击一个列表项,活动 B 以打开的文章开始。 现在我将设备旋转回横向模式。

发生了什么事?活动 B 正在显示一篇全尺寸的文章,而我当然想回到两个窗格。我不确定如何很好地解决这个问题。当然,活动 B 可能会检查多窗格模式并自行完成,活动 A 可能会检查最后显示的文章是什么……这看起来很难看。想法?

PS 我怀疑 GMail 应用程序使用单一活动方法。当我从列表中选择一封电子邮件时,我没有看到任何活动转换。它在我描述的场景中也表现正常。

【讨论】:

您的问题是谷歌工程师所说的“旋转稳定性”。我的看法 - ActivityB 是一个仅纵向模式的 Activity。它不应该在双窗格配置中显示。所以基本上你描述的解决方案就是要走的路。它当然不丑。 嗯,您在哪里看到“旋转稳定性”这个词?我可以通过“从不显示活动”来理解您的观点,但问题是 Android 平台不允许我以漂亮的方式执行该规则 - 当我在活动 B 中切换到横向模式时,活动只是重新创建。任何解决方案这个案子对我来说就像一个黑客攻击。 我记得它被用于 Google IO 演讲或 Android Design in Action(不太记得是哪一个)。我同意活动 B 的“如果不再需要就完成”行为可能看起来像一个 hack。但这就是 Google 提供的一些示例的工作方式。【参考方案4】:

在这篇文章中http://developer.android.com/guide/practices/tablets-and-handsets.html#FragmentsGoogle 说:

“您选择的方法取决于您的设计和个人 偏好。”

“但是,其他时候,为您的手机动态交换片段 设计可以使你的代码更复杂,因为你必须管理 活动代码中的所有片段组合(而不是使用 用于定义片段组合的替代布局资源)和 自己管理碎片的后台堆栈(而不是允许 正常的活动堆栈来处理后退导航)。”

【讨论】:

以上是关于片段设计:通过在单个 Activity 中显示/隐藏片段来适应多种屏幕布局?的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式通过 Main Activity 在 ViewPager 中更新刷新片段

具有相同功能的活动和片段

如何在按下单个片段的手动后退按钮时返回上一个片段?

(导航组件)返回首页fragment时如何在activity上显示返回箭头?

在片段转换期间隐藏 BottomNavigationView

从片段显示对话框?