【问题标题】:Using ActionBar Up Navigation in master/detail fragments在主/详细片段中使用 ActionBar Up Navigation
【发布时间】:2013-01-23 03:36:02
【问题描述】:

我有一个具有主/详细布局的应用程序(1 个活动、1 个 ListView 片段和 1 个详细片段)。当用户单击 ListView 中的项目时,片段事务会在右侧窗格中实例化一个详细片段,其中包括与该项目对应的信息。当显示细节片段时,我隐藏了初始操作栏按钮/项目并显示 3 个新的 AB 项目(完成/删除/取消)。用户可以通过按下后退按钮或按下 3 个 AB 项之一来清理右窗格并返回到初始 UI 状态。

我遇到的问题是,当用户选择应用程序的主页图标(即“向上导航”)时,活动会重新加载(即指示活动开始的动画可以看作是两个动作栏和 UI 被重绘)。该问题仅在按下应用程序主页图标时发生。如果用户按下后退按钮或取消/完成/删除操作栏按钮,片段会简单地从右窗格中移除,并且 UI 会返回初始状态而无需任何“重新加载”。

活动的 XML 布局如下(在 LinearLayout 内;美化隐藏了该行):

<fragment class="*.*.*.ListFragment"
        android:id="@+id/titles" android:layout_weight="1"
        android:layout_width="0px"
        android:layout_height="match_parent" />

<FrameLayout android:id="@+id/details" android:layout_weight="2"
        android:layout_width="0px"
        android:layout_height="match_parent" />

DetailsFragement 在其 onCreate 方法中有 actionBar.setDisplayHomeAsUpEnabled 语句:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);


    ActionBar actionBar = getSherlockActivity().getSupportActionBar();
    actionBar.setDisplayHomeAsUpEnabled(true);

}

对于 ListView 片段和 Detail 片段,onCreateOptionsMenu() 和 onOptionsItemSelected() 方法都在片段中实现。在详细信息片段的代码下方:

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.edit_menu, menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {

    // some variable statements...

    switch (item.getItemId()) {
        case android.R.id.home:
            //Toast.makeText(getSherlockActivity(), "Tapped home", Toast.LENGTH_SHORT).show();
            onHomeSelectedListener.onHomeSelected();
            return true;

        case R.id.menu_edit_item_done:
            editedTask.setTitle(editedTaskTitle);
            onTaskEditedListener.onTaskEdited(editedTask, UPDATE_TASK, true);
            return true;

        default:
            return super.onOptionsItemSelected(item);

    }
}

在主机活动中,我实现了 onHomeSelectedListner 来处理应用主页图标按下(即“向上导航”:

public void onHomeSelected(){

    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction ft = manager.beginTransaction();
    TaskFragment taskFragment = (TaskFragment)getSupportFragmentManager().findFragmentById(R.id.details);
    ft.remove(taskFragment);
    ft.commit();
    manager.popBackStack();

}

负责处理所有其他操作栏按钮(即完成/删除/取消)的活动侦听器是 onTaskEditedListener,除了处理某些数据的其他代码之外,它具有与上面所示相同的片段事务。

更新(1/24) 根据 tyczj 和 straya 的反馈,我在活动的 onCreate()、onResume()、onPause() 中放置了日志语句,以确定 onHomeSelected 和 onTaskEdited 侦听器之间的差异。我能够确认在“向上导航”事件(即 onHomeSelected)期间调用了 onPause()、onCreate() 和 onResume()。而在 onTaskEdited 调用期间(即后退按钮或完成/删除/取消按下)没有调用这些事件。

更新 (1/25) 根据 Mark Murphy 的建议,我在“case android.R.id.home”语句中注释掉了 onHomeSelected 方法调用,只是为了看看 Activity 会做什么。当时的想法是,应用程序不会做任何事情,因为 are no 语句。事实证明并非如此。即使没有调用监听器方法(即移除片段),Activity 也会重新启动,并从片段容器中移除详细片段。

更新 (2/28) 我暂时解决了我的主要活动通过禁用窗口动画重新启动的事实(如我自己的答案中突出显示的那样)。然而,通过进一步的测试,我发现了一个错误。感谢 Wolfram Rittmeyer 的示例代码,我能够找出我的活动在向上导航期间重新启动(在主/详细单一布局中)的真正原因: 1) 尽管我使用此“onHomeSelectedListener”从后台堆栈中正确删除片段,但在 ListView 片段的 onOptionsItemSelected 中仍有一些剩余代码创建了启动托管活动的新意图。这就是为什么按下应用程序的主页图标会重新启动活动。 2)在我的最终实现中(如我自己的答案所示),我摆脱了活动中的 onHomeSelectedListener 并替换了 ListView 的 onOptionsItemSelected 内的 startActivity 意图(即违规代码),以使用最初在 onHomeSelectedListener 中的片段删除 + popBackStack 代码。

