Is there tabbed layout platform provided by mvvmcross?

◇◆丶佛笑我妖孽 提交于 2020-01-16 17:03:55

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!