导航到Android中的另一个片段后如何清除导航堆栈


121

我在android中使用新的Navigation Architecture组件,并且在移至新片段后仍无法清除导航堆栈。

示例:我在loginFragment中,并且当我导航到home片段时,我希望从堆栈中清除此片段,以便用户在按“后退”按钮时不会返回到loginFragment。

我正在使用一个简单的NavHostFragment.findNavController(Fragment).navigate(R.id.homeFragment)进行导航。

当前代码:

mAuth.signInWithCredential(credential)
            .addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment);
                    } else {
                        Log.w(TAG, "signInWithCredential:failure", task.getException());
                    }
                }
            });

我尝试使用NavOptions导航() ,但后退按钮仍然送我回loginFragment

NavOptions.Builder navBuilder = new NavOptions.Builder();
NavOptions navOptions = navBuilder.setPopUpTo(R.id.homeFragment, false).build();   
             NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment, null, navOptions);

您可以使用popBackStack或不添加LoginFragment到堆栈中提供nulladdToBackStack(null);和新的取代它Fragment
雨披

请编辑您的信息,并添加您现在用于导航的代码
谷仓

我编辑我的问题,并加入我的代码
优素福厄尔尼诺Behi

我认为@Yupi提供了很好的建议。或者,您可以使用navigate()类似的方法,navigate(int resId, Bundle args, NavOptions navOptions)并提供NavOptions最适合您的Senario的方法
Barns,

我尝试使用NavOptions,但后退按钮仍将我发送回loginFragment
Youssef El Behi

Answers:


179

首先,添加属性app:popUpTo='your_nav_graph_id'并添加app:popUpToInclusive="true"到操作标签。

<fragment
    android:id="@+id/signInFragment"
    android:name="com.glee.incog2.android.fragment.SignInFragment"
    android:label="fragment_sign_in"
    tools:layout="@layout/fragment_sign_in" >
    <action
        android:id="@+id/action_signInFragment_to_usersFragment"
        app:destination="@id/usersFragment"
        app:launchSingleTop="true"
        app:popUpTo="@+id/main_nav_graph"
        app:popUpToInclusive="true" />
</fragment>

其次,使用上述操作作为参数导航到目的地。

findNavController(fragment).navigate(
     SignInFragmentDirections.actionSignInFragmentToUserNameFragment())

有关更多信息,请参阅文档

注意:如果使用方法导航navigate(@IdRes int resId),则不会获得所需的结果。因此,我使用method navigate(@NonNull NavDirections directions)


1
最好的答案是clearTask现在在新版本的Android Navigation Component中已弃用!
米哈尔Ziobro

14
人们还可以像这样使用动作的IDfindNavController(fragment).navigate(R.id.action_signInFragment_to_usersFragment)
Calin

1
太棒了!但是,现在有了后备/字形,但是按它并没有任何作用。删除它的一种干净方法是什么?
丹麦汗

6
我认为此答案的关键在于,将“ popUpTo”(删除当前片段与传递给此参数的id的第一个匹配项之间的所有片段)设置为导航图本身id。有效地完全清除了堆栈。仅添加此评论是因为我没有看到它完全像这样解释,而这正是我想要的。+1!
Scott O'Toole,

7
注意:如果您不包括safe-args gradle,则不会生成“ Directions”类。有关更多信息,请访问这里
Jaydip Kalkani

28

在我的情况下,我需要在打开新片段之前先移除堆栈中的所有内容,然后才使用此代码

navController.popBackStack(R.id.fragment_apps, true);
navController.navigate(R.id.fragment_company);

第一行删除后堆栈,直到到达我所指定的片段为止,这是家庭片段,因此它将完全删除所有后堆栈,当用户在fragment_company中单击时,他关闭了该应用程序。


1
什么是fragment_apps??
IgorGanapolsky

1
当前片段。就像您是否在fragmentOne上并想进入fragmentTwo一样,您也需要使用navController.popBackStack(R.id.fragmentOne,true); navController.navigate(R.id.fragmentTwo);
贝贝尼

26

我认为您的问题特别涉及如何使用“流行为” /“流行为” / app:popUpTo(在xml中)

在文档中,
导航之前弹出到给定的目的地。这将从后堆栈中弹出所有不匹配的目标,直到找到该目标为止。

