问题
I'm trying to add tabbed layout in xamarin app. I use mvvmcross platform, but it is not easy to find the tabbed layout platform provided by mvvmcross for both android and ios. If there is any platform or example in mvvmcross, pls help me! thanks!
回答1:
TR;DR;
In the docs of MvvmCross
you'll find it in the presenters (Xamarin.Android, Xamarin.iOS, Xamarin.Forms)
Basically, you'll have to decore with attributes your views to generate the tabs.
Long examples (these are using Mvx 6)
Examples extracted from the Playground project in the MvvmCross repository.
ViewModels
You'll have a root tabs ViewModel
that will be the container of all of the tabs which will have a ViewModel
each.
Tabs root (there are two roots to provide different ways to do the same thing and to show that one tab can navigate to another one, you should only use one)
public class TabsRootViewModel : MvxNavigationViewModel
{
public TabsRootViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService)
{
ShowInitialViewModelsCommand = new MvxAsyncCommand(ShowInitialViewModels);
ShowTabsRootBCommand = new MvxAsyncCommand(async () => await NavigationService.Navigate<TabsRootBViewModel>());
}
public IMvxAsyncCommand ShowInitialViewModelsCommand { get; private set; }
public IMvxAsyncCommand ShowTabsRootBCommand { get; private set; }
private async Task ShowInitialViewModels()
{
var tasks = new List<Task>();
tasks.Add(NavigationService.Navigate<Tab1ViewModel, string>("test"));
tasks.Add(NavigationService.Navigate<Tab2ViewModel>());
tasks.Add(NavigationService.Navigate<Tab3ViewModel>());
await Task.WhenAll(tasks);
}
private int _itemIndex;
public int ItemIndex
{
get { return _itemIndex; }
set
{
if (_itemIndex == value) return;
_itemIndex = value;
Log.Trace("Tab item changed to {0}", _itemIndex.ToString());
RaisePropertyChanged(() => ItemIndex);
}
}
}
public class TabsRootBViewModel : MvxNavigationViewModel
{
public TabsRootBViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService)
{
ShowInitialViewModelsCommand = new MvxAsyncCommand(ShowInitialViewModels);
}
public IMvxAsyncCommand ShowInitialViewModelsCommand { get; private set; }
private async Task ShowInitialViewModels()
{
var tasks = new List<Task>();
tasks.Add(NavigationService.Navigate<Tab1ViewModel, string>("test"));
tasks.Add(NavigationService.Navigate<Tab2ViewModel>());
await Task.WhenAll(tasks);
}
private int _itemIndex;
public int ItemIndex
{
get { return _itemIndex; }
set
{
if (_itemIndex == value) return;
_itemIndex = value;
Log.Trace("Tab item changed to {0}", _itemIndex.ToString());
RaisePropertyChanged(() => ItemIndex);
}
}
}
Tab1
public class Tab1ViewModel : MvxNavigationViewModel<string>
{
public Tab1ViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService)
{
OpenChildCommand = new MvxAsyncCommand(async () => await NavigationService.Navigate<ChildViewModel>());
OpenModalCommand = new MvxAsyncCommand(async () => await NavigationService.Navigate<ModalViewModel>());
OpenNavModalCommand = new MvxAsyncCommand(async () => await NavigationService.Navigate<ModalNavViewModel>());
CloseCommand = new MvxAsyncCommand(async () => await NavigationService.Close(this));
OpenTab2Command = new MvxAsyncCommand(async () => await NavigationService.ChangePresentation(new MvxPagePresentationHint(typeof(Tab2ViewModel))));
}
public override async Task Initialize()
{
await Task.Delay(3000);
}
string para;
public override void Prepare(string parameter)
{
para = parameter;
}
public override void ViewAppeared()
{
base.ViewAppeared();
}
public IMvxAsyncCommand OpenChildCommand { get; private set; }
public IMvxAsyncCommand OpenModalCommand { get; private set; }
public IMvxAsyncCommand OpenNavModalCommand { get; private set; }
public IMvxAsyncCommand OpenTab2Command { get; private set; }
public IMvxAsyncCommand CloseCommand { get; private set; }
}
Tab2
public class Tab2ViewModel : MvxNavigationViewModel
{
public Tab2ViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService)
{
ShowRootViewModelCommand = new MvxAsyncCommand(async () => await NavigationService.Navigate<RootViewModel>());
CloseViewModelCommand = new MvxAsyncCommand(async () => await NavigationService.Close(this));
}
public IMvxAsyncCommand ShowRootViewModelCommand { get; private set; }
public IMvxAsyncCommand CloseViewModelCommand { get; private set; }
}
Xamarin Traditional
Android
The keys here are the attributes MvxFragmentPresentation
that determines that it is a fragment and MvxTabLayoutPresentation
that determines that it will be presented as a tab. Obviously the tabs root has a ViewPager
to host the pages of the tabs.
First you'll need a view to host the tabs (in this example the view is also hosted in a SplitDetailView
but you can put it whereever you want:
Root tab
I assume you want your root tab a fragment, you can have it as an activity as well (look in the Playground project if you want that).
[MvxFragmentPresentation(fragmentHostViewType: typeof(SplitDetailView), fragmentContentId: Resource.Id.tabs_frame, addToBackStack: true)]
[Register(nameof(TabsRootBView))]
public class TabsRootBView : MvxFragment<TabsRootBViewModel>
{
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
var view = this.BindingInflate(Resource.Layout.TabsRootBView, null);
return view;
}
public override void OnViewCreated(View view, Bundle savedInstanceState)
{
base.OnViewCreated(view, savedInstanceState);
if (savedInstanceState == null)
{
ViewModel.ShowInitialViewModelsCommand.Execute();
}
}
}
Its axml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimaryDark"
local:popupTheme="@style/ThemeOverlay.AppCompat.Light"
local:layout_scrollFlags="scroll|enterAlways" />
<android.support.design.widget.TabLayout android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimaryDark"
android:paddingLeft="16dp"
local:tabGravity="center"
local:tabMode="scrollable" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
local:layout_behavior="@string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
Tab 1
Take into consideration well referencing the TabLayoutResourceId
and ViewPagerResourceId
as named in the root tabs view.
[MvxTabLayoutPresentation(TabLayoutResourceId = Resource.Id.tabs, ViewPagerResourceId = Resource.Id.viewpager, Title = "Tab 1", FragmentHostViewType = typeof(TabsRootBView))]
[Register(nameof(Tab1View))]
public class Tab1View : MvxFragment<Tab1ViewModel>
{
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your fragment here
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
var view = this.BindingInflate(Resource.Layout.Tab1View, null);
return view;
}
}
Its axml:
<?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:id="@+id/main_frame"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Show Child"
local:MvxBind="Click OpenChildCommand;" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Show Tab 2"
local:MvxBind="Click OpenTab2Command;" />
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Tab 2
[MvxTabLayoutPresentation(TabLayoutResourceId = Resource.Id.tabs, ViewPagerResourceId = Resource.Id.viewpager, Title = "Tab 2", FragmentHostViewType = typeof(TabsRootBView))]
[Register(nameof(Tab2View))]
public class Tab2View : MvxFragment<Tab2ViewModel>
{
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your fragment here
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
var view = this.BindingInflate(Resource.Layout.Tab2View, null);
return view;
}
}
Its axml:
<?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:id="@+id/main_frame"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Close tab"
local:MvxBind="Click CloseViewModelCommand;" />
</LinearLayout>
iOS
The key here are the attributes MvxRootPresentation
that deterines that it's the root and giving that it is an MvxTabBarViewController
it will host tabs. Besides this MvxTabPresentation
determines that it the ViewController
is a tab.
Root tabs
[MvxFromStoryboard("Main")]
[MvxRootPresentation(WrapInNavigationController = true)]
public partial class TabsRootView : MvxTabBarViewController<TabsRootViewModel>
{
private bool _isPresentedFirstTime = true;
public TabsRootView(IntPtr handle) : base(handle)
{
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (ViewModel != null && _isPresentedFirstTime)
{
_isPresentedFirstTime = false;
ViewModel.ShowInitialViewModelsCommand.ExecuteAsync(null);
}
}
protected override void SetTitleAndTabBarItem(UIViewController viewController, MvxTabPresentationAttribute attribute)
{
// you can override this method to set title or iconName
if (string.IsNullOrEmpty(attribute.TabName))
attribute.TabName = "Tab 2";
if (string.IsNullOrEmpty(attribute.TabIconName))
attribute.TabIconName = "ic_tabbar_menu";
base.SetTitleAndTabBarItem(viewController, attribute);
}
public override bool ShowChildView(UIViewController viewController)
{
var type = viewController.GetType();
return type == typeof(ChildView)
? false
: base.ShowChildView(viewController);
}
public override bool CloseChildViewModel(IMvxViewModel viewModel)
{
var type = viewModel.GetType();
return type == typeof(ChildViewModel)
? false
: base.CloseChildViewModel(viewModel);
}
}
Tab 1
[MvxFromStoryboard("Main")]
[MvxTabPresentation(WrapInNavigationController = true, TabIconName = "home", TabName = "Tab 1")]
public partial class Tab1View : MvxViewController<Tab1ViewModel>
{
public Tab1View(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var set = this.CreateBindingSet<Tab1View, Tab1ViewModel>();
set.Bind(btnModal).To(vm => vm.OpenModalCommand);
set.Bind(btnNavModal).To(vm => vm.OpenNavModalCommand);
set.Bind(btnChild).To(vm => vm.OpenChildCommand);
set.Bind(btnTab2).To(vm => vm.OpenTab2Command);
set.Apply();
}
}
Tab 2
[MvxFromStoryboard("Main")]
[MvxTabPresentation]
public partial class Tab2View : MvxViewController<Tab2ViewModel>
{
public Tab2View(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var set = this.CreateBindingSet<Tab2View, Tab2ViewModel>();
set.Bind(btnShowStack).To(vm => vm.ShowRootViewModelCommand);
set.Bind(btnClose).To(vm => vm.CloseViewModelCommand);
set.Apply();
}
}
Xamarin Forms
Root tab
The main things here are the views:MvxTabbedPage
and MvxTabbedPagePresentation
to indicate that it'll be a page that will host tabs.
<?xml version="1.0" encoding="UTF-8"?>
<views:MvxTabbedPage x:TypeArguments="viewModels:TabsRootViewModel"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:mvx="clr-namespace:MvvmCross.Forms.Bindings;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:Playground.Core.ViewModels;assembly=Playground.Core"
x:Class="Playground.Forms.UI.Pages.TabsRootPage" Title="TabsRoot page">
<ContentPage.Content>
</ContentPage.Content>
</views:MvxTabbedPage>
[MvxTabbedPagePresentation(TabbedPosition.Root, NoHistory = true)]
public partial class TabsRootPage : MvxTabbedPage<TabsRootViewModel>
{
public TabsRootPage()
{
InitializeComponent();
}
private bool _firstTime = true;
protected override void OnAppearing()
{
base.OnAppearing();
if (_firstTime)
{
//ViewModel.ShowInitialViewModelsCommand.Execute();
ViewModel.ShowInitialViewModelsCommand.ExecuteAsync(null);
_firstTime = false;
}
}
protected override void OnViewModelSet()
{
base.OnViewModelSet();
}
}
Tab 1
The main thing here is MvxTabbedPagePresentation
that indicates that it will be a a tab.
<?xml version="1.0" encoding="UTF-8"?>
<views:MvxContentPage x:TypeArguments="viewModels:Tab1ViewModel"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:mvx="clr-namespace:MvvmCross.Forms.Bindings;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:Playground.Core.ViewModels;assembly=Playground.Core"
x:Class="Playground.Forms.UI.Pages.Tab1Page" Title="Tab1 page">
<ContentPage.Content>
<StackLayout Margin="10">
<Label Text="I'm a tab page" />
<Button Text="Show Child" mvx:Bi.nd="Command OpenChildCommand"/>
<Button Text="Show Modal" mvx:Bi.nd="Command OpenModalCommand"/>
</StackLayout>
</ContentPage.Content>
</views:MvxContentPage>
[MvxTabbedPagePresentation(WrapInNavigationPage = false, Title = "Tab1")]
public partial class Tab1Page : MvxContentPage<Tab1ViewModel>
{
public Tab1Page()
{
InitializeComponent();
}
}
Tab 2
<?xml version="1.0" encoding="UTF-8"?>
<views:MvxContentPage x:TypeArguments="viewModels:Tab2ViewModel"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:mvx="clr-namespace:MvvmCross.Forms.Bindings;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:Playground.Core.ViewModels;assembly=Playground.Core"
x:Class="Playground.Forms.UI.Pages.Tab2Page" Title="Tab2 page">
<ContentPage.Content>
<StackLayout Margin="10">
<Label Text="I'm a tab page" />
<Button Text="Close tab" mvx:Bi.nd="Command CloseViewModelCommand"/>
</StackLayout>
</ContentPage.Content>
</views:MvxContentPage>
[MvxTabbedPagePresentation(WrapInNavigationPage = false)]
public partial class Tab2Page : MvxContentPage<Tab2ViewModel>
{
public Tab2Page()
{
InitializeComponent();
}
}
Maybe the example is not full enough so you can check the Playground project of the MvvmCross repository to see it complete.
HIH
来源:https://stackoverflow.com/questions/53568720/is-there-tabbed-layout-platform-provided-by-mvvmcross