将方向从单窗格更改为双窗格碎片布局时出现问题

时间:2016-09-29 18:16:16

标签: android android-layout android-fragments

我有一个包含多个片段的活动。在平板电脑(720dp或更宽)上,我会在左侧的片段中显示一个列表,然后在右侧的窗格中显示多个片段中的一个。当用户从列表中选择不同的项目时,右窗格中的片段将更改。在单窗格模式下,用户将看到碎片列表,点击一个,然后列表片段将被替换为细节片段。

这是一个棘手的部分:采用7英寸平板电脑,因此景观足够宽,适合两个窗格,但肖像足够宽,只有一个。在单窗格模式和双窗格模式之间旋转。有些事情,比如动作栏图标和标题,需要更新,但这并不太复杂。

问题出现在片段状态中。片段需要被处理:如果用户在单窗格模式下查看Detail Fragment B,然后旋转到双窗格模式,他们应该看到左边的片段Fragment和右边的片段B.

移动碎片是非常糟糕的,但后堆是真正的问题。假设我处于双窗格模式并且选项卡片段为C片段。列表片段显示在左侧,细节片段C在右侧。然后我旋转到单窗格模式。现在我应该看到碎片C的细节,但是如果按下后退按钮我应该回到列表片段。另一方面,如果我以单窗格模式启动并构建后台堆栈,然后切换到双窗格模式,则应该没有后台堆栈。

这似乎是任何人在尝试使用片段和/或平板电脑友好时会遇到的常见情况。他们将不得不面对在单窗格和双窗格方向之间切换时处理碎片并操纵后台堆栈的挑战。然而到目前为止,我还没有找到答案或这方面的例子,只有很多关于“如何在平板电脑上并排显示两个片段”等基础知识的问题。

有人处理过这种情况吗?当用户从Fragment转移到Fragment并来回旋转时,您为用户提供一致体验的解决方案是什么?

2 个答案:

答案 0 :(得分:1)

这就是我所做的:在纵向模式下,我为列表视图和详细视图创建了相同的容器视图,但列表视图的宽度为0dp。这样,当用户处于横向列表+细节模式时,如果他们向前导航,更改方向并按下后退按钮,列表和细节片段都将正确加载,但现在在纵向模式下,您只能看到详细视图。

我没有变得非常复杂。如果您以纵向模式导航,将列表转到详细信息,然后旋转到横向,我会显示列表和详细信息,但按下后退按钮将再次显示列表。我的理念是恢复背部堆栈,就冗余或缺失的窗格而言,让筹码落到他们可能的位置。

但我认为你可以做得更好。让我们来看看每个案例并通过它。

  

用例1:用户处于横向模式。用户导航到列表+详细信息窗格,然后选择详细信息。如果用户旋转为纵向,则应仅查看详细信息窗格。如果他们现在按下,而不是列表+详细信息之前的UI,他们应该在导航回到他们开始的位置之前看到列表。

     

用例2:用户处于纵向模式。用户导航到列表屏幕,然后导航到详细屏幕。当用户旋转到横向模式时,他们应该看到list + detail。当他们按下时,他们应该跳过后面的堆栈列表屏幕。

我花了一些时间在我的平板电脑上使用GMail应用程序,它似乎做得很好。

让我们从布局XML开始。纵向和横向版本都将具有两个窗格,但在纵向中您只看到一个:

layout/list_detail.xml

    <LinearLayout
        android:width="match_parent"
        android:height="match_parent"
        orientation="horizontal">

        <FrameLayout
            android:id="+id/list_pane"
            android:width="match_parent"
            android:height="match_parent"/>

        <FrameLayout
            android:id="+id/detail_pane"
            android:width="match_parent"
            android:height="match_parent"
            android:visibility="gone"/>

    </LinearLayout>

layout-landscape/list_detail.xml

    <LinearLayout
        android:width="match_parent"
        android:height="match_parent"
        orientation="horizontal">

        <FrameLayout
            android:id="+id/list_pane"
            android:width="240dp"
            android:height="match_parent"/>

        <FrameLayout
            android:id="+id/detail_pane"
            android:width="0dp"
            android:weight="1"
            android:height="match_parent"/>

    </LinearLayout>

这个想法是两个窗格中的片段将始终被加载,但在纵向模式下,您只能看到一个或另一个(通过切换哪个窗格可见以及哪个窗格已消失),而在横向模式下,您可以看到两者。

我将假设在横向模式下,您最初使用列表中的第一个项目加载详细信息窗格。所以你总是有一个列表和一个细节,只是在纵向你现在没有看到细节。

首先,我们想知道我们的UI是处于列表模式还是详细模式,特别是当我们从横向到纵向时:

        private boolean mListMode;

onCreate()中,我们会对此进行初始化:

        mListMode = true;

onCreateView()中,我们想要恢复保存的模式状态(假设这里有一个片段):

        if (savedInstanceState != null) {
            mListMode = savedInstanceState.getBoolean("listMode");
        }

        if (getResources().getBoolean(R.bool.is_portrait)) {
            mListPane.setVisibility(mListMode ? View.VISIBLE : View.GONE);
            mDetailPane.setVisibility(mListMode ? View.GONE : View.VISIBLE);
        }

我们保存在

     @Override
     public void onSaveInstanceState(Bundle outState) {
         outState.putBoolean("listMode", mListMode);
         super.onSaveInstanceState(outState);
     }