示例(简单的求职应用)
我的start_screen_nav图是这样的:

startScreenFragment (start) -> loginFragment -> EmployerMainFragment

                            -> loginFragment -> JobSeekerMainFragment

如果我想导航EmployerMainFragment并弹出所有包含 startScreenFragment的代码,则代码将为:

        <action
            android:id="@+id/action_loginFragment_to_employerMainFragment"
            app:destination="@id/employerMainFragment"

            app:popUpTo="@+id/startScreenFragment"
            app:popUpToInclusive="true" />

如果我要导航到EmployerMainFragment并弹出所有排除项, startScreenFragment则代码为:

        <action
            android:id="@+id/action_loginFragment_to_employerMainFragment"
            app:destination="@id/employerMainFragment"

            app:popUpTo="@+id/startScreenFragment"/>

如果我想导航到EmployerMainFragment并弹出loginFragment,但不是 startScreenFragment那么代码将是:

        <action
            android:id="@+id/action_loginFragment_to_employerMainFragment"
            app:destination="@id/employerMainFragment"

            app:popUpTo="@+id/loginFragment"
            app:popUpToInclusive="true"/>

要么

        <action
            android:id="@+id/action_loginFragment_to_employerMainFragment"
            app:destination="@id/employerMainFragment"

            app:popUpTo="@+id/startScreenFragment"/>

如何以编程方式而不是XML方式做到这一点?
IgorGanapolsky

14

注意:不建议使用清除任务,正式说明为

不建议使用此方法。将setPopUpTo(int,boolean)与NavController的图的ID一起使用,并将inclusive设置为true。

旧答案

如果您不想遍历代码中的所有毛刺,则只需检Clear Task入操作Launch Options的属性即可。

启动选项

编辑:从Android Studio 3.2 Beta 5开始,“清除任务”在“启动选项”窗口中不再可见,但是您仍然可以通过添加以下标签在导航的XML代码,操作标签中使用它:

app:clearTask="true"

您应该使用图的根的id来代替clearTask xml attributedeprecation消息说:“将popUpTo设置为图的根并使用popUpToInclusive”。我已经做到了这一点,并使用findNavController()。navigate(@IdRes resId,bundle)实现了相同的预期行为
artnest

使用建议的方法设置popUpTo和popUpToInclusive似乎不适用于除起始目标之外的
hsson

我实际上遇到了一个问题,该问题不适用于启动目标,令人沮丧。
梅尔(Mel)

如果您不知道要弹出的片段,该怎么办?例如,我有一个创建新项目的片段。创建后,我想显示该项目的详细信息视图。但是我不希望用户随后弹出到创建片段,而是弹出到先前的视图。这可以是所有项目的列表,也可以是新项目所基于的其他项目。那么clearTask是唯一的选择吗?
findusl

不,实际上不应该再使用它。相反,您应该使用popUpTo(包括)。我将很快使用popUpTo用法的发现来更新答案。
梅尔(Mel)

5

我终于弄清楚了,这要归功于如何通过新的导航体系结构组件对某些片段禁用导航中的UP?

我必须将.setClearTask(true)指定为NavOption。

mAuth.signInWithCredential(credential)
            .addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        Log.d(TAG, "signInWithCredential:success");


                        NavOptions.Builder navBuilder = new NavOptions.Builder();
                        NavOptions navOptions = navBuilder.setClearTask(true).build();
                        NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment,null,navOptions);
                    } else {
                        Log.w(TAG, "signInWithCredential:failure", task.getException());

                    }

                }
            });

2
很高兴看到您能够实现我使用navigate(int resId, Bundle args, NavOptions navOptions)and的建议NavOptions
Barns

是。感谢@Barns
Youssef El Behi 18'26

14
yeah“ setClearTask”已过时,但您可以使用: val navOptions = NavOptions.Builder().setPopUpTo(R.id.my_current_frag, true).build() findNavController().navigate(R.id.my_next_frag, null, navOptions)
Pablo Reyes

1
@ c-an应该可以。可能您使用了错误的目标fragmentId(例如,R.id.my_next_frag可能类似于登录或启动屏幕);如果只想弹出一个,也可以使用:NavHostFragment.findNavController(this).popBackStack()
Pablo Reyes

