MvvmCross,Mvx.MvxListView和MvxItemTemplate

时间:2016-01-09 08:55:22

标签: mvvmcross

在MvvmCross应用程序中,我有一个具有经典聊天行为的页面(WhatsApp之类):此页面显示两个用户之间交换的消息的历史记录,列表底部的最后一条消息。 我已成功在Windows Phone 8.1中实现了该视图,但我正在努力解决Android中的问题。

我将简要介绍一下我的问题,然后我会详细介绍技术细节。

引言

实际上,我需要对不同用户发送的消息应用不同的样式:小心地对齐从其他用户发送的左消息并对齐我发送的正确消息(我通过weight属性执行此操作);我需要应用不同的drawable背景并设置不同的gravity属性。 我使用自定义绑定,因为,AFAIK,这些属性不能与经典绑定绑定:local:MvxBind="Gravity MyPropery"不起作用,因为没有重力属性。

所以,我当然有两个axml文件:

  • 第一个包含Mvx.MvxListView
  • 第二个包含MvxListView的项目模板

我按照这些指南创建了三种不同的自定义绑定(背景,重力和重量):

问题

我想要的是,当用户打开聊天视图时,列表小部件会自动显示最后一条消息。为此,我以编程方式将列表滚动到最后一条消息,似乎是问题所在。

如果我不以编程方式滚动,当我打开页面并手动滚动到页面末尾时,所有自定义绑定都会成功应用:我可以看到左右对齐的消息,并且应用了正确的背景和重量。 / p>

如果我以编程方式强制滚动,当我打开页面时,我看到一个奇怪的行为:所有消息都存在(经典绑定,例如Text属性,已成功应用),但缺少自定义绑定。所有消息都具有相同的背景,并且都保持对齐。 但是,如果我上下手动滚动,则会处理自定义绑定并以正确的方式显示消息。

调查分析

要调试行为,我在自定义绑定过程中放置​​了一个简单的静态计数器,以便在每次处理函数时进行跟踪。

public class LinearLayoutWeightTargetBinding : MvxAndroidTargetBinding
{
    public static int debugCounter = 0;
    public LinearLayoutWeightTargetBinding(object target) : base(target)
    {
    }

    protected LinearLayout MyTarget
    {
        get { return (LinearLayout)Target; }
    }

    public override Type TargetType { get { return typeof(bool); } }

    protected override void SetValueImpl(object target, object value)
    {
        var ll = (LinearLayout)target;
        var itsMe = (bool)value;
        var weight = itsMe ? (float)20.0 : (float)5.0;

        var layoutParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WrapContent, weight);
        ll.LayoutParameters = layoutParams;
        Log.Debug("MeeCHAT", string.Format("LinearLayoutWeightTargetBinding::SetValueImpl::ItsMe:{0} - counter:{1}", itsMe, ++debugCounter));
    }

    public override MvxBindingMode DefaultMode { get {return MvxBindingMode.TwoWay;} }
}

通过这种方式,我看到实际上通过上下滚动应用自定义绑定(debugCounter正确增加)。 但是当我以编程方式滚动时,自定义绑定处理前10个项目,这似乎是我看到没有正确样式的消息的原因。因为我有一个很长的列表,只处理前10个项目,但它们不可见(它们在可见区域之外),并且尚未处理可见项目。

技术细节

以下是与我的应用技术方面相关的一些细节。我试着给你所有重要的方面。

观点的组织 按照Greg Shackles在本文http://gregshackles.com/presenters-in-mvvmcross-navigating-android-with-fragments/中描述的方法,我只为应用程序提供了一个Activity,为每个Fragment提供了一个View;然后通过Presenter可以激活正确的ViewModel并管理导航的堆栈

我拥有Mvx.MvxListView小部件的视图片段是

public class MyMatchersChatView : MvxFragment
{
    public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        var ignore = base.OnCreateView(inflater, container, savedInstanceState);
        var result = this.BindingInflate(Resource.Layout.MyMatchersChatView, null);

        var headerFrame = result.FindViewById<FrameLayout>(Resource.Id.headerFrameMyMatchersChatView);
        var headerWidget = new HeaderWidget() { ViewModel = this.ViewModel };
        var tran = ChildFragmentManager.BeginTransaction();
        tran.Add(headerFrame.Id, headerWidget, "headerMyMatchersChat");
        tran.Commit();

