如何以编程方式更改导航图的起始目标[Jetpack]

时间:2018-07-04 11:50:39

标签: android android-jetpack android-navigation android-architecture-navigation

基本上,我有以下导航图:

enter image description here

我想在到达导航图后立即将导航图的起点更改为fragment 2(以防止在按“后退”按钮时像回到启动屏幕一样回到fragment 1)。

这是我的代码:

navGraph = navController.getGraph();
navGraph.setStartDestination(R.id.fragment2);
navController.setGraph(navGraph);

但是,很明显,它不起作用,并且在按下返回按钮后返回到fragment 1

我做错了吗? 还有其他解决方案吗?

7 个答案:

答案 0 :(得分:20)

更新:

当您具有如下导航图时:

public function uploaderPost(Request $request)
{
    // Here we get auth token and put into protected valiable `$this->token`
    $this->authorizeApi(); 

    $requestData = $request->except('_token');


    $package = $requestData['file'];

    $uploadPackageRequest =
        $this->client->request('POST', config('bulk_api.url') .'/api/bulk/upload?token=' . $this->token,
            [
            'multipart' => [
                [
                    'name'     => 'file',
                    'contents' => fopen($package->getPathName(), 'r'),
                    'filename' => $package->getClientOriginalName(),
                ],
            ]
        ]);
    $uploadPackageRequestJson = json_decode($uploadPackageRequest->getBody()->getContents());
    $uploadPackageRequestStatus = $uploadPackageRequestJson->status;

    if($uploadPackageRequestStatus == 1) {
        $package = BulkUploadPackage::where('id', '=',$uploadPackageRequestJson->id)->first();

        // If package is okay - running it
        if($package !== null){
            // Here where I expect job to be dispatched (code above)
            $runPackageRequest =
                $this->client->request('POST', config('api.url') .'/api/bulk/run?token=' . $this->token,
                    [
                        'multipart' => [
                            [
                                'name' => 'package_id',
                                'contents' => $package->id
                            ],
                        ]
                    ]);


            // Here I'm receiving stream for some reason
            dd($runPackageRequest->getBody());

            if($runPackageRequest->getStatusCode()==200){
                return redirect(url('/success'));
            }
        }
    }
    return back();
}

您要导航到第二个片段并将其设为图的根,请指定下一个<fragment android:id="@+id/firstFragment" android:name="com.appname.package.FirstFragment" > <action android:id="@+id/action_firstFragment_to_secondFragment" app:destination="@id/secondFragment" /> </fragment> <fragment android:id="@+id/secondFragment" android:name="com.appname.package.SecondFragment"/>

NavOptions

并使用它们进行导航:

NavOptions navOptions = new NavOptions.Builder()
        .setPopUpTo(R.id.firstFragment, true)
        .build();
  

Navigation.findNavController(view).navigate(R.id.action_firstFragment_to_secondFragment, bundle, navOptions); -导航至给定目的地。这将从后堆栈中弹出所有不匹配的目标,直到找到该目标为止。

     

setPopUpTo(int destinationId, boolean inclusive)-弹出的目的地,清除所有中间目的地。

     

destinationId-为true也将从后堆栈中弹出给定的目的地。


替代:

inclusive

然后在您的代码上:

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment" >
<action
    android:id="@+id/action_firstFragment_to_secondFragment"
    app:destination="@id/secondFragment"
    app:popUpTo="@+id/firstFragment"
    app:popUpToInclusive="true" /> 
</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"/>

旧答案

已弃用已弃用findNavController(fragment).navigate( FirstFragmentDirections.actionFirstFragmentToSecondFragment()) 中的动作属性clearTaskNavOptions中的关联API。

来源:https://developer.android.com/jetpack/docs/release-notes


如果要将根片段更改为fragment 2(例如,按fragment 2上的后退按钮将退出应用程序),则应将下一个属性置于actiondestination

app:clearTask="true"

