Android 导航组件 - 向上导航打开相同的片段

Android Navigation Component - Navigate up opens the same fragment

我在执行时遇到问题

findNavController(R.id.main_nav_host).navigateUp()

findNavController(R.id.main_nav_host).popBackStack()

它reopens/navigates 返回到 same/current 片段,而不是返回到后台堆栈中的最后一个片段。

有人能为我指明正确的方向吗?

导航图:

<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/main_navigation_root"
            app:startDestination="@+id/dest_main">

    <fragment
            android:id="@+id/dest_main"
            android:name="com.example.popularmovies.ui.main.views.MainMoviesFragment"
            android:label="@string/home"
            tools:layout="@layout/fragment_main_movies">

        <action
                android:id="@+id/action_dest_main_to_dest_movie_details"
                app:destination="@+id/dest_movie_details"
                app:enterAnim="@anim/slide_in_right"
                app:exitAnim="@anim/slide_out_left"
                app:popEnterAnim="@anim/slide_in_left"
                app:popExitAnim="@anim/slide_out_right" />

    </fragment>

    <fragment
            android:id="@+id/dest_movie_details"
            android:name="com.example.popularmovies.ui.details.movie.view.MovieDetailsFragment"
            android:label="@string/movie_details"
            tools:layout="@layout/fragment_movie_details"/>

</navigation>

MainActivity 布局:

<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <fragment
            android:id="@+id/main_nav_host"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/main_navigation"/>

</FrameLayout>

主要活动:

class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>

    private lateinit var appBarConfiguration: AppBarConfiguration

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

        initNavUi()
    }

    override fun onBackPressed() {

        findNavController(R.id.main_nav_host).popBackStack()
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        return when (item.itemId) {
            R.id.action_settings -> true
            else -> super.onOptionsItemSelected(item)
        }
    }

    override fun onSupportNavigateUp(): Boolean {

        return findNavController(R.id.main_nav_host).navigateUp()
    }

    override fun supportFragmentInjector(): AndroidInjector<Fragment> {

        return dispatchingAndroidInjector
    }

    private fun initNavUi() {

        val navController = Navigation.findNavController(this, R.id.main_nav_host)
        appBarConfiguration = AppBarConfiguration(
            setOf(R.id.dest_main)
        )

        NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration)
    }

}

目标主页片段:

class MainMoviesFragment : Fragment(), Injectable, MovieViewHolder.MovieClickListener {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private lateinit var fragmentViewModel: MainMoviesFragmentViewModel

    private lateinit var moviesRv: RecyclerView
    private lateinit var moviesAdapter: MainMoviesAdapter

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

        return inflater.inflate(R.layout.fragment_main_movies, container, false)
    }

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

        initViews(view)
    }

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

        fragmentViewModel = ViewModelProviders.of(this,viewModelFactory).get(MainMoviesFragmentViewModel::class.java)
        fragmentViewModel.start()

        observe()
    }

    override fun onMovieClicked(position: Int) {

        fragmentViewModel.onMovieClicked(position)
    }

    private fun initViews(view: View) {

        moviesRv = view.findViewById<RecyclerView>(R.id.fragment_main_movies_rv).apply{

            layoutManager = LinearLayoutManager(context)
            setHasFixedSize(true)

            moviesAdapter = MainMoviesAdapter(this@MainMoviesFragment)
            adapter = moviesAdapter
        }
    }

    private fun observe() {

        fragmentViewModel.moviesLiveData.observe(this, Observer { moviesAdapter.submitList(it) })
        fragmentViewModel.onMovieClickedLiveEvent.observe(this, Observer { handleMovieClickedEvent(it) })

    }

    private fun handleMovieClickedEvent(movieModel: MovieModel?){

        val action = MainMoviesFragmentDirections.actionDestMainToDestMovieDetails()
        findNavController().navigate(action)
    }

}

目标目标片段:

class MovieDetailsFragment : Fragment() {

    private lateinit var viewModel: MovieDetailsFragmentViewModel

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

        return inflater.inflate(R.layout.fragment_movie_details, container, false)
    }

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

        viewModel = ViewModelProviders.of(this).get(MovieDetailsFragmentViewModel::class.java)
    }

}

GitHub上的项目代码可以找到here

你的 onMovieClickedLiveEvent 用于你的 MainMoviesFragmentViewModel,每次你回到你的 MainMoviesFragment 时都会触发,因为 MutableLiveData 保存了当前值。这意味着 popBackStack() 工作正常,但随后您会立即返回到详细信息页面(注意:您仍然需要删除 onBackPressed() 中的代码,因为现在您无法退出通过点击后退按钮的应用程序)。

似乎,尤其是对于变量的名称,您应该使用 SingleLiveEvent class, instead of MutableLiveData directly, as per this blog post.

当然,在这种情况下,根本没有特别理由使用 LiveData 或通过 ViewModel。您的 MovieViewHolder 可以将 MovieModel 直接传递给 onMovieClicked,后者可以直接调用 handleMovieClickedEvent。这将避免使用 LiveData(旨在存储 state,而不是事件)并更好地模拟您实际想要实现的目标:事件侦听器。