TabLayout 中的片段不绑定到视图模型

Fragments in TabLayout do not bind to viewmodel

我已经为这个问题苦苦挣扎了 2 天,我完全被困住了。由于某种原因,片段的绑定根本不会启动。页面显示正确,选项卡工作正常。我可以从选项卡 1 滑动到 2,反之亦然。 TextView 应该显示来自视图模型的一些文本。当我调试时,两个片段视图模型的构造函数都被执行,但 LoginNotificationViewModel 上的属性(LoginDescription 和 LastLoginRequestReceivedOn)永远不会触发。

我有一个包含 TabLayout 的视图,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <include
        layout="@layout/header" />
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:id="@+id/main_content"
        android:layout_below="@id/header">
        <android.support.design.widget.TabLayout
            android:id="@+id/sliding_tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            local:tabMode="fixed" />
        <android.support.v4.view.ViewPager
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="0px"
            android:layout_weight="1"
            android:background="@android:color/white" />
    </LinearLayout>
</RelativeLayout>

标签视图的代码:

using Android.App;
using Android.OS;
using Android.Runtime;
using Android.Support.Design.Widget;
using Android.Support.V4.View;
using Android.Support.V7.Widget;
using Android.Views;
using MvvmCross.Droid.Support.V4;
using Notifier.Adapters;
using Notifier.Android.Fragments;
using Notifier.Classes;
using Notifier.ViewModels;
using System;
using static MvvmCross.Droid.Support.V4.MvxCachingFragmentStatePagerAdapter;

namespace Notifier.Android.Views
{
    [Activity(Label = "HomeNotification", Theme = "@style/Theme.NatuurNetwerk.Main", NoHistory = true)]
    public class HomeNotificationView : MvxFragmentActivity<HomeNotificationViewModel>
    {
        private HomeNotificationViewModel _HomeNotificationViewModel;

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

            _HomeNotificationViewModel = this.ViewModel;

            SetContentView(Resource.Layout.HomeNotificationView);

            var fragments = new FragmentInfo[]
            {
                new FragmentInfo("Login", typeof(LoginNotificationFragment), typeof(LoginNotificationViewModel)),
                new FragmentInfo("Tekst", typeof(TextNotificationFragment), typeof(TextNotificationViewModel)),
            };

            var viewPager = FindViewById<ViewPager>(Resource.Id.viewpager);
            viewPager.Adapter = new TabsFragmentPagerAdapter(this, SupportFragmentManager, fragments);

            // Give the TabLayout to the ViewPager
            var tabLayout = FindViewById<TabLayout>(Resource.Id.sliding_tabs);
            tabLayout.SetupWithViewPager(viewPager, true);
        }

        public override bool OnCreateOptionsMenu(IMenu menu)
        {
            SetContentView(Resource.Layout.HomeNotificationView);

            menu.Add(Menu.None, (int)Parameters.NotificationMenuItems.ResetPincode, 0, Resource.String.menuResetPincode);
            menu.Add(Menu.None, (int)Parameters.NotificationMenuItems.AddApplication, 0, Resource.String.menuAddApplication);
            menu.Add(Menu.None, (int)Parameters.NotificationMenuItems.RemoveApplication, 0, Resource.String.menuRemoveApplication);

            return base.OnCreateOptionsMenu(menu);
        }

        public override bool OnOptionsItemSelected(IMenuItem item)
        {
            Parameters.NotificationMenuItems menuItemSelected = item.ItemId.GetEnum<Parameters.NotificationMenuItems>();

            switch (menuItemSelected)
            {
                case Parameters.NotificationMenuItems.ResetPincode:
                    _HomeNotificationViewModel.ResetPincode();
                    break;
                case Parameters.NotificationMenuItems.AddApplication:
                    break;
                case Parameters.NotificationMenuItems.RemoveApplication:
                    break;

                default:
                    throw new InvalidOperationException(string.Format("Notifier: Invalid menu {0}", menuItemSelected.ToString()));
            }

            return base.OnOptionsItemSelected(item);
        }

        protected override void OnResume()
        {
            base.OnResume();

            Toolbar toolbar = FindViewById(Resource.Id.toolbar) as Toolbar;

            if (toolbar != null)
            {
                toolbar.Title = _HomeNotificationViewModel.Title;
                toolbar.Subtitle = _HomeNotificationViewModel.SubTitle;
            }
        }
    }
}

.. 它是视图模型:

namespace Notifier.ViewModels
{
    public class HomeNotificationViewModel : ViewModelBase
    {
        public override string SubTitle => "Notificatie";

