首页 > 解决方案 > 导航组件 - 仅限 Activity 中的 ActionBar 句柄

问题描述

我终于在一个新项目中采用了导航架构组件,我已经遇到了一些文档似乎没有解决的问题。

在活动中设置 ActionBar 菜单时,当我导航到另一个片段然后尝试使用我收到的 ActionBar 菜单时

java.lang.IllegalArgumentException: Navigation action/destination X cannot be found from the current destination

看来我还必须从所有可能的目的地添加动作以转到所有其他目的地,这似乎有点矫枉过正,这是不可能的。这个问题必须有一个我找不到的解决方案。

我打算打开在片段元素中膨胀 MainActivity 和 MainFragment 的应用程序。MainActivity 仍应处理 ActionBar 内的顶级导航。绝对没有理由我需要在每个片段中重复菜单点击工作并定义应用程序所有其他区域的目的地。

主要活动

class MainActivity : AppCompatActivity() {

    private lateinit var navController: NavController
    private lateinit var appBarConfiguration: AppBarConfiguration
    private var menu: Menu? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val controller by lazy { findNavController(R.id.fragment_container) }
        navController = controller

        val appBarConfig by lazy { AppBarConfiguration(navController.graph) }
        appBarConfiguration = appBarConfig

        setSupportActionBar(toolbar)
        setupActionBarWithNavController(navController, appBarConfiguration)
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.appbar_menu, menu)
        this.menu = menu
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        var direction: NavDirections? = null

        when(item.itemId) {
            R.id.action_messages -> {
                direction = MainFragmentDirections.actionMainFragmentToMessagesFragment()
            }
            R.id.action_menu -> {
                direction = MainFragmentDirections.actionMainFragmentToMessagesFragment()
            }
            else -> super.onOptionsItemSelected(item)
        }

        if (direction != null) navController.navigate(direction)

        return true
    }

    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }
}

活动主

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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/card_id_test"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?actionBarSize"
        android:background="@color/colorPrimary"
        android:elevation="4dp"
        android:clipToPadding="false"
        app:menu="@menu/appbar_menu"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:titleTextColor="@color/white" />

    <fragment
        android:id="@+id/fragment_container"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph"
        app:layout_constraintTop_toBottomOf="@+id/toolbar"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

主要片段

class MainFragment : Fragment() {

    private var _binding: FragmentMainBinding? = null
    private val binding get() = _binding!!
    private var mActivity : MainActivity? = null

    private var mView: View? = null

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        super.onCreateView(inflater, container, savedInstanceState)

        _binding = FragmentMainBinding.inflate(inflater, container, false)
        mView = binding.root
        mActivity = (activity as MainActivity)

        return mView
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        btn_dashboard.setOnClickListener {
            findNavController().navigate(
                MainFragmentDirections.actionMainFragmentToDashboardFragment())
        }
    }
}

片段主

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/content_test"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.appcompat.widget.AppCompatTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:text="Main Fragment"
        android:textSize="24dp" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_dashboard"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:text="Dashboard" />

</androidx.constraintlayout.widget.ConstraintLayout>

仪表板片段

class DashboardFragment : Fragment() {

    private var _binding: FragmentDashboardBinding? = null
    private val binding get() = _binding!!
    private var mActivity : MainActivity? = null

    private var mView: View? = null

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        super.onCreateView(inflater, container, savedInstanceState)

        _binding = FragmentDashboardBinding.inflate(inflater, container, false)
        mView = binding.root
        mActivity = (activity as MainActivity)

        return mView
    }
}

片段仪表板

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/content_test"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.appcompat.widget.AppCompatTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:text="Dashboard Fragment"
        android:textSize="24dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

导航图

<?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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.example.appbarnavigation.MainFragment"
        android:label="Main" >

        <action
            android:id="@+id/action_mainFragment_to_messagesFragment"
            app:destination="@id/action_messages" />

        <action
            android:id="@+id/action_mainFragment_to_menuFragment"
            app:destination="@id/action_menu" />

        <action
            android:id="@+id/action_mainFragment_to_dashboardFragment"
            app:destination="@id/dashboardFragment" />

    </fragment>

    <fragment
        android:id="@+id/action_messages"
        android:name="com.example.appbarnavigation.MessagesFragment"
        android:label="Messages" />

    <fragment
        android:id="@+id/action_menu"
        android:name="com.example.appbarnavigation.MenuFragment"
        android:label="Menu" />

    <fragment
        android:id="@+id/dashboardFragment"
        android:name="com.example.appbarnavigation.DashboardFragment"
        android:label="Dashboard" />

</navigation>

应用栏菜单

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_messages"
        android:orderInCategory="1"
        android:title="Messages"
        android:icon="@drawable/ic_message"
        app:showAsAction="always"/>
    <item
        android:id="@+id/action_menu"
        android:orderInCategory="2"
        android:title="Menu"
        android:icon="@drawable/ic_menu"
        app:showAsAction="always"/>
</menu>

标签: androidandroid-actionbarandroid-toolbarillegalstateexceptionandroid-architecture-navigation

解决方案


这是通过定义和使用全局操作来解决的。

<?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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.example.appbarnavigation.MainFragment"
        android:label="Main" >

        <action
            android:id="@+id/action_mainFragment_to_dashboardFragment"
            app:destination="@id/dashboardFragment" />

    </fragment>

    <fragment
        android:id="@+id/action_messages"
        android:name="com.example.appbarnavigation.MessagesFragment"
        android:label="Messages" />

    <fragment
        android:id="@+id/action_menu"
        android:name="com.example.appbarnavigation.MenuFragment"
        android:label="Menu" />

    <fragment
        android:id="@+id/dashboardFragment"
        android:name="com.example.appbarnavigation.DashboardFragment"
        android:label="Dashboard" />

    <action android:id="@+id/action_global_action_messages" app:destination="@id/action_messages" />
    <action android:id="@+id/action_global_action_menu" app:destination="@id/action_menu" />

</navigation>

并在 MainActivity

override fun onOptionsItemSelected(item: MenuItem) = when(item.itemId) {
    R.id.action_messages -> {
        navController.navigate(R.id.action_global_action_messages)
        true
    }
    
    R.id.action_menu -> {
        navController.navigate(R.id.action_global_action_menu)
        true
    }

    // This is used for menu buttons or anything not explicitly defined here
    else -> {
        super.onOptionsItemSelected(item)
    }
}

推荐阅读