2
@PabloReyes这是正确的答案。只需指定最后删除的片段即可:假设您具有以下堆栈:Fragment_1 Fragment_2 Fragment_3您现在位于Fragment_3上,并希望导航至Fragment_4并清除所有其他片段。只需在导航xml中指定:app:popUpTo =“ @ id / Fragment_1” app:popUpToInclusive =“ true”
user3193413

5

这是我的完成方式。

 //here the R.id refer to the fragment one wants to pop back once pressed back from the newly  navigated fragment
 val navOption = NavOptions.Builder().setPopUpTo(R.id.startScorecardFragment, false).build()

//now how to navigate to new fragment
Navigation.findNavController(this, R.id.my_nav_host_fragment)
                    .navigate(R.id.instoredBestPractice, null, navOption)

为什么是包容性标志false
IgorGanapolsky

2
根据文档 inclusive boolean: true to also pop the given destination from the back stack So,我将inclusive设置为false,因为我不想从堆栈中弹出当前片段。
Abdul Rahman Shamair

4
    NavController navController 
    =Navigation.findNavController(requireActivity(),          
    R.id.nav_host_fragment);// initialize navcontroller

    if (navController.getCurrentDestination().getId() == 
     R.id.my_current_frag) //for avoid crash
  {
    NavDirections action = 
    DailyInfoSelectorFragmentDirections.actionGoToDestionationFragment();

    //for clear current fragment from stack
    NavOptions options = new 
    NavOptions.Builder().setPopUpTo(R.id.my_current_frag, true).build();
    navController.navigate(action, options);
    }

1

您可以像这样覆盖基本活动的后退键:

override fun onBackPressed() {

  val navigationController = nav_host_fragment.findNavController()
  if (navigationController.currentDestination?.id == R.id.firstFragment) {
    finish()
  } else if (navigationController.currentDestination?.id == R.id.secondFragment) {
    // do nothing
  } else {
    super.onBackPressed()
  }

}

1

注意:这可能与实际问题无关。

如果需要单个片段实例来维护Navigation,则此方法将很有用。

// imports
import androidx.navigation.navOptions

// navigate to fragment
navController.navigate(R.id.nav_single_instance_fragment, null,
                            navOptions {
                                popUpTo = R.id.nav_single_instance_fragment
                                launchSingleTop = true
                            })

这里navOptions是DSL,需要导入。在回答此问题时,我正在使用以下gradle版本的Navigation UI

    def nav_version = "2.2.0"
    implementation "androidx.navigation:navigation-ui:$nav_version"
    implementation "androidx.navigation:navigation-fragment:$nav_version"
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

0

我努力了一段时间,以防止“后退”按钮返回到我的开始片段,在我的情况下,这是一个介绍性消息,应该只出现一次。

一种简单的解决方案是创建一个指向用户应停留的目的地的全局动作。您必须app:popUpTo="..."正确设置-将其设置为要弹出的目的地。就我而言,这是我的介绍性信息。也设置app:popUpToInclusive="true"


0

我可以通过一个小例子对此进行解释。我有两个片段雇员列表和删除雇员。在删除员工中,我有两个单击事件,即取消和删除。如果我单击“取消”,则它仅返回到员工列表屏幕,如果我单击“删除”,则我想清除所有以前的后退堆栈并转到员工列表屏幕。

要转到上一个屏幕:

<action
        android:id="@+id/action_deleteEmployeeFragment_to_employeesListFragment2"
        app:destination="@id/employeesListFragment"/>


    btn_cancel.setOnClickListener {
                it.findNavController().popBackStack()
            }

要清除所有先前的后退堆栈并转到新屏幕:

<action
        android:id="@+id/action_deleteEmployeeFragment_to_employeesListFragment2"
        app:destination="@id/employeesListFragment"
        app:popUpTo="@id/employeesListFragment"
        app:popUpToInclusive="true"
        app:launchSingleTop="true" />


 btn_submit.setOnClickListener {
                   it.findNavController().navigate(DeleteEmployeeFragmentDirections.actionDeleteEmployeeFragmentToEmployeesListFragment2())
        }

有关更多参考:Jetpack导航示例


0

您可以执行以下操作:

getFragmentManager().popBackStack();

如果要检查计数,可以执行以下操作:

getFragmentManager().getBackStackEntryCount()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.