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

5
假设我们有一个MvvmCross 6.0.1本地应用程序,其中包含一个Android Activity,其中包含BottomNavigationView,实现方式如James Montemagno的博客文章所示,但没有导航和替换片段。
我想要做的是将BottomNavigationView项目绑定到ViewModel中的MvxCommands(或MvxAsyncCommands),以便在多个ViewModel之间导航。
我应该应用什么样的架构来实现这一点?我的方法正确吗?还是我违反了MVVM模式和MvvmCross的可能性?
可以在github上找到完整的工作示例

animated gif showing current progress

目前我已经使用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>());

你好,我不确定你的问题是什么。 - Robbit
我想请您对我的方法进行一些评论 - 看起来有太多的代码来执行简单的导航。似乎违反了一些基本规则,而且很难维护。如果您能分享一些更好的方法,我将不胜感激。 - Dominik Roszkowski
1个回答

0

不必使用流畅绑定,而是可以为BottomNavigationView创建一个定向绑定,并在MainViewModel中处理导航。在XML中使用Swiss绑定。

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_width="match_parent"
            android:layout_height="wrap_content"
            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";
        }
}

希望这能帮助到后来遇到同样问题的人!:)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接