在MvvmCross应用程序中,我有一个具有经典聊天行为的页面(WhatsApp之类):此页面显示两个用户之间交换的消息的历史记录,列表底部的最后一条消息。 我已成功在Windows Phone 8.1中实现了该视图,但我正在努力解决Android中的问题。
我将简要介绍一下我的问题,然后我会详细介绍技术细节。
实际上,我需要对不同用户发送的消息应用不同的样式:小心地对齐从其他用户发送的左消息并对齐我发送的正确消息(我通过weight
属性执行此操作);我需要应用不同的drawable
背景并设置不同的gravity
属性。
我使用自定义绑定,因为,AFAIK,这些属性不能与经典绑定绑定:local:MvxBind="Gravity MyPropery"
不起作用,因为没有重力属性。
所以,我当然有两个axml文件:
我按照这些指南创建了三种不同的自定义绑定(背景,重力和重量):
我想要的是,当用户打开聊天视图时,列表小部件会自动显示最后一条消息。为此,我以编程方式将列表滚动到最后一条消息,此似乎是问题所在。
如果我不以编程方式滚动,当我打开页面并手动滚动到页面末尾时,所有自定义绑定都会成功应用:我可以看到左右对齐的消息,并且应用了正确的背景和重量。 / 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);
}
}
环境 最后,这是我的环境:
如果我做错了什么,有人可以帮我理解吗? 感谢阅读和任何帮助!
我通过添加stackFromBottom
和transcriptMode
属性并在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;
}
答案 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);
}
}