目前有没有办法在MVVMCross中进行选项卡式导航,例如YouTube应用程序,每个选项卡都有一个子导航,或者这是必须由我自己实现的东西?那怎么会这么干净呢?我应该调整演示者吗?
一个例子: 我有三个选项卡,我可以从一个选项卡导航到另一个选项卡。如果我在第一个选项卡中选择一个元素,它会导航到一个新的ViewModel并替换第一个选项卡,而我仍然可以切换到其他选项卡。如果我在第一个选项卡中导航回来,我会回到旧的第一个标签页。
答案 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
仅此而已。导航到视图模型会在当前顶部片段中将其打开。这意味着标准导航将按预期工作,但是后退按钮在您的主要活动中需要一些代码。同样,如果没有自定义视图演示器,也无法通过代码更改选项卡。
主要活动布局:
<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();
}
}
}
希望有帮助。