        var listView = result.FindViewById<MvxListView>(Resource.Id.messagesList);
        listView.SetSelection(listView.Adapter.Count - 1); // Scroll to the end of the list
        return result;
    }        
}

语句listView.SetSelection(listView.Adapter.Count - 1);强制列表滚动到结尾。

最后两件事:如何注册自定义绑定以及如何在axml文件中应用。

注册自定义

在Setup.cs中我有:

protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
    base.FillTargetFactories(registry);
    registry.RegisterFactory(new MvxCustomBindingFactory<LinearLayout>("CustomWeight",
            (b) => new LinearLayoutWeightTargetBinding(b)));
}

应用自定义绑定

在我的axml中我有:

<LinearLayout 
  android:orientation="horizontal" 
  android:layout_width="0dp" 
  android:layout_height="wrap_content" 
  local:MvxBind="CustomWeight IsCurrentUser">

LISTVIEW和VIEWMODEL

这是ListView的代码

<Mvx.MvxListView
  android:id="@+id/messagesList"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  local:MvxBind="ItemsSource MyMessages"
  local:MvxItemTemplate="@layout/mymatcherschatview_itemtemplate" />

和ViewModel中的属性

    private IEnumerable<MyMatchMessageModel> _myMessages;
    public IEnumerable<MyMatchMessageModel> MyMessages
    {
        get { return _myMessages; }
        set
        {
            _myMessages = value;
            RaisePropertyChanged(() => MyMessages);
        }
    }

环境 最后,这是我的环境:

  • Visual Studio 2015
  • MvvmCross 3.5.1
  • 核心目标:.NET Framework 4.5,Windows 8,ASP.NET Core 5.0,Windows Phone 8.1,Xamarin.Android,Xamarin.iOS,Xamarin.iOS(经典)
  • Android应用目标是API级别19(Xamarin.Android v4.4支持)
  • Xamarin 3.11.1450.0
  • Xamarin.Android 5.1.6.7

如果我做错了什么,有人可以帮我理解吗? 感谢阅读和任何帮助!

&gt;&gt;编辑1&lt;&lt;

我通过添加stackFromBottomtranscriptMode属性并在Fragment中以编程方式删除滚动到下面来获取自动滚动行为来更改布局,但问题仍然存在:要查看具有正确样式的消息,我必须手动上下滚动(以激活自定义绑定)

这是新的axml ......

<Mvx.MvxListView
  android:id="@+id/messagesList"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:stackFromBottom="true"
  android:transcriptMode="alwaysScroll"
  local:MvxBind="ItemsSource MyMessages"
  local:MvxItemTemplate="@layout/mymatcherschatview_itemtemplate" />

...和Fragment中的新代码

public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
  var ignore = base.OnCreateView(inflater, container, savedInstanceState);
  var result = this.BindingInflate(Resource.Layout.MyMatchersChatView, null);

  var headerFrame = result.FindViewById<FrameLayout>(Resource.Id.headerFrameMyMatchersChatView);
  var headerWidget = new HeaderWidget() { ViewModel = this.ViewModel };
  var tran = ChildFragmentManager.BeginTransaction();
  tran.Add(headerFrame.Id, headerWidget, "headerMyMatchersChat");
  tran.Commit();

  return result;
}   

1 个答案:

答案 0 :(得分:0)

我要做的第一件事就是确保始终调用自定义绑定。 在ApplicationController方法上设置断点,并检查是否在这些有问题的项目上调用它。如果发生这种情况,那么问题依赖于视图没有因任何原因而更新,您应该继续这样做。如果它没有中断,你肯定会知道SetValueImpl()中的自定义绑定问题(可能是错误)。

如果你发现它是第二个。我建议您删除自定义绑定并创建自己的MvxAdapter,如下所示:

ChatListAdapter : MvxAdapter

然后,在你的android视图中:

public class CoolChatListAdapter : MvxAdapter
{
    public CoolChatListAdapter(Context context, IMvxAndroidBindingContext bindingContext) : base(context, bindingContext)
    {
    }

    protected override View GetBindableView(View convertView, object source, int templateId)
    {
        var item = source as MyMatchMessageModel;
        var weight = item.IsCurrentUser ? (float) 20.0 : (float) 5.0;

        var ll = (LinearLayout) convertView;
        var layoutParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WrapContent, weight);
        ll.LayoutParameters = layoutParams;

        return base.GetBindableView(convertView, source, templateId);
    }
}