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,而不是事件)并更好地模拟您实际想要实现的目标:事件侦听器。
我在执行时遇到问题
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,而不是事件)并更好地模拟您实际想要实现的目标:事件侦听器。