如何使用MvvmCross框架使用MonoDroid TabActivity

时间:2012-04-20 09:36:00

标签: xamarin.android mvvmcross

我正在尝试使用TabActivity和MvvmCross,但我在框架代码中得到NullReferenceException,因为传入OnViewCreate的viewModelLoader为null

namespace Cirrious.MvvmCross.ExtensionMethods
{
    public static class MvxViewExtensionMethods
    {
        public static void OnViewCreate<TViewModel>(this IMvxView<TViewModel> view, Func<TViewModel> viewModelLoader)
            where TViewModel : class, IMvxViewModel
        {
            if (view.ViewModel != null)
                return;

            var viewModel = viewModelLoader();
            viewModel.RegisterView(view);
            view.ViewModel = (TViewModel)viewModel;
        }

我怀疑这是因为我试图直接加载视图而不是通过ViewModel。我的TabHost活动中的代码如下所示:

[Activity(Label = "TabHost")]
    public class TabHostView : MvxBindingTabActivityView<TabHostViewModel>
    {
        protected override void OnViewModelSet()
        {
            SetContentView(Resource.Layout.Page_TabHostView);
            var tabHostWidget = this.TabHost;

            TabHost.TabSpec spec;     // Resusable TabSpec for each tab
            Intent intent;            // Reusable Intent for each tab
            // Create an Intent to launch an Activity for the tab (to be reused)
            intent = new Intent(this, typeof(HomeView));
            intent.AddFlags(ActivityFlags.NewTask);

            // Initialize a TabSpec for each tab and add it to the TabHost
            spec = tabHostWidget.NewTabSpec("home");
            spec.SetIndicator("Home", Resources.GetDrawable(Resource.Drawable.icon_home));
            spec.SetContent(intent);
            tabHostWidget.AddTab(spec);
//... more tabs

我如何解决这个问题?

此外,我的ViewModel也已设置好,以便TabHostViewModel具有每个标签页ViewModel的属性。这些是懒惰的,因为当调用属性的get访问器时,它们只从Model获取数据。

所以如果我的标签页中有数据绑定的axml布局,大概是路径必须假设TabHostViewModel是Context(root)?

非常感谢, 杰森

1 个答案:

答案 0 :(得分:6)

在一个微不足道的层面上,我认为您可以通过让框架为您创建意图来解决当前问题:

        TabHost.TabSpec spec;     // Resusable TabSpec for each tab
        Intent intent;            // Reusable Intent for each tab
        // Create an Intent to launch an Activity for the tab (to be reused)
        intent = base.CreateIntentFor<HomeViewModel>();
        intent.AddFlags(ActivityFlags.NewTask);

更完整的......


解决标签页的方法不止一种 - 无论是在Android还是在MvvmCross中。

在Android中,您选择通过使用直接包含每个单独选项卡内的视图的所有axml的布局来处理TabActivity。如果您使用此方法,那么我相信您可以直接使用数据绑定,就像“正常”一样 - 每个单独的选项卡应该像普通的子项Android Widget / View一样工作...我已经读过使用Android标签以这种方式工作有性能优势,但通常我不会这样工作。

其次 - 我通常的工作方式 - 您可以选择通过将每个Tab视为单独的Activity来处理TabActivity,并将每个Activity链接到主选项卡ViewModel的子ViewModel。 (我将尝试绘制这种结构的图片,并在今天晚些时候上传!)

如果你选择这样做,那么一个很好的例子就是会议一 - https://github.com/slodge/MvvmCross/blob/master/Sample%20-%20CirriousConference/Cirrious.Conference.UI.Droid/Views/HomeView.cs

此会议示例中发生的情况是,使用spec.SetContent(intent)初始化选项卡规范,其中使用选项卡活动基类方法CreateIntentFor创建意图 - 这是相关代码:

    protected override void OnViewModelSet()
    {
        SetContentView(Resource.Layout.Page_Home);

        TabHost.TabSpec spec;     // Resusable TabSpec for each tab
        Intent intent;            // Reusable Intent for each tab

        // Initialize a TabSpec for each tab and add it to the TabHost
        spec = TabHost.NewTabSpec("welcome");
        spec.SetIndicator(this.GetText("Welcome"), Resources.GetDrawable(Resource.Drawable.Tab_Welcome));
        spec.SetContent(CreateIntentFor(ViewModel.Welcome));
        TabHost.AddTab(spec);

        spec = TabHost.NewTabSpec("sessions");
        spec.SetIndicator(this.GetText("Sessions"), Resources.GetDrawable(Resource.Drawable.Tab_Sessions));
        spec.SetContent(CreateIntentFor(ViewModel.Sessions));
        TabHost.AddTab(spec);

        spec = TabHost.NewTabSpec("favorites");
        spec.SetIndicator(this.GetText("Favorites"), Resources.GetDrawable(Resource.Drawable.Tab_Favorites));
        spec.SetContent(CreateIntentFor(ViewModel.Favorites));
        TabHost.AddTab(spec);

        spec = TabHost.NewTabSpec("tweets");
        spec.SetIndicator(this.GetText("Tweets"), Resources.GetDrawable(Resource.Drawable.Tab_Tweets));
        spec.SetContent(CreateIntentFor(ViewModel.Twitter));
        TabHost.AddTab(spec);
    }

其中相应的顶级ViewModel有点像:

public class HomeViewModel
    : MvxBaseViewModel
{
    public HomeViewModel()
    {
        Welcome = new WelcomeViewModel();
        Sessions = new SessionsViewModel();            
        Twitter = new TwitterViewModel();
        Favorites = new FavoritesViewModel();
    }

    public FavoritesViewModel Favorites { get; private set; }
    public WelcomeViewModel Welcome { get; private set; }
    public SessionsViewModel Sessions { get; private set; }
    public TwitterViewModel Twitter { get; private set; }
}

...

作为第二种替代方案的变体(看起来这就是您在此问题中尝试做的事情),如果您的单个选项卡ViewModel与彼此或“父”TabHost ViewModel并不真正相关,然后,您可以将每个选项卡链接到按需创建的新ViewModel。我不认为任何公共样本都这样做,但如果你需要它,那么这个格式将是:

        spec = TabHost.NewTabSpec("newTab");
        spec.SetIndicator("new tab text"), Resources.GetDrawable(Resource.Drawable.Tab_Icon));
        spec.SetContent(CreateIntentFor<NewViewModel>(new { constructorParameter1 = "value1", constructorParameter2 = "value2" }));
        TabHost.AddTab(spec);

请注意,这里有一个潜在的哲学 - 它是MvvmCross的意见的一部分 - mvx中的导航/链接总是关于ViewModels,而不是关于Views。我们的想法是,应用程序首先构建ViewModel - 每个客户端平台只是决定如何在屏幕上表示这些ViewModel。这可能会继续并在iPad和WinRT开发中进一步发展,其中分割视图,弹出窗口等将是常见的。

还有一个注意事项......如果您有复杂的ViewModel构造或者您需要为某些ViewModel(例如单例)使用特殊生命周期,那么也可以在“核心应用程序”中使用自定义ViewModelLocators - 这些然后允许你改变/控制动态ViewModel创建,如果你想...但我怀疑这不是这个问题的情况。


感谢您提问中的详细信息 - 抱歉投入高级选项。我会尝试在今天晚些时候或本周末更好地解释这个答案。


Mvx仍然对创意持开放态度......所以我们可以修改代码以使用原始代码......这样做并不难,我可以看到一些理由......我我会考虑一下......