如何通过 Xamarin MvvmCross 中的 BottomNavigationView 在视图模型之间导航

Posted

技术标签:

【中文标题】如何通过 Xamarin MvvmCross 中的 BottomNavigationView 在视图模型之间导航【英文标题】:How to navigate between view models via BottomNavigationView in Xamarin MvvmCross 【发布时间】:2018-10-23 10:29:54 【问题描述】:

假设我们有一个 MvvmCross 6.0.1 原生应用,其中包含一个包含 BottomNavigationViewandroid Activity,其实现方式与 this blog post by James Montemagno 相同,但没有导航和替换片段。

我想做的是将BottomNavigationView 项目绑定到 ViewModel 中的 MvxCommands(或 MvxAsyncCommands),以便在多个 ViewModel 之间导航。

我应该应用什么样的架构来实现这一目标? 我的方法是正确的还是我在做一些违反 MVVM 模式和 MvvmCross 可能性的事情?

可以在here on github 找到完整的工作示例和几个添加。

目前我有(用MvxScaffolding搭建的脚手架)。

MainContainerActivity 和对应的 MainContainerViewModel - 在这里我想存储在视图模型之间导航的命令 MainFragment 和对应的 MainViewModel - 这是第一个片段/视图模型 SettingsFragment 和对应的 SettingsViewModel - 我想从 MainViewModel 导航到它,反之亦然 FavoritesFragment 和对应的FavoritesViewModel

主要活动如下:

using Android.App;
using Android.OS;
using Android.Views;
using PushNotifTest.Core.ViewModels.Main;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using Microsoft.AppCenter.Push;
using Android.Graphics.Drawables;
using Android.Support.Design.Widget;
using MvvmCross.Binding.BindingContext;
using System;
using System.Windows.Input;

namespace PushNotifTest.Droid.Views.Main

    [Activity(
        Theme = "@style/AppTheme",
        WindowSoftInputMode = SoftInput.AdjustResize | SoftInput.StateHidden)]
    public class MainContainerActivity : BaseActivity<MainContainerViewModel>
    
        protected override int ActivityLayoutId => Resource.Layout.activity_main_container;

        BottomNavigationView bottomNavigation;

        public ICommand GoToSettingsCommand  get; set; 
        public ICommand GoToFavoritesCommand  get; set; 
        public ICommand GoToHomeCommand  get; set; 

        protected override void OnCreate(Bundle bundle)
        
            base.OnCreate();
            AddBottomNavigation();
        

        private void AddBottomNavigation()
        
            bottomNavigation = (BottomNavigationView)FindViewById(Resource.Id.bottom_navigation);
            if (bottomNavigation != null)
            
                bottomNavigation.NavigationItemSelected += BottomNavigation_NavigationItemSelected;
                // trying to bind command to view model property
                var set = this.CreateBindingSet<MainContainerActivity, MainContainerViewModel>();
                set.Bind(this).For(v => v.GoToSettingsCommand).To(vm => vm.NavigateToSettingsCommand);
                set.Bind(this).For(v => v.GoToHomeCommand).To(vm => vm.NavigateToHomeCommand);
                set.Bind(this).For(v => v.GoToFavoritesCommand).To(vm => vm.NavigateToFavoritesCommand);
                set.Apply();
            
            else
            
                System.Diagnostics.Debug.WriteLine("Bottom navigation menu is null");
            
        

        private void BottomNavigation_NavigationItemSelected(object sender, BottomNavigationView.NavigationItemSelectedEventArgs e)
        
            try
            
                System.Diagnostics.Debug.WriteLine($"Bottom navigation menu is selected: e.Item.ItemId");

                if (e.Item.ItemId == Resource.Id.menu_settings)
                    if (GoToSettingsCommand != null && GoToSettingsCommand.CanExecute(null))
                        GoToSettingsCommand.Execute(null);
                if (e.Item.ItemId == Resource.Id.menu_list)
                    if (GoToFavoritesCommand != null && GoToFavoritesCommand.CanExecute(null))
                        GoToFavoritesCommand.Execute(null);
                if (e.Item.ItemId == Resource.Id.menu_home)
                    if (GoToHomeCommand != null && GoToHomeCommand.CanExecute(null))
                        GoToHomeCommand.Execute(null);
            
            catch (Exception exception)
            
                System.Diagnostics.Debug.WriteLine($"Exception: exception.Message");
                Crashes.TrackError(exception);
            
        
    

底部导航元素是:

<?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/menu_home"
      android:enabled="true"
      android:icon="@drawable/ic_history"
      android:title="@string/tab1_title"
      app:showAsAction="ifRoom" />

  <item
      android:id="@+id/menu_list"
      android:enabled="true"
      android:icon="@drawable/ic_list"
      android:title="@string/tab2_title"
      app:showAsAction="ifRoom" />

  <item
      android:id="@+id/menu_settings"
      android:enabled="true"
      android:icon="@drawable/ic_settings"
      android:title="@string/tab3_title"
      app:showAsAction="ifRoom" />
</menu>

而视图模型中的命令只是:

public IMvxAsyncCommand NavigateToSettingsCommand => new MvxAsyncCommand(async () => await _navigationService.Navigate<SettingsViewModel>());
public IMvxAsyncCommand NavigateToFavoritesCommand => new MvxAsyncCommand(async () => await _navigationService.Navigate<FavoritesViewModel>());
public IMvxAsyncCommand NavigateToHomeCommand => new MvxAsyncCommand(async () => await _navigationService.Navigate<MainViewModel>());

【问题讨论】:

