【问题标题】:Is there tabbed layout platform provided by mvvmcross?有 mvvmcross 提供的选项卡式布局平台吗?
【发布时间】:2018-12-03 21:49:26
【问题描述】:

我正在尝试在 xamarin 应用程序中添加选项卡式布局。 我用的是mvvmcross平台,但是好不容易找到mvvmcross为android和ios都提供的tabbed layout平台。如果mvvmcross有什么平台或者例子, 请帮助我!谢谢!

【问题讨论】:

  • 适用于 Xamarin.Forms 还是传统版?

标签: c# tabs mvvmcross


【解决方案1】:

TR;DR;

MvvmCross 的文档中,您可以在演示者中找到它(Xamarin.AndroidXamarin.iOSXamarin.Forms

基本上,您必须使用属性来装饰视图以生成选项卡。

长示例(这些使用 Mvx 6)

从 MvvmCross 存储库中的 Playground project 提取的示例。

 视图模型

您将拥有一个根标签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);
        }
    }
}

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 繁体

安卓

这里的键是属性MvxFragmentPresentation 确定它是一个片段,MvxTabLayoutPresentation 确定它将显示为一个选项卡。显然,标签根有一个ViewPager 来托管标签页。

首先,您需要一个视图来托管选项卡(在此示例中,该视图也托管在 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

考虑在根选项卡视图中很好地引用 TabLayoutResourceIdViewPagerResourceId

[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:MvxTabbedPageMvxTabbedPagePresentation,表示它将是一个包含标签的页面。

<?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 repositoryPlayground project 以查看它是否完整。

【讨论】:

  • 所以我使用它并获得标签很好,我没有看到项目索引的使用方式以及如何告诉标签页以编程方式更改项目索引,我觉得我在这里错过了一些东西,有什么想法吗?
  • @Barry 我假设您需要将 Observer 添加到与 TabsRootViewModel 对应的 ViewController - 并在那里更改它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多