如何在Xamarin MvvmCross中通过BottomNavigationView导航视图模型
Hey there! Let's walk through how to get your BottomNavigationView navigation working properly with MvvmCross 6.0.1 while sticking strictly to MVVM and MvvmCross best practices.
1. Recommended Architecture Implementation
Your existing project structure (host MainContainerActivity/MainContainerViewModel + individual Fragment/ViewModel pairs) is already aligned with MvvmCross's ViewModel-first navigation philosophy. Here's how to flesh out the navigation logic:
a. Add Navigation Commands to MainContainerViewModel
First, define MvxAsyncCommand properties in your host ViewModel to handle navigation triggers. Use MvvmCross's built-in IMvxNavigationService to handle the heavy lifting of ViewModel/Fragment resolution:
public class MainContainerViewModel : MvxViewModel { private readonly IMvxNavigationService _navigationService; private int _selectedMenuItemId; // Navigation commands public ICommand NavigateToMainCommand { get; } public ICommand NavigateToSettingsCommand { get; } public ICommand NavigateToFavoritesCommand { get; } // Bindable property for bottom nav selected state public int SelectedMenuItemId { get => _selectedMenuItemId; set => SetProperty(ref _selectedMenuItemId, value); } public MainContainerViewModel(IMvxNavigationService navigationService) { _navigationService = navigationService; _selectedMenuItemId = Resource.Id.menu_main; // Initialize with first tab // Initialize commands with navigation logic NavigateToMainCommand = new MvxAsyncCommand(async () => await NavigateToTab<MainViewModel>(Resource.Id.menu_main)); NavigateToSettingsCommand = new MvxAsyncCommand(async () => await NavigateToTab<SettingsViewModel>(Resource.Id.menu_settings)); NavigateToFavoritesCommand = new MvxAsyncCommand(async () => await NavigateToTab<FavoritesViewModel>(Resource.Id.menu_favorites)); } private async Task NavigateToTab<TViewModel>(int menuItemId) where TViewModel : IMvxViewModel { // Skip navigation if we're already on the target tab var currentVm = Mvx.IoCProvider.Resolve<IMvxCurrentViewModel>().CurrentViewModel; if (currentVm is TViewModel) return; // Navigate to the target ViewModel and update selected menu state await _navigationService.Navigate<TViewModel>(); SelectedMenuItemId = menuItemId; } }
b. Bind BottomNavigationView to ViewModel in MainContainerActivity
Use MvvmCross's binding system to link menu item clicks to your ViewModel commands, and sync the selected state with the SelectedMenuItemId property:
public class MainContainerActivity : MvxAppCompatActivity<MainContainerViewModel> { private BottomNavigationView _bottomNav; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.activity_main_container); _bottomNav = FindViewById<BottomNavigationView>(Resource.Id.bottom_navigation); // Setup data bindings var bindingSet = this.CreateBindingSet<MainContainerActivity, MainContainerViewModel>(); // Bind menu item clicks to corresponding commands bindingSet.Bind(_bottomNav) .For(v => v.ItemClick) .To(vm => vm.NavigateToMainCommand) .WithConversion(new BottomNavItemMatcher(Resource.Id.menu_main)); bindingSet.Bind(_bottomNav) .For(v => v.ItemClick) .To(vm => vm.NavigateToSettingsCommand) .WithConversion(new BottomNavItemMatcher(Resource.Id.menu_settings)); bindingSet.Bind(_bottomNav) .For(v => v.ItemClick) .To(vm => vm.NavigateToFavoritesCommand) .WithConversion(new BottomNavItemMatcher(Resource.Id.menu_favorites)); // Sync selected menu state with ViewModel bindingSet.Bind(_bottomNav).For(v => v.SelectedItemId).To(vm => vm.SelectedMenuItemId); bindingSet.Apply(); // Load initial fragment if first launch if (savedInstanceState == null) { ViewModel.NavigateToMainCommand.Execute(null); } } }
You'll need a custom value converter to trigger the command only when the correct menu item is clicked:
public class BottomNavItemMatcher : MvxValueConverter<BottomNavigationView.ItemSelectedEventArgs, object> { private readonly int _targetItemId; public BottomNavItemMatcher(int targetItemId) => _targetItemId = targetItemId; protected override object Convert(BottomNavigationView.ItemSelectedEventArgs value, Type targetType, object parameter, CultureInfo culture) { // Only pass the command parameter if the clicked item matches our target return value.Item.ItemId == _targetItemId ? parameter : MvxBindingConstant.UnsetValue; } }
c. Configure ViewModel-Fragment Mappings
Tell MvvmCross which Fragment corresponds to each ViewModel by updating your Setup.cs:
protected override void InitializeViewLookup() { base.InitializeViewLookup(); var viewMappings = new Dictionary<Type, Type> { { typeof(MainViewModel), typeof(MainFragment) }, { typeof(SettingsViewModel), typeof(SettingsFragment) }, { typeof(FavoritesViewModel), typeof(FavoritesFragment) } }; Mvx.IoCProvider.RegisterSingleton<IMvxViewsContainer>(new MvxViewsContainer(viewMappings)); }
Ensure your activity has a FrameLayout container (with ID fragment_container) where MvvmCross will load the fragments.
2. Is Your Current Setup MVVM & MvvmCross Compliant?
Short answer: Yes, your structure is fully aligned with MVVM and MvvmCross best practices—here's why:
- You're following the ViewModel-first pattern: Navigation logic lives in the ViewModel, not the Activity/Fragment.
- Each UI component (Fragment) has its own dedicated ViewModel, separating business logic from presentation code.
- The host
MainContainerViewModelacts as a navigation coordinator, which is a clean way to manage bottom tab navigation in MVVM.
Just make sure you avoid:
- Writing direct Fragment replacement code in your Activity/Fragment (let MvvmCross handle this via navigation service).
- Putting business logic in views—keep all state and logic in ViewModels.
3. Bonus Tips
- Handle Fragment Lifecycle: Use MvvmCross's
IMvxViewModelLifecycleinterface in your Fragment ViewModels to react toAppearing/Disappearingevents (useful for refreshing data when a tab is selected). - Avoid Duplicate Fragments: The check for the current ViewModel in
NavigateToTabprevents redundant Fragment creation. - Animate Transitions: You can customize Fragment transitions by modifying the MvvmCross presenter in
Setup.csif needed.
内容的提问来源于stack exchange,提问作者Dominik Roszkowski