您好,我不确定您的问题是什么。 我想对我的方法发表一些评论 - 似乎有太多代码无法执行简单的导航。似乎它违反了一些基本规则并且很难维护。如果您能分享一些更好的方法,我将不胜感激。 【参考方案1】:

您可以为 BottomNavigationView 创建目标绑定并在 MainViewModel 中处理导航,而不是使用 Fluent Binding。在您的 XML 中使用瑞士绑定。

TargetBinding 类:

public class MvxBottomNavigationItemChangedBinding : MvxAndroidTargetBinding
    
        readonly BottomNavigationView _bottomNav;
        IMvxCommand _command;

        public override MvxBindingMode DefaultMode => MvxBindingMode.TwoWay;
        public override Type TargetType => typeof(MvxCommand);

        public MvxBottomNavigationItemChangedBinding(BottomNavigationView bottomNav) : base(bottomNav)
        
            _bottomNav = bottomNav;
            _bottomNav.NavigationItemSelected += OnNavigationItemSelected;
        

        public override void SetValue(object value)
        
            _command = (IMvxCommand)value;
        

        protected override void SetValueImpl(object target, object value)
        

        

        void OnNavigationItemSelected(object sender, BottomNavigationView.NavigationItemSelectedEventArgs e)
        
            if (_command != null)
                _command.Execute(e.Item.TitleCondensedFormatted.ToString());
        

        protected override void Dispose(bool isDisposing)
        
            if (isDisposing)
                _bottomNav.NavigationItemSelected -= OnNavigationItemSelected;

            base.Dispose(isDisposing);
        
    

Setup.cs:

protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
        
            MvxAppCompatSetupHelper.FillTargetFactories(registry);
            base.FillTargetFactories(registry);
            registry.RegisterCustomBindingFactory<BottomNavigationView>("BottomNavigationSelectedBindingKey",
                                                                        view => new MvxBottomNavigationItemChangedBinding(view));

        

BottomNavigationView XML:

注意我们在Setup.cs中添加的目标绑定键是绑定时使用的。

<com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/navigation"
            android:layout_
            android:layout_
            android:background="@color/white"
            app:labelVisibilityMode="labeled"
            app:menu="@menu/bottom_nav_menu"
            app:elevation="10dp"
            local:MvxBind="BottomNavigationSelectedBindingKey BottomNavigationItemSelectedCommand"/>

MainViewModel:

public class MainViewModel : BaseViewModel

public IMvxCommand<string> BottomNavigationItemSelectedCommand  get; private set; 

List<TabViewModel> _tabs;
public List<TabViewModel> Tabs

    get => _tabs;
    set => SetProperty(ref _tabs, value);


public MainViewModel(IMvxNavigationService navigationService) : base(navigationService)

    //these are for android - start
    BottomNavigationItemSelectedCommand = new MvxCommand<string>(BottomNavigationItemSelected);

    var tabs = new List<TabViewModel>
    
        Mvx.IoCProvider.IoCConstruct<FirstViewModel>(),
        Mvx.IoCProvider.IoCConstruct<SecondViewModel>(),
        Mvx.IoCProvider.IoCConstruct<ThirdViewModel>()
    ;

    Tabs = tabs;
    //end


// Android-only, not used on ios
private void BottomNavigationItemSelected(string tabId)

    if (tabId == null)
    
        return;
    

    foreach (var item in Tabs)
    
        if (tabId == item.TabId)
        
            _navigationService.Navigate(item);
            break;
        
    


TabViewModel:

public class TabViewModel : BaseViewModel
    
        public string TabName  get; protected set; 
        public string TabId  get; protected set; 

        public TabViewModel(IMvxNavigationService navigationService) : base(navigationService)
        
           
        
    

底部导航元素:

添加“android:titleCondensed”,将用作Id。

<?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/menu_home"
      android:enabled="true"
      android:icon="@drawable/ic_history"
      android:title="@string/tab1_title"
      android:titleCondensed ="tab_first"
      app:showAsAction="ifRoom" />

  <item
      android:id="@+id/menu_list"
      android:enabled="true"
      android:icon="@drawable/ic_list"
      android:title="@string/tab2_title"
      android:titleCondensed ="tab_second"
      app:showAsAction="ifRoom" />

  <item
      android:id="@+id/menu_settings"
      android:enabled="true"
      android:icon="@drawable/ic_settings"
      android:title="@string/tab3_title"
      android:titleCondensed ="tab_third"
      app:showAsAction="ifRoom" />
</menu>

ViewModel 示例:

public class FirstViewModel : TabViewModel

    public FirstViewModel(IMvxNavigationService navigationService) : base(navigationService)
        
            TabId = "tab_first";
        


public class SecondViewModel : TabViewModel

    public SecondViewModel(IMvxNavigationService navigationService) : base(navigationService)
        
            TabId = "tab_second";
        

希望这可以帮助以后遇到此问题的其他人! :)

【讨论】:

以上是关于如何通过 Xamarin MvvmCross 中的 BottomNavigationView 在视图模型之间导航的主要内容,如果未能解决你的问题,请参考以下文章

将 Xamarin.UITest 与 MvvmCross 绑定一起使用

如何将 Xamarin Forms Shell 集成到 MvvmCross 设置中

如何将 Xamarin Forms Shell 集成到 MvvmCross 设置中

如何将 MVVMCross 与 C# 的标记绑定

MvvmCross Xamarin Android 在初始屏幕上挂起并带有链接

MvvmCross 4 和 Xamarin.iOS -> 使用 Storyboard 时如何从 Core 加载视图控制器?