【问题讨论】:

  • 可能值得注意的是,Tasks 应用程序使用单个 Activity 并显示/隐藏用于主/详细配置的片段,而不是具有多个 Activity。检查 this G+ post 和 cmets。
  • @curioustechizen 在主/详细配置中,我也使用单个活动。我以前读过那个 G+ 帖子。虽然它没有讨论向上导航的话题(与这个问题有关),但它提醒我们解决“旋转稳定性”的问题,所以感谢分享!

标签: android android-actionbar


【解决方案1】:

经过大量研究和探索,事实证明,我的活动在主/详细配置的“向上导航”期间重新启动的唯一原因是因为我在 ListView Fragment 的 onOptionsItemSelected 中留下了一些代码,该代码创建了启动主程序的意图除了我在其他地方的完整片段事务代码之外的活动。下面是我在手机(多个活动)和平板电脑(单个活动/多窗格)配置上正常工作的“向上导航”的最终实现。感谢 Wolfram Rittmeyer 在他的代码中提供了一些提示(评论部分中的链接)帮助我查明我的问题!

Main Activity:托管片段并执行其他一些特定于应用的操作

ListView Fragment: 处理表格配置中的“向上导航”

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            if(mDualPane){
                FragmentManager manager = getSherlockActivity().getSupportFragmentManager();
                FragmentTransaction ft = manager.beginTransaction();
                DetailFragment detailFragment = (DetailFragment)manager.findFragmentById(R.id.details);
                ft.remove(detailFragment);
                ft.commit();
                manager.popBackStack();
                getSherlockActivity().getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                getSherlockActivity().getSupportActionBar().setHomeButtonEnabled(false);
            }
            return true;

        // Other case statements...

        default:
            return super.onOptionsItemSelected(item);
    }
}

Details Fragment:处理手机配置中的导航

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);

    // Sets "up navigation" for both phone/tablet configurations
    ActionBar actionBar = getSherlockActivity().getSupportActionBar();
    actionBar.setDisplayHomeAsUpEnabled(true);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {

    switch (item.getItemId()) {
        case android.R.id.home:
            if(!mDualPane){
                Intent parentActivityIntent = new Intent(getSherlockActivity(), MainActivity.class);
                parentActivityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(parentActivityIntent);
                getSherlockActivity().finish();
            }
            return true;

        // Other case statements...

        default:
            return super.onOptionsItemSelected(item);
    }

}

【讨论】:

  • 版主,供以后参考,请将引用的stackoverflow答案的链接添加到我的回复中。
  • 对不起,我来晚了。你怎么看这个:gist.github.com/writtmeyer/5052512
  • +1 获取有用信息!有人知道如何一起使用Master detail and navigation drawer 吗?当我们在 xml 中使用带有布局别名的 API 11 的片段时,主细节有效,我正在寻找如何在导航抽屉中实现相同的功能,我们正在以编程方式加载片段!
  • @LOG_TAG 我认为我见过的唯一能做到 Drawer + Master/Detail 的应用是 Play 商店。您可能想尝试使用 Fragment 来托管主/详细布局,并使用 ChildFragment 来托管各个 Master 和 Detail 片段。我自己没有尝试过,但可能值得一试。
  • @cavega 感谢您的信息!让你知道结果!
【解决方案2】:

如果您查看Navigation Design Pattern,您会看到您希望在按下主页按钮时返回到开始活动。

假设您有 2 个活动,分别称为 A1 和 A2。单击 A1 中的某些内容会将您带到 A2。如果用户点击主页按钮,您应该将他们返回到 A1,清除堆栈中的所有内容,直到像这样的活动

Intent intent = new Intent(this, A1.class);  
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

这就是标志Intent.FLAG_ACTIVITY_CLEAR_TOP 所做的事情

如果设置了,并且正在启动的 Activity 已经在当前任务中运行,那么不会启动该 Activity 的新实例,而是关闭它之上的所有其他 Activity,并且此 Intent 将被传递到(现在在顶部)作为新 Intent 的旧活动。

