【问题标题】:MvvmCross, Mvx.MvxListView and custom binding in MvxItemTemplateMvvmCross、Mvx.MvxListView 和 MvxItemTemplate 中的自定义绑定
【发布时间】:2016-04-13 23:48:02
【问题描述】:

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

我将简要介绍和描述我的问题,然后我将介绍技术细节。

简介

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

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

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

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

问题

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

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

如果我以编程方式强制滚动,当我打开页面时,我会看到一个奇怪的行为:所有消息都存在(经典绑定,如 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 并管理导航的stack

我拥有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的代码

<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

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

>>编辑1

我通过添加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;
}   

【问题讨论】:

  • 在你的视图模型中使用 observable 集合而不是 ienumerable
  • 嗨@xleon,我已经尝试过你的建议,但没有任何改变。请注意,如果我有一个少于 10 个项目的列表,那么我当前的代码一切正常
  • 它不是项目的数量,而是您的自定义绑定的问题,直到项目被刷新我认为。我使用自定义 mvxadapter 而不是自定义绑定。没有问题
  • 绑定设置后是否尝试了list.Invalidate()?
  • 感谢您写了一个好问题。我不相信您所看到的与“自定义绑定”有关。 Mvx 自定义绑定与内置绑定具有完全相同的状态。我猜你所看到的可能与 Android 控件本身的行为更相关。例如在您的问题示例中,我猜想 weight 在您滚动之前应用 - 但直到滚动 Android 布局,直到您的滚动导致它被重新计算或重新应用(或两者)。要测试这个...

标签: mvvmcross


【解决方案1】:

我要做的第一件事是确保您的自定义绑定总是被调用。 在SetValueImpl() 方法上设置一个断点并检查它是否被那些有问题的项目调用。如果发生这种情况,那么问题取决于视图因任何原因没有更新,您应该对此进行处理。如果它没有损坏,您肯定会知道这是 MvxAdapter 中的自定义绑定问题(可能是错误)。

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

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);
    }
}

然后,在你的 android 视图中:

var adapter = new ChatListAdapter(this, (IMvxAndroidBindingContext)BindingContext);
_chatList = FindViewById<MvxListView>(Resource.Id.chat_list_view);
_chatList.Adapter = adapter;

【讨论】:

    猜你喜欢
    • 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
    相关资源
    最近更新 更多