现在列表项单击逻辑将如下所示:

        if (mListMode) {
            if (getResources().getBoolean(R.bool.is_portrait)) {
                // TODO animate list -> detail
                mDetailPane.setVisibility(View.VISIBLE);
                mListPane.setVisibility(View.GONE);
            }
            mListMode = false;  // a click puts UI in detail mode, even in landscape
        }

您需要确保设置mListMode = false,以防用户点击详细信息窗格中横向导航的内容,因此如果他们旋转为纵向并回击,他们会看到细节窗格。

好的,现在你需要一个技巧来处理肖像的后退按钮。您可以在UI中执行类似的操作(让我们假设它是一个片段):

    public boolean onBackPressed() {

        if (getResources().getBoolean(R.bool.is_portrait) && ! mListMode) {
            // TODO animate detail -> list
            mDetailPane.setVisibility(View.GONE);
            mListPane.setVisibility(View.VISIBLE);
            mListMode = true;
            return true;   // we handled back button ourselves
        }

        return false;  // we didn't handle back button
    }

然后在你的活动中:

    @Override
    public void onBackPressed() {

        ListDetailFragment listDetailFragment = (ListDetailFragment ) getFragmentManager().findFragmentByTag(LIST_DETAIL_TAG);

        if (listDetailFragment != null && listDetailFragment.isResumed() && listDetailFragment.onBackPressed()) {
             return;
         }

         super.onBackPressed();
    }

我会将过渡动画作为练习留给读者。

答案 1 :(得分:0)

@kris larson,我接受你的回答,奖励你所有的辛勤工作和详细的解释!自从我向前推进并试图自己找出一些东西后,我想我会简明扼要地分享一些我尝试过的东西,以便其他人可以从我的头痛中受益。我当然不会说这是最好的方式!

我制作了两个Fragment布局文件。一个包含一个ID为fragment_container_master的FrameLayout和一个ID为fragment_container_detail的FrameLayout。另一个只有fragment_container_detail FrameLayout。我将双窗格放在layout-w720dp中,将单窗格放在布局中。

我注意到Android文档听起来像-sw(&#34;最小宽度&#34;)修饰符优于-w(&#34; width&#34;)。 -sw将取较小,宽度或高度。我并不认为这与这个用例有关:在宽屏幕上,我想要两个窗格,就像那样简单。但也许有时候使用-sw是个好理由。

使用FragmentTransactions,我会将片段列表添加到fragment_container_master,将默认细节Fragment添加到fragment_container_detail。当用户点击列表中的不同片段时,我会在片段中放置片段。在fragment_container_detail中。很容易。

我会使用fragment_container_master的存在与否来知道我是在单窗格还是双窗格模式下操作。如果是单窗格,我确保将Fragment过渡添加到后台堆栈,并确保最初在fragment_container_detail中加载片段列表。

正如我所指出的,在单窗格和双窗格之间切换(例如在旋转7&#34;平板电脑时)是棘手的部分。我必须确保更新应用栏以防止按钮被添加两次。

首先,我需要检测是否在这两种模式之间切换。我可以在onCreate中做到这一点 1)检测我是在单窗格还是双窗格模式下操作 2)检测是否存在具有fragment_container_master ID的片段 如果我处于单窗格模式,并且有一个主片段,则它必须从双窗格模式中保留。 如果我处于双窗格模式,并且没有主片段,则它必须尚未初始化,因为我刚刚从单窗格模式切换。

如果我刚切换到双窗格模式,我需要将主片段加载到fragment_container_master中。 如果详细信息片段是列表片段,我将其删除并将其添加到fragment_container_master。 如果没有,我们已经展示了一个细节片段。你认为我可以把它放在那里......但是后面的堆栈有问题。所以我保存Fragment状态,弹出它并用保存的状态重新创建它。然后我有一个双窗格UI,后面的堆栈上没有任何东西,正如我想要的那样。

如果我刚切换到单窗格模式,我需要决定在详细视图中显示什么。如果具有ID fragment_container_detail的片段是默认片段,我可以在那里显示片段片段(替换现有片段)。 如果它不是默认的片段,那么我仍然用列表片段替换它,但是然后将列表片段替换为之前的细节片段 - 确保将其添加到后栈。

听起来很复杂,对吧?

情况变得更糟。

假设用户以双窗格模式拉起屏幕。用户与默认细节片段交互,可能是滚动它或将数据输入到字段中。然后屏幕更改为单窗格模式。用户显然希望看到他们正在使用的细节片段。 另一方面,假设用户打开屏幕,然后在执行任何其他操作之前,更改为单窗格模式。他们不想看到默认细节片段。他们希望看到列出所有其他片段的主片段。他们会因为已经走过他们没有选择的道路而感到非常困惑。 试试Gmail应用,您就会看到它的工作原理。在双窗格模式下,切换到单窗格模式(旋转平板电脑),您将看到收件箱。但是,如果在双窗格模式下滚动第一封电子邮件或以某种方式与其进行交互,然后切换到单窗格模式,您将看到该电子邮件。 我无法找到任何聪明,简单的方法来做到这一点。我很尴尬地说出来,但是当Fragment与之交互时,我在默认的片段报告中有一堆不同的听众。因此,我必须针对每种特定情况进行编码:如果用户单击此按钮或触摸此ListView或执行此其他操作,则报告用户与此片段进行了交互。

我很抱歉没有详细说明或创建代码示例,只是想在忘记之前忘记以备将来参考。