例如,考虑一个由以下活动组成的任务:A、B、C、D。如果 D 调用 startActivity() 并带有解析为活动 B 的组件的 Intent,则 C 和 D 将完成并且 B 接收给定的 Intent,导致堆栈现在是:A,B。`

上面示例中当前运行的活动 B 实例将在其 onNewIntent() 方法中接收您在此处启动的新意图,或者自身完成并使用新意图重新启动。如果它已声明其启动模式为“multiple”(默认)并且您没有在同一意图中设置 FLAG_ACTIVITY_SINGLE_TOP,那么它将完成并重新创建;对于所有其他启动模式,或者如果设置了 FLAG_ACTIVITY_SINGLE_TOP,则此 Intent 将被传递到当前实例的 onNewIntent()。

这种启动模式也可以和FLAG_ACTIVITY_NEW_TASK配合使用效果很好:如果用于启动一个任务的根Activity,它会将当前正在运行的该任务的任何实例带到前台,然后将其清除到它的根状态。这尤其有用,例如,在从通知管理器启动活动时。

【讨论】:

  • 我正在按照您在手机配置中的建议进行操作,其中有 2 个活动。但是,在平板电脑配置中,ListView 和 Details 片段都由同一个活动托管(即 Details 片段是从 ListView 片段中的片段事务启动的)。如果我已经在家庭活动中,为什么还需要意图?按下主页/向上提示按钮应该只是从堆栈中删除片段。我的代码正在正确删除片段,问题是由于我看到的飞入动画,主要且唯一的活动似乎重新启动。
  • 那你为什么不直接弹出 backstack 直到你到达你想去的地方。当您开始片段并使用addToBackStack("fragmentname") 而不是addToBackStack(null) 时,当按下主页按钮时,您使用.popBackStack("name of fragment you wan to go back to", FragmentManager.POP_BACK_STACK_INCLUSIVE)
  • 如果您查看我的 onHomeSelected 方法,我已经在使用 popBackStack。详细信息片段位于活动布局的右侧窗格中(列表视图片段位于左侧窗格中)。当用户完成查看列表中的项目时,右窗格应该是空的,因此没有片段可以导航到。我的代码的唯一问题是“case android.R.id.home:”和 onHomeSelected 处理的“向上导航”显示动画,好像整个活动(包括操作栏和 2 个片段)正在重新启动并且确实与 onTaskEdited 的工作方式不同。
  • 那么我不知道,我可以说最好的代码量是在其中放置一些断点并开始逐行遍历代码。尝试在不删除片段的情况下弹出回栈,看看是否有任何改变
  • 感谢您的建议。我重新编写了问题描述并添加了额外的代码。如果需要更多信息,请告诉我。
【解决方案3】:

不要:break然后返回super.onOptionsItemSelected(item),而只是:return true;

更新:

因此,您是说 Activity 是根据您看到的 View 发生的情况“重新启动”的,但是您能否通过使用各种生命周期中的日志记录来确认 Activity(以及与此相关的 Fragments)可能发生或可能不会发生的情况方法?这样您就可以在进行诊断之前确定当前(错误)行为是什么。

更新:

好的,很高兴确定行为:) 现在关于您的问题“为主/详细布局(1 个活动/2 个片段)实施“向上导航”的正确方法是什么?“: 典型的方式是在单个 FragmentTransaction 中添加 2 个 Fragment,然后您只需 popBackStack 删除它们并返回到之前的状态。我认为您通过手动删除 FragmentTransaction 中的 Fragment 然后弹出回栈来加倍。试试popBackStack。哦,为了确定和一致,因为您使用的是 ActionBarSherlock 和 support.v4,您是在使用 FragmentActivity(而不是 Activity)和 SherlockFragment 吗?

【讨论】:

  • 我将 break 语句更改为“return true”,但即便如此,根据Google's training 的这篇文章,需要使用超级实现作为默认值。即使经过这些修改(原始问题中的代码更新),我仍然会在平板电脑配置中体验飞入动画。请记住,onHomeSelectedListener 和 onTaskEditedListener 都使用相同的片段事务,并且只有“home” switch case 语句会导致活动重启。
  • 我使用了 Log 语句,并且能够确认仅在向上导航期间调用 onPause()、onCreate() 和 onResume(),因此正在重新创建活动。当用户按下后退按钮或任何操作栏项以导航出片段时,不会发生这种情况。
  • 所有主/详细谷歌示例(包括 ADT 模板)在 xml 布局中实例化“listview 片段”。详细信息(右窗格)片段是唯一需要通过 xml 容器中定义的 FrameLayout 进行片段事务的片段。这正是我在我的代码中所做的。我会试试你的建议。但是,我使用相同的片段事务来删除 1) onHomeSelected 和 2) onTaskEdited 中的片段,所以这并不能解释为什么 Activity 只会为 onHomeSelected(向上导航)重新启动。
【解决方案4】:

我认为你应该只在活动内部处理向上按钮。 如果您在手机中,向上按钮将由充当该片段包装器的活动处理,在平板电脑(主/详细模式)中您无论如何都不需要它

【讨论】:

  • 我同意你的观点,但即使是 Google 自己的 Gmail 应用程序(平板电脑配置)也允许用户使用“向上导航”在同一主要活动中的片段之间切换。如果您仔细查看 Gmail 应用程序,您不会看到任何表明“家庭活动”正在重新启动的动画。
  • 这意味着当Gmail应用在State#1:文件夹(左窗格)/电子邮件列表(右窗格)和State#2:电子邮件列表(左窗格)之间来回切换时/选定的电子邮件(右窗格),所有都在同一个活动中完成。所以要么 1)谷歌实际上正在使用 2 个活动(并且做了一个黑客来禁用“活动重新启动”动画或 2)他们正在使用一个活动。
  • 另一个名为“Tasks”的应用程序也能够在主/从配置中使用“向上导航”,而无需重新启动活动。除非像我上面的猜测一样,它们被禁用了指示活动何时重新启动的动画。
  • 好吧,我没有平板电脑,只有模拟器,但我的意思是让将片段包装在手机布局中的活动处理向上按钮,当你在平板电脑中时,这个活动不会存在,因此没有要处理的向上按钮,这在简单的主/详细信息的情况下是正确的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-23
  • 2015-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多