从ViewModel
内部导航将意味着您需要一个与MVVM概念背道而驰的视图实例。而是使用LiveData
指示片段需要导航到下一个目的地。您可以使用以下Event
类(来自Google的architecture-samples之一)来确保仅触发一次导航。
[我正在使用MVVM模式创建应用。我正在使用Navigation Graph在我的应用中管理片段,根据推荐的方法,我们不必将UI逻辑放在Activity / Fragments]中>,但在Viewmodel中。
所以我的问题是如何从一个片段导航到另一个片段。我知道可以使用navController.navigate(R.id.action_here)
直接在片段内部完成此操作,但是如何处理按下按钮时从ViewModel进行的导航?。
我的代码:
IntroViewModel.kt
class IntroViewModel : ViewModel() { fun onBtn1Pressed(view: View) { Log.d(IntroViewModel::class.java.simpleName, ": onBtn1Pressed") } fun onBtn2Pressed(view: View) { Log.d(IntroViewModel::class.java.simpleName, ": onBtn2Pressed ") } }
IntroFragment.kt:
class IntroFragment : Fragment() { private lateinit var viewModel: IntroViewModel private lateinit var navController: NavController lateinit var introBinding: IntroFragmentBinding override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { introBinding = DataBindingUtil.inflate(inflater, R.layout.intro_fragment, container, false) viewModel = ViewModelProviders.of(this).get(IntroViewModel::class.java) introBinding.introModel = viewModel return introBinding.root; } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) navController = Navigation.findNavController(view) } }
intro_fragment.xml:
<data>
<variable
name="introModel"
type="example.com.viewmodel.IntroViewModel" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:padding="@dimen/padding_16dp"
tools:context=".fragments.IntroFragment">
<TextView
android:id="@+id/txt_"
style="@style/TextAppearance.MaterialComponents.Headline5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Choose one " />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txt_"
android:onClick="@{introModel::onBtn1Pressed}"
android:layout_marginTop="@dimen/margin_8dp"
android:text="Btn1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_2"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{introModel::onBtn2Pressed}"
android:layout_below="@id/btn_1"
android:layout_alignStart="@id/btn_1"
android:layout_alignEnd="@id/btn_1"
android:layout_marginTop="@dimen/margin_8dp"
android:text="Btn2" />
</RelativeLayout>
[我正在使用MVVM模式创建一个应用程序。我正在使用导航图来管理我的应用程序中的片段,根据推荐的方法,我们不必将UI逻辑放在Activity / Fragments中,而在...]]] >>
从ViewModel
内部导航将意味着您需要一个与MVVM概念背道而驰的视图实例。而是使用LiveData
指示片段需要导航到下一个目的地。您可以使用以下Event
类(来自Google的architecture-samples之一)来确保仅触发一次导航。
open class Event<out T>(private val content: T) { @Suppress("MemberVisibilityCanBePrivate") var hasBeenHandled = false private set // Allow external read but not write /** * Returns the content and prevents its use again. */ fun getContentIfNotHandled(): T? { return if (hasBeenHandled) { null } else { hasBeenHandled = true content } } /** * Returns the content, even if it's already been handled. */ fun peekContent(): T = content }
与此
Observer
一起使用:
/** * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has * already been handled. * * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled. */ class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> { override fun onChanged(event: Event<T>?) { event?.getContentIfNotHandled()?.let { onEventUnhandledContent(it) } } }
这是您的
LiveData
:
private val _openTaskEvent = MutableLiveData<Event<String>>() val openTaskEvent: LiveData<Event<String>> = _openTaskEvent
最后,您可以这样观察它:
viewModel.openTaskEvent.observe(this, EventObserver {
//Do your navigation here
})
更新后的答案(感谢Mohamed Mohsin ::)>
IntroViewModel.kt:
class IntroViewModel : ViewModel() {
private val _navigateScreen = MutableLiveData<Event<Any>>()
val navigateScreen: LiveData<Event<Any>> = _navigateScreen
fun onBtn1Pressed(view: View) {
_navigateScreen.value = Event(R.id.action_here)
}
fun onBtn2Pressed(view: View) {
_navigateScreen.value = Event(R.id.action_here)
}
}
Event.kt:
open class Event<out T>(private val content: T) { var hasBeenHandled = false private set // Allow external read but not write /** * Returns the content and prevents its use again. */ fun getContentIfNotHandled(): T? { return if (hasBeenHandled) { null } else { hasBeenHandled = true content } } /** * Returns the content, even if it's already been handled. */ fun peekContent(): T = content } class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> { override fun onChanged(event: Event<T>?) { event?.getContentIfNotHandled()?.let { onEventUnhandledContent(it) } } }
IntroFragment.kt:
class IntroFragment : Fragment() { private lateinit var viewModel: IntroViewModel private lateinit var navController: NavController private lateinit var introBinding: IntroFragmentBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { introBinding = DataBindingUtil.inflate(inflater, R.layout.intro_fragment, container, false) viewModel = ViewModelProviders.of(this).get(IntroViewModel::class.java) introBinding.introModel = viewModel return introBinding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) navController = Navigation.findNavController(view) viewModel.navigateScreen.observe(activity!!, EventObserver { navController.navigate(it) }) } }
从ViewModel
内部导航将意味着您需要一个与MVVM概念背道而驰的视图实例。而是使用LiveData
指示片段需要导航到下一个目的地。您可以使用以下Event
类(来自Google的architecture-samples之一)来确保仅触发一次导航。
更新后的答案(感谢Mohamed Mohsin ::)>
IntroViewModel.kt: