带Backstack的MVVMCross选项卡导航

时间:2018-04-23 05:56:13

标签: android tabs mvvmcross

目前有没有办法在MVVMCross中进行选项卡式导航,例如YouTube应用程序,每个选项卡都有一个子导航,或者这是必须由我自己实现的东西?那怎么会这么干净呢?我应该调整演示者吗?

一个例子: 我有三个选项卡,我可以从一个选项卡导航到另一个选项卡。如果我在第一个选项卡中选择一个元素,它会导航到一个新的ViewModel并替换第一个选项卡,而我仍然可以切换到其他选项卡。如果我在第一个选项卡中导航回来,我会回到旧的第一个标签页。

1 个答案:

答案 0 :(得分:1)

解决方案架构:

Activity  
  TabLayout  
  ViewPager  
     RootFragment (1 per tab, created/deleted by the viewpager depdending of the selected tab)
         |-- ChildFragmentManager of the RootFragment (= independant navigation stack)
         | Tab1Fragment1  
         | Tab1Fragment2  
         | Tab1Fragment3  
  • 主视图是一个mvvmcross活动“ HomeContainerActivity”
  • 对于选项卡按钮,我使用了TabLayout。
  • 内容是链接到布局的ViewPager。选择选项卡时,查看器会切换显示的片段。
  • ViewPager的适配器提供了自定义的MvxCachingFragmentPagerAdapter,该实例化RootTabFragment而不是指定的片段MvxViewPagerFragmentInfo并使用将作为堆栈根的片段进行初始化
  • RootTabFragment的布局只有一个内容放置在上面的片段中。

仅此而已。导航到视图模型会在当前顶部片段中将其打开。这意味着标准导航将按预期工作,但是后退按钮在您的主要活动中需要一些代码。同样,如果没有自定义视图演示器,也无法通过代码更改选项卡。

主要活动布局:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/chrome"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    >

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

    <!-- fragment content for popups -->
    <FrameLayout
        android:id="@+id/contentPopup"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:elevation="5dp"/>

    <!-- action/tool bar -->
    <!-- must be below all contents -->
    <include layout="@layout/activity_home_toolbar" />

    <!-- tab bar -->
    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:animateLayoutChanges="true"
        android:layout_gravity="bottom"
        >
            <android.support.design.widget.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
                app:tabIndicator="@null"
                />
    </android.support.design.widget.AppBarLayout>

</android.support.design.widget.CoordinatorLayout>

使用TabLayout的ViewPager设置:

            var fragments = new List<MvxViewPagerFragmentInfo>
            {
                new MvxViewPagerFragmentInfo("Tab1", typeof(Tab1Fragment).FragmentJavaName(), typeof(Tab1Fragment), typeof(Tab1ViewModel)),
                new MvxViewPagerFragmentInfo("Tab2", typeof(Tab2Fragment).FragmentJavaName(), typeof(Tab2Fragment), typeof(Tab2ViewModel)),
            };

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

            tabLayout = FindViewById<TabLayout>(Resource.Id.tabs);
            tabLayout.SetupWithViewPager(viewPager);

自定义片段寻呼机适配器:

    [Register("vapolia.MvxCachingFragmentStatePagerAdapter2")]
    public class MvxCachingFragmentStatePagerAdapter2 : MvxCachingFragmentPagerAdapter
    {
        private readonly Context context;

        [Preserve(Conditional=true)]
        protected MvxCachingFragmentStatePagerAdapter2(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) {}

        public MvxCachingFragmentStatePagerAdapter2(Context context, FragmentManager fragmentManager, List<MvxViewPagerFragmentInfo> fragmentsInfo) : base(fragmentManager)
        {
            this.context = context;
            FragmentsInfo = fragmentsInfo;
        }

        public override int Count => FragmentsInfo?.Count() ?? 0;
        public List<MvxViewPagerFragmentInfo> FragmentsInfo { get; }

        public override Fragment GetItem(int position, Fragment.SavedState fragmentSavedState = null)
        {
            var fragment = (RootTabFragment)Fragment.Instantiate(context, typeof(RootTabFragment).FragmentJavaName());
            fragment.FragmentInfo = FragmentsInfo[position];
            return fragment;
        }

        public override int GetItemPosition(Java.Lang.Object @object) => PagerAdapter.PositionNone;
        public override ICharSequence GetPageTitleFormatted(int position) => new Java.Lang.String(FragmentsInfo.ElementAt(position).Title);
        protected override string GetTag(int position) => FragmentsInfo[position].Tag;
        public override void DestroyItem(ViewGroup container, int position, Object objectValue) {} //Disable destroy
    }

根标签片段:

/// <summary>
    /// Special fragment used as the root content for every tab items.
    /// This enables a specific navigation stack for each tab item (using ChildFragmentManager).
    /// 
    /// Contains only one subfragment
    /// </summary>
    public class RootTabFragment : Fragment
    {
        public MvxViewPagerFragmentInfo FragmentInfo { get; set; }

        [Android.Runtime.Preserve(Conditional=true)]
        protected RootTabFragment(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) {}

        [Android.Runtime.Preserve(Conditional=true)]
        public RootTabFragment()
        {
        }

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

        public override void OnViewCreated(View view, Bundle savedInstanceState)
        {
            base.OnViewCreated(view, savedInstanceState);
            var fm = ChildFragmentManager;

            var isCacheable = FragmentInfo.FragmentType.IsFragmentCacheable(Mvx.IoCProvider.Resolve<IMvxAndroidCurrentTopActivity>().Activity.GetType());
            var firstFragment = fm.FindFragmentByTag(FragmentInfo.Tag) ?? Fragment.Instantiate(Context, FragmentInfo.FragmentType.FragmentJavaName());

            // ReSharper disable once SuspiciousTypeConversion.Global
            if (firstFragment is IMvxFragmentView mvxFragment)
            {
                if (savedInstanceState == null || !isCacheable)
                {
                    var viewModel = FragmentInfo.ViewModel ?? CreateViewModel();
                    mvxFragment.ViewModel = viewModel;
                }
            }

            var ft = fm.BeginTransaction();
            ft.SetReorderingAllowed(true);
            ft.Replace(Resource.Id.content, firstFragment, FragmentInfo.Tag);
            ft.CommitAllowingStateLoss();
        }

        private IMvxViewModel CreateViewModel()
        {
            MvxBundle mvxBundle = null;
            if (FragmentInfo.ParameterValuesObject != null)
                mvxBundle = new MvxBundle(FragmentInfo.ParameterValuesObject.ToSimplePropertyDictionary());
            var request = new MvxViewModelRequest(FragmentInfo.ViewModelType, mvxBundle, null);

            return Mvx.IoCProvider.Resolve<IMvxViewModelLoader>().LoadViewModel(request, null);
        }
    }

根标签页布局:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="true"
    >

    <!-- fragment content for navigation -->
    <FrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    </FrameLayout>

</FrameLayout>

将进入根标签片段的示例片段:

    [MvxFragmentPresentation(FragmentContentId = Resource.Id.content, AddToBackStack = false, ActivityHostViewModelType = typeof(HomeContainerActivity), IsCacheableFragment = true)]
    public class SettingsFragment : BaseFragment<SettingsViewModel>, IFragmentWithTitle
    {
        protected override int FragmentLayoutId => Resource.Layout.home_settings;
        public string TitleText => "Settings";
    }

在活动中受到压迫的固定问题:

        public override void OnBackPressed()
        {
            var n = SupportFragmentManager.BackStackEntryCount;
            if (n >= 1)
            {
                base.OnBackPressed();
            }
            else
            {
                //Try to use the current selected tab's backstack
                var currentFragment = (Fragment)viewPager.Adapter.InstantiateItem(null, viewPager.CurrentItem); //Returns the existing fragment
                var fm = currentFragment.ChildFragmentManager;
                n = fm.BackStackEntryCount;
                if (n >= 1)
                {
                    //Must use immediate, so SetCorrectTitle can use the updated backstack to set the correct title
                    fm.PopBackStackImmediate();

                    if (n == 1)
                        SupportActionBar.SetDisplayHomeAsUpEnabled(false); //Set to true to display back button

                    //SetCorrectTitle();
                }
                else
                {
                    base.OnBackPressed();
                }
            }
        }

希望有帮助。