        public override void InitView()
        {
        }

        public override void InitData()
        {
            // TODO: Init Data
        }

        public override void ApplicationSelected()
        {
        }

        public void ResetPincode()
        {
            DeviceRegistrationHelper.UpdatePincode(null, () =>
            {
                DetermineAndStartHomeView();
            });
        }
    }
}

涉及的片段之一:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  <TextView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_margin="30dp"
      local:MvxBind="Text LoginDescription" />
  <TextView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_margin="30dp"
      local:MvxBind="Text LastLoginRequestReceivedOn"/>
</LinearLayout>

登录片段代码:

using Android.OS;
using Android.Views;
using Notifier.ViewModels;

namespace Notifier.Android.Fragments
{
    public class LoginNotificationFragment : BaseFragment<LoginNotificationViewModel>
    {
        protected override int LayoutResource => Resource.Layout.LoginNotificationFragment;

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            return inflater.Inflate(Resource.Layout.LoginNotificationFragment, container, false);
        }
    }
}

.. 及其基数 class:

using MvvmCross.Droid.Support.V4;
using Notifier.Android.Views;
using Notifier.ViewModels;

namespace Notifier.Android.Fragments
{
    public abstract class BaseFragment<TViewModel> : MvxFragment<TViewModel>
        where TViewModel : ViewModelBase
    {
        protected abstract int LayoutResource { get; }
    }
}

视图模型:

using Notifier.Classes;
using System;
using System.Linq;

namespace Notifier.ViewModels
{
    public class LoginNotificationViewModel : ViewModelBase
    {
        public override string SubTitle => "Inlog notificatie";

        public String Status { get; set; }

        public override void InitView()
        {
        }

        public override void InitData()
        {
            // TODO: Init Data
        }

        public override void ApplicationSelected()
        {
        }

        public string LoginDescription
        {
            get => Settings.HasNotification 
                ? string.Format("Er is een inlog verzoek ontvangen voor {0}", ApplicationShortName(Settings.Notification.ApplicationId))
                : "Geen openstaande inlogverzoeken";
        }

        /// <summary>
        /// Short name for the application
        /// </summary>
        private string ApplicationShortName(int applicationId)
        {
            return Applications.Where(app => app.ApplicationID == applicationId).FirstOrDefault()?.ApplicationEntity.ApplicationShortName;
        }

        public string LastLoginRequestReceivedOn
        {
            get => string.Format("Laatste inlogverzoek: {0}", 
                Settings.HasLastNotificationReceivedOn 
                ? string.Empty
                : Settings.LastNotificationReceivedOn.ToString("DD-MM-YYYY HH:MM"));
        }
    }
}

和(部分)基础 class:

namespace Notifier.ViewModels
{
    /// <summary>
    /// Base class for all view models.
    /// </summary>
    public abstract class ViewModelBase : MvxViewModel
    {
        private readonly IPlatformEntrance _platformEntrance;
        private readonly IPlatformLog _platformLog;

        public string Title => "nNotifier©";

        public ViewModelBase()
        {
            _platformEntrance = Mvx.Resolve<IPlatformEntrance>();
            _platformLog = Mvx.Resolve<IPlatformLog>();

            InitData();
        }

        public abstract void InitData();

        public abstract string SubTitle { get; }

        ...
    }
}

这是 PageAdapter:

using Android.Content;
using Android.Support.V4.App;
using MvvmCross.Droid.Support.V4;

namespace Notifier.Adapters

{
    public class TabsFragmentPagerAdapter : MvxCachingFragmentStatePagerAdapter
    {
        FragmentInfo[] _fragments;

        public TabsFragmentPagerAdapter(Context context, FragmentManager fragmentManager, FragmentInfo[] fragments)
            : base(context, fragmentManager, fragments)
        {
            _fragments = fragments;
        }

        // public override int Count => _fragments.Length;

        //public override global::Android.Support.V4.App.Fragment GetItem(int position, 
        //    global::Android.Support.V4.App.Fragment.SavedState fragmentSavedState = null)
        //{
        //    return _fragments[position];
        //}

        //public override ICharSequence GetPageTitleFormatted(int position)
        //{
        //    return _titles[position];
        //}
    }
}

您需要使用 BindingInflate() 而不是默认的 Android inflater,因为它不知道如何处理 MvxBind 属性。

using MvvmCross.Binding.Droid.BindingContext;

...

public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    var ignored = base.OnCreateView(inflater, container, savedInstanceState);

    var view = this.BindingInflate(Resource.Layout.MyFragmentLayout, container, false);

    return view;
}