实际上,它看起来是另一种方式:

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment"
    android:label="fragment_first" >
    <action
        android:id="@+id/action_firstFragment_to_secondFragment"
        app:destination="@id/secondFragment"
        app:clearTask="true" /> 
</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"
    android:label="fragment_second"/>

我已将app:clearTask="true"添加到操作中。


现在,当您执行从fragment 1fragment 2的导航时,请使用以下代码:

Navigation.findNavController(view)
        .navigate(R.id.action_firstFragment_to_secondFragment);

答案 1 :(得分:2)

您真的不需要弹出飞溅片段。它可以在您的App余生中保持不变。您应该从“启动画面”中确定要显示的下一个画面。

Login State Machine flow

在上面的图片中,您可以在“初始屏幕状态”中询问是否存在已保存的LoginToken。如果为空,则导航到登录屏幕。

完成登录屏幕后,您需要分析结果并保存令牌,然后导航到下一个片段主屏幕。

在主屏幕中按下“后退”按钮时,您会将结果消息发送回启动屏幕,指示其完成应用程序。

答案 2 :(得分:2)

在MainActivity.kt

    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val inflater = navHostFragment.navController.navInflater
    val graph = inflater.inflate(R.navigation.booking_navigation)

    if (isTrue){
        graph.startDestination = R.id.DetailsFragment
    }else {
        graph.startDestination = R.id.OtherDetailsFragment
    }

    val navController = navHostFragment.navController
    navController.setGraph(graph, intent.extras)

从nav_graph.xml中删除startDestination

?xml version="1.0" encoding="utf-8"?>
<!-- app:startDestination="@id/oneFragment" -->

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/navigation_main">

  <fragment
    android:id="@+id/DetailFragment"
    android:name="DetailFragment"
    android:label="fragment_detail"
    tools:layout="@layout/fragment_detail"/>
  <fragment
    android:id="@+id/OtherDetailFragment"
    android:name="OtherDetailFragment"
    android:label="fragment_other_detail"
    tools:layout="@layout/fragment_other_detail"/>
</navigation>

答案 3 :(得分:1)

您也可以尝试以下方法。

$('.my-select').on('change', function() {
  var value = $(this).val(); //get the value
  $('.box').hide(); //hide all of them
  $('#id' + value).show(); //show the correct one
});

$('document').ready(function() {
  //trigger the change to run the process once
  $('.my-select').trigger('change');
});

与其试图弹出目标位置或手动导航到目标位置,不如让另一个具有不同工作流程的导航图更好。 当您希望有条件地完全不同的导航流程时,这会更好。

答案 4 :(得分:1)

好吧,在弄乱了一点之后,我发现了一个对我有用的解决方案,不需要大量的工作。

似乎必须要做好两件事,好像您的secondFragment是起始目的地一样。

在接受的帖子中使用替代选项

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment" >

    <action
        android:id="@+id/action_firstFragment_to_secondFragment"
        app:destination="@id/secondFragment"
        app:popUpTo="@+id/firstFragment"
        app:popUpToInclusive="true" /> 

</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"/>

以上内容将从移动堆栈中移除firstFragment,并在移动时使secondFragment膨胀。该应用程序无法再退回到firstFragment了,但是您的左边的secondFragment显示了一个后退箭头,如@szaske所述。

这就是与众不同的原因。我以前像这样使用NavigationController.graph定义了AppBarConfig

// Old code
val controller by lazy { findNavController(R.id.nav_host_fragment) }
val appBarConfig by lazy { AppBarConfiguration(controller.graph) }

更新它以定义一组顶级目的地,解决了在secondFragment上显示向后箭头而不是汉堡菜单图标的问题。

// secondFragment will now show hamburger menu instead of back arrow.
val appBarConfig by lazy { AppBarConfiguration(setOf(R.id.firstFragment, R.id.secondFragment)) }

