c# - 有 mvvmcross 提供的选项卡式布局平台吗?
问题描述
我正在尝试在 xamarin 应用程序中添加选项卡式布局。我用的是mvvmcross平台,但是好不容易找到mvvmcross为android和ios都提供的tabbed layout平台。如果 mvvmcross 中有任何平台或示例,请帮助我!谢谢!
解决方案
TR;博士;
在您的文档中,MvvmCross
您会在演示者中找到它(Xamarin.Android、Xamarin.iOS、Xamarin.Forms)
基本上,您必须使用属性来装饰您的视图以生成选项卡。
长示例(这些使用 Mvx 6)
从MvvmCross 存储库中的Playground 项目中提取的示例。
视图模型
您将有一个根选项卡ViewModel
,它将成为所有选项卡的容器,每个选项卡都有一个ViewModel
。
选项卡根(有两个根可以提供不同的方式来做同样的事情,并显示一个选项卡可以导航到另一个选项卡,你应该只使用一个)
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);
}
}
}
选项卡1
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; }
}
选项卡2
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 传统的
安卓
这里的键是MvxFragmentPresentation
确定它是一个片段并MvxTabLayoutPresentation
确定它将显示为一个选项卡的属性。显然,选项卡根有一个ViewPager
托管选项卡的页面。
首先,您需要一个视图来托管选项卡(在此示例中,视图也托管在 a 中,SplitDetailView
但您可以将其放在任何您想要的位置:
根选项卡
我假设您希望您的根选项卡成为一个片段,您也可以将它作为一个活动(如果需要,请查看 Playground 项目)。
[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();
}
}
}
它的 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>
选项卡 1
考虑在根选项卡视图中很好地引用TabLayoutResourceId
和命名。ViewPagerResourceId
[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;
}
}
它的 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>
选项卡 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;
}
}
它的 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
这里的关键是MvxRootPresentation
确定它是根并确定它是一个MvxTabBarViewController
它将承载选项卡的属性。除此之外MvxTabPresentation
,它确定它ViewController
是一个选项卡。
根标签
[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);
}
}
选项卡 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();
}
}
选项卡 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 表单
根选项卡
这里的主要内容是views:MvxTabbedPage
and MvxTabbedPagePresentation
,表示它将是一个承载标签的页面。
<?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();
}
}
选项卡 1
这里的主要内容是MvxTabbedPagePresentation
表明它将是一个选项卡。
<?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();
}
}
选项卡 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();
}
}
也许示例不够完整,因此您可以检查MvvmCross 存储库的Playground 项目以查看它是否完整。
HIH
推荐阅读
- java - how to get new action command in a while loop
- git - 使用 git 通过正则表达式提交微不足道的更改
- elixir - 嵌套函数定义
- node.js - NodeJS上的预热请求导致损坏?
- sockets - 通过套接字发送文件:SendText() 和 SendStream() 未正确发送数据
- c# - 如何使用restsharp web rest api
- python - 可以关闭背景地图数据的自动下载吗?
- r - 将多列的最后一个非 NA 日期结转到一列
- typescript - 如何使用严格的 args 类型正确覆盖 Express 方法?
- python - 在接受/完成信号上从向导/对话框中检索值的 Qt 规范方法