【发布时间】: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 的项目模板
我按照这些指南创建了三种不同的自定义绑定(背景、重力和重量):
- http://slodge.blogspot.it/2013/06/n28-custom-bindings-n1-days-of-mvvmcross.html
- In MvvmCross how do I do custom bind properties
问题
我希望,当用户打开聊天视图时,列表小部件会自动显示最后一条消息。为此,我以编程方式将列表滚动到最后一条消息,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
我通过添加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;
}
【问题讨论】:
-
在你的视图模型中使用 observable 集合而不是 ienumerable
-
嗨@xleon,我已经尝试过你的建议,但没有任何改变。请注意,如果我有一个少于 10 个项目的列表,那么我当前的代码一切正常
-
它不是项目的数量,而是您的自定义绑定的问题,直到项目被刷新我认为。我使用自定义 mvxadapter 而不是自定义绑定。没有问题
-
绑定设置后是否尝试了list.Invalidate()?
-
感谢您写了一个好问题。我不相信您所看到的与“自定义绑定”有关。 Mvx 自定义绑定与内置绑定具有完全相同的状态。我猜你所看到的可能与 Android 控件本身的行为更相关。例如在您的问题示例中,我猜想
weight在您滚动之前应用 - 但直到滚动 Android 布局,直到您的滚动导致它被重新计算或重新应用(或两者)。要测试这个...
标签: mvvmcross