设置开始目的地可能会对您的项目造成负面影响,也可能不会产生负面影响,因此请根据需要进行设置,但是在本示例中,我们不需要这样做。如果它使您变得温暖而模糊,以确保您的图形定义了正确的起始片段,则可以这样做。

controller.graph.startDestination = R.id.secondFragment

注意:设置此项不会阻止在第二片段中出现后退箭头,而且我发现这似乎对导航没有影响。

答案 5 :(得分:0)

对于那些具有与此内容相似的导航xml文件的用户:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation"
    app:startDestination="@+id/nav_home">

    <fragment
        android:id="@+id/nav_home"
        android:name="HomeFragment"
        android:label="@string/menu_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/nav_users"
        android:name="UsersFragment"
        android:label="@string/users"
        tools:layout="@layout/fragment_users" />

    <fragment
        android:id="@+id/nav_settings"
        android:name="SettingsFragment"
        android:label="@string/settings"
        tools:layout="@layout/fragment_settings" />
</navigation>

假设当前打开的片段是home片段,并且您想导航到users片段,为此,只需从要导航到导航方法的元素中调用setOnClickListener,类似于以下代码:

yourElement.setOnClickListener {
  view.findNavController().navigate(R.id.nav_users)
}

这将使应用导航到该其他片段,并且还将处理工具栏中的标题。

答案 6 :(得分:-2)

我为此找到了解决方案,但这很丑陋。我想这在alpha库中是可以预期的,但是我希望Google考虑简化/修复它,因为这是一种非常流行的导航模式。

Alexey的解决方案对我不起作用。我的问题是,使用以下命令,我的操作栏上显示了向上箭头:

NavigationUI.setupActionBarWithNavController(this, navController)

如果我按照Alexey的建议进行操作,那么我的新起始片段仍然会有一个箭头指向我的初始起始片段。如果按下该向上箭头,我的应用程序将重新启动,然后过渡到自身(新的开始片段)

这是实现我想要的代码所需的代码:

  • 片段#1是我的应用程序最初启动的地方
  • 我可以在片段#1中进行Auth检查,然后以编程方式将起始位置更改为片段#2。
  • 在片段#2中,没有向上箭头,按后退按钮不会带您进入片段#1。

这是完成此操作的代码。在我的活动的onCreate中:

// Setup the toolbar
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(false)

// Configure the navigation
val navHost = nav_host_fragment as NavHostFragment
graph = navHost.navController
        .navInflater.inflate(R.navigation.nav_graph)
graph.startDestination = R.id.welcomeFragment

// This seems to be a magical command. Not sure why it's needed :(
navHost.navController.graph = graph

NavigationUI.setupActionBarWithNavController(this, navHost.navController)

还有:

fun makeHomeStart(){
    graph.startDestination = R.id.homeFragment
}

然后根据Alexey的建议,在片段#1的onActivityCreated中创建:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    ...

    // Check for user authentication
    if(sharedViewModel.isUserAuthenticated()) {

        (activity as MainActivity).makeHomeStart() //<---- THIS is the key
        val navOptions = NavOptions.Builder()
                .setPopUpTo(R.id.welcomeFragment, true)
                .build()
        navController.navigate(R.id.action_welcomeFragment_to_homeFragment,null,navOptions)

    } else {
        navController.navigate(R.id.action_welcomeFragment_to_loginFragment)
    }
}

关键代码是: (activity as MainActivity).makeHomeStart()仅在更改图表startDestination的活动中运行方法。我可以清理它并将其转换为界面,但是我将等待Google并希望他们改善整个过程。方法“ setPopUpTo”在我看来命名很差,而且命名您要从图中切出的片段的名称也不直观。我对他们在navOptions中进行这些更改也感到奇怪。我认为navOptions仅与它们连接的导航动作有关。

我什至不知道navHost.navController.graph = graph的作用,但是没有它,向上箭头会返回。 :(

我正在使用导航1.0.0-alpha06。