如何通过 MvvmCross 在 TabLayout 中正确使用 Fragments 6.x

How to properly use Fragments inside TabLayout with MvvmCross 6.x

问题

我正在尝试使用 MvvmCross 6.1.2 使用 TabLayout 和 Fragments 进行非常简单的概念验证。为此,我实现了一个带有 TabLayout 和 ViewPager 的 activity,它应该有两个选项卡 - 每个选项卡包含一个不同的片段,只有一个 TextView。

但是我收到一个异常,然后在运行时崩溃,而这个 activity 应该显示:

The type MvxTabLayoutPresentationAttribute is not configured in the presenter dictionary

代码

这就是我的代码,我在 Playground example and the Documentation:

之后实现的

AppStart.cs:

public class AppStart : MvxAppStart
{
    private readonly IMvxNavigationService _mvxNavigationService;

    public AppStart(IMvxApplication app, IMvxNavigationService mvxNavigationService)
        : base(app, mvxNavigationService)
    {
        _mvxNavigationService = mvxNavigationService;
    }

    protected override void NavigateToFirstViewModel(object hint = null)
    {
        Mvx.Resolve<IMvxNavigationService>().Navigate<TabLayoutViewModel>();
    }
}

TabLayoutViewModel.cs

public class TabLayoutViewModel: MvxViewModel
{
    public override async Task Initialize()
    {
        await base.Initialize();

        var tasks = new List<Task>();
        tasks.Add(Mvx.Resolve<IMvxNavigationService>().Navigate<FragmentTab1ViewModel>());
        tasks.Add(Mvx.Resolve<IMvxNavigationService>().Navigate<FragmentTab2ViewModel>());
        await Task.WhenAll(tasks);
    }
}

FragmentTab1ViewModel.cs(FragmentTab2ViewModel.cs 同样)

public class FragmentTab1ViewModel : MvxViewModel
{
    public override Task Initialize()
    {
        return base.Initialize();
    }
}

TabLayoutViewController.cs

[MvxActivityPresentation]
[Activity(Label = "", ScreenOrientation = ScreenOrientation.Portrait, LaunchMode = LaunchMode.SingleTask, Theme = "@style/LoginTheme")]
public class TabLayoutViewController: MvxAppCompatActivity<TabLayoutViewModel>
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        SetContentView(Resource.Layout.TabLayoutView);

        var set = this.CreateBindingSet<TabLayoutViewController, TabLayoutViewModel>();

        set.Apply();
    }
}

TabLayoutView.axml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:alwaysDrawnWithCache="false"
    android:background="#f4f4f4"
    android:minWidth="25px"
    android:minHeight="25px">
  <android.support.design.widget.TabLayout
      android:id="@+id/tabsTeste"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:paddingLeft="16dp"
      app:tabGravity="center"
      app:tabMode="scrollable" />
  <android.support.v4.view.ViewPager
      android:id="@+id/viewpagerTeste"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />
</android.support.design.widget.CoordinatorLayout>

FragmentTab1ViewController.cs(FragmentTab2ViewController.cs 同样)

[MvxTabLayoutPresentation(ActivityHostViewModelType = typeof(TabLayoutViewModel), ViewPagerResourceId = Resource.Id.viewpagerTest, TabLayoutResourceId = Resource.Id.tabsTest, Title = "Tab A")]
[Register("smartSolution.coleta.droid.view.FragmentTab1ViewController")]
public class FragmentTab1ViewController : MvxFragment<FragmentTab1ViewModel>
{
    public override Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        base.OnCreateView(inflater, container, savedInstanceState);

        var view = this.BindingInflate(Resource.Layout.FragmentTab1View, null);

        inflater.Inflate(Resource.Layout.FragmentTab1View, container, true);

        var set = this.CreateBindingSet<FragmentTab1ViewController, FragmentTab1ViewModel>();

        set.Apply();

        return view;
    }
}

(FragmentTab1View.axml 和 FragmentTab2View.axml 只是带有 TextView 的 LinearLayouts)

问题

  1. 抛出异常的原因是什么?
  2. 这是用片段实现 TabLayout 的推荐方法吗?
  3. 按照 MvvmCross 6.x 好的做法,可以做些什么来解决这个问题?

抛出该异常是因为该属性未在 PresenterAttributeTypesToActionsDictionary 中注册。

在代码中,您可以在方法 RegisterAttributeTypes it's registered but take into account that it is in the MvxAppCompatViewPresenter. Furthermore in the docs 中看到该属性仅适用于 AppCompat

鉴于您收到该异常,我可以假设正在使用 non-AppCompat 演示者,因此您正在使用 MvxAndroidSetup.

要解决此问题,请确保您使用的是 AppCompat 类,特别是如果您的自定义设置 where the MvxAppCompatViewPresenter is set. Also make sure you are using MvxAppCompatApplication so if forces 继承自 MvxAppCompatSetup AppCompat 版本 Setup.


更新关于异常的评论MvvmCross.Exceptions.MvxException: ViewPager not found

我认为问题是您导航到 Initialize 中的 children 视图模型,而不是在创建选项卡视图后执行此操作,因此当您导航时 ViewPager 可能尚未初始化尝试导航至 children,因此未找到。

因此,在 Playground Viewmodel 中,您应该有一个命令调用一个方法来在您的 ViewModel 上进行导航:

...
ShowInitialViewModelsCommand = new MvxAsyncCommand(ShowInitialViewModels);
...

public IMvxAsyncCommand ShowInitialViewModelsCommand { get; private set; }

...

private async Task ShowInitialViewModels()
{
    var tasks = new List<Task>();
    tasks.Add(Mvx.Resolve<IMvxNavigationService>().Navigate<FragmentTab1ViewModel>());
    tasks.Add(Mvx.Resolve<IMvxNavigationService>().Navigate<FragmentTab2ViewModel>());
    await Task.WhenAll(tasks);
}

Playground View 中一样,您应该在 TabLayoutViewController 中调用命令:

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

    SetContentView(Resource.Layout.TabLayoutView);

    var set = this.CreateBindingSet<TabLayoutViewController, TabLayoutViewModel>();

    set.Apply();

    if (bundle == null)
    {
        ViewModel.ShowInitialViewModelsCommand.Execute();
    }
}