Merge pull request #2110 from MvvmCross/bugfix/listview-multiitems

Simplify MvxAdapter implemetation
This commit is contained in:
Martijn van Dijk 2017-08-10 10:12:32 +02:00 коммит произвёл GitHub
Родитель 742ca6967d 03c118800a
Коммит 46f0c7e11c
10 изменённых файлов: 157 добавлений и 227 удалений

Просмотреть файл

@ -28,7 +28,7 @@ namespace MvvmCross.Droid.Support.V7.AppCompat.Widget
: this(context, attrs,
new MvxAdapter(context)
{
SimpleViewLayoutId = Android.Resource.Layout.SimpleSpinnerItem,
ItemTemplateId = Android.Resource.Layout.SimpleSpinnerItem,
DropDownItemTemplateId = Resource.Layout.support_simple_spinner_dropdown_item
})
{
@ -61,9 +61,9 @@ namespace MvvmCross.Droid.Support.V7.AppCompat.Widget
if (existing != null && value != null)
{
value.ItemsSource = existing.ItemsSource;
value.ItemTemplateId = existing.ItemTemplateId;
value.DropDownItemTemplateId = existing.DropDownItemTemplateId;
value.ItemsSource = existing.ItemsSource;
}
base.Adapter = value;

Просмотреть файл

@ -129,6 +129,7 @@
<Compile Include="Views\MvxAutoCompleteTextView.cs" />
<Compile Include="Views\MvxLayoutInflater.cs" />
<Compile Include="Views\MvxRadioGroup.cs" />
<Compile Include="Views\MvxSimpleListItemView.cs" />
<Compile Include="Views\MvxSpinner.cs" />
<Compile Include="Views\MvxFilteringAdapter.cs" />
<Compile Include="Views\IMvxListItemView.cs" />

Просмотреть файл

@ -15,8 +15,6 @@ namespace MvvmCross.Binding.Droid.Views
: ISpinnerAdapter
, IListAdapter
{
int SimpleViewLayoutId { get; set; }
[MvxSetToNullAfterBinding]
IEnumerable ItemsSource { get; set; }

Просмотреть файл

@ -9,6 +9,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Android.Content;
using Android.Runtime;
using Android.Views;
@ -24,27 +25,21 @@ using Object = Java.Lang.Object;
namespace MvvmCross.Binding.Droid.Views
{
public class MvxAdapter
: BaseAdapter
, IMvxAdapter
{
private int _itemTemplateId;
private int _dropDownItemTemplateId;
{
private static int[] SimpleItemTemplateIds { get; } =
{
Android.Resource.Layout.SimpleListItem1,
Android.Resource.Layout.SimpleSpinnerItem
};
private int _itemTemplateId = Android.Resource.Layout.SimpleListItem1;
private int _dropDownItemTemplateId = Android.Resource.Layout.SimpleSpinnerDropDownItem;
private IEnumerable _itemsSource;
private IDisposable _subscription;
// Note - _currentSimpleId is a bit of a hack
// - it's essentially a state flag identifying wheter we are currently inflating a dropdown or a normal view
// - ideally we would have passed the current simple id as a parameter through the GetView chain instead
// - but that was too big a breaking change for this release (7th Oct 2013)
private int _currentSimpleId;
// Note2 - _currentParent is similarly a bit of a hack
// - it is just here to avoid a breaking api change for now
// - will seek to remove both the _currentSimpleId and _currentParent private fields in a major release soon
private ViewGroup _currentParent;
public MvxAdapter(Context context)
: this(context, MvxAndroidBindingContextHelpers.Current())
{
@ -57,10 +52,10 @@ namespace MvvmCross.Binding.Droid.Views
if (BindingContext == null)
{
throw new MvxException(
"bindingContext is null during MvxAdapter creation - Adapter's should only be created when a specific binding context has been placed on the stack");
"bindingContext is null during MvxAdapter creation - " +
"Adapter's should only be created when a specific binding " +
"context has been placed on the stack");
}
SimpleViewLayoutId = Android.Resource.Layout.SimpleListItem1;
SimpleDropDownViewLayoutId = Android.Resource.Layout.SimpleSpinnerDropDownItem;
}
protected MvxAdapter(IntPtr javaReference, JniHandleOwnership transfer)
@ -72,56 +67,38 @@ namespace MvvmCross.Binding.Droid.Views
protected IMvxAndroidBindingContext BindingContext { get; }
public int SimpleViewLayoutId { get; set; }
public int SimpleDropDownViewLayoutId { get; set; }
public bool ReloadOnAllItemsSourceSets { get; set; }
[MvxSetToNullAfterBinding]
public virtual IEnumerable ItemsSource
{
get { return _itemsSource; }
set { SetItemsSource(value); }
get => _itemsSource;
set => SetItemsSource(value);
}
public virtual int ItemTemplateId
{
get
{
return _itemTemplateId;
}
set
{
if (_itemTemplateId == value)
return;
_itemTemplateId = value;
// since the template has changed then let's force the list to redisplay by firing NotifyDataSetChanged()
if (_itemsSource != null)
NotifyDataSetChanged();
}
get => _itemTemplateId;
set => SetItemTemplate(ref _itemTemplateId, value);
}
public virtual int DropDownItemTemplateId
{
get
{
return _dropDownItemTemplateId;
}
set
{
if (_dropDownItemTemplateId == value)
return;
_dropDownItemTemplateId = value;
// since the template has changed then let's force the list to redisplay by firing NotifyDataSetChanged()
if (_itemsSource != null)
NotifyDataSetChanged();
}
get => _dropDownItemTemplateId;
set => SetItemTemplate(ref _dropDownItemTemplateId, value);
}
public override int Count => _itemsSource.Count();
private void SetItemTemplate(ref int templateId, int newTemplateId)
{
if (templateId == newTemplateId) return;
templateId = newTemplateId;
if (ItemsSource != null)
NotifyDataSetChanged();
}
public override int Count => ItemsSource.Count();
protected virtual void SetItemsSource(IEnumerable value)
{
@ -129,23 +106,20 @@ namespace MvvmCross.Binding.Droid.Views
&& !ReloadOnAllItemsSourceSets)
return;
if (_subscription != null)
{
_subscription.Dispose();
_subscription = null;
}
_subscription?.Dispose();
_subscription = null;
_itemsSource = value;
if (_itemsSource != null && !(_itemsSource is IList))
MvxBindingTrace.Trace(MvxTraceLevel.Warning,
"You are currently binding to IEnumerable - this can be inefficient, especially for large collections. Binding to IList is more efficient.");
"You are currently binding to IEnumerable - " +
"this can be inefficient, especially for large collections. " +
"Binding to IList is more efficient.");
var newObservable = _itemsSource as INotifyCollectionChanged;
if (newObservable != null)
{
_subscription = newObservable.WeakSubscribe(OnItemsSourceCollectionChanged);
}
if (_itemsSource is INotifyCollectionChanged newObservable)
_subscription = newObservable?.WeakSubscribe(OnItemsSourceCollectionChanged);
NotifyDataSetChanged();
}
@ -173,19 +147,21 @@ namespace MvvmCross.Binding.Droid.Views
catch (Exception exception)
{
Mvx.Warning(
"Exception masked during Adapter RealNotifyDataSetChanged {0}. Are you trying to update your collection from a background task? See http://goo.gl/0nW0L6",
"Exception masked during Adapter RealNotifyDataSetChanged " +
"{0}. Are you trying to update your collection from a " +
"background task? See http://goo.gl/0nW0L6",
exception.ToLongString());
}
}
public virtual int GetPosition(object item)
{
return _itemsSource.GetPosition(item);
return ItemsSource.GetPosition(item);
}
public virtual object GetRawItem(int position)
{
return _itemsSource.ElementAt(position);
return ItemsSource.ElementAt(position);
}
public override Object GetItem(int position)
@ -201,27 +177,15 @@ namespace MvvmCross.Binding.Droid.Views
return position;
}
public override View GetDropDownView(int position, View convertView, ViewGroup parent)
{
_currentSimpleId = SimpleDropDownViewLayoutId;
_currentParent = parent;
var toReturn = GetView(position, convertView, parent, DropDownItemTemplateId);
_currentParent = null;
return toReturn;
}
public override View GetDropDownView(int position, View convertView, ViewGroup parent)
=> GetView(position, convertView, parent, DropDownItemTemplateId);
public override View GetView(int position, View convertView, ViewGroup parent)
{
_currentSimpleId = SimpleViewLayoutId;
_currentParent = parent;
var toReturn = GetView(position, convertView, parent, ItemTemplateId);
_currentParent = null;
return toReturn;
}
public override View GetView(int position, View convertView, ViewGroup parent)
=> GetView(position, convertView, parent, ItemTemplateId);
protected virtual View GetView(int position, View convertView, ViewGroup parent, int templateId)
{
if (_itemsSource == null)
if (ItemsSource == null)
{
MvxBindingTrace.Trace(MvxTraceLevel.Error, "GetView called when ItemsSource is null");
return null;
@ -232,111 +196,69 @@ namespace MvvmCross.Binding.Droid.Views
return GetBindableView(convertView, source, parent, templateId);
}
protected virtual View GetSimpleView(View convertView, object dataContext)
protected virtual View GetBindableView(
View convertView, object dataContext, ViewGroup parent, int templateId)
{
if (convertView == null)
IMvxListItemView viewToUse = null;
// we have a templateid lets use bind and inflate on it :)
if (convertView?.Tag is IMvxListItemView item &&
item.TemplateId == templateId)
{
convertView = CreateSimpleView(dataContext);
viewToUse = item;
}
else
{
BindSimpleView(convertView, dataContext);
}
return convertView;
}
protected virtual void BindSimpleView(View convertView, object dataContext)
{
var textView = convertView as TextView;
if (textView != null)
{
textView.Text = (dataContext ?? string.Empty).ToString();
}
}
protected virtual View CreateSimpleView(object dataContext)
{
// note - this could technically be a non-binding inflate - but the overhead is minimal
// note - it's important to use `false` for the attachToRoot argument here
// see discussion in https://github.com/MvvmCross/MvvmCross/issues/507
var view = BindingContext.BindingInflate(_currentSimpleId, _currentParent, false);
BindSimpleView(view, dataContext);
return view;
}
//protected virtual View GetBindableView(View convertView, object dataContext)
//{
// return GetBindableView(convertView, dataContext, ItemTemplateId);
//}
protected virtual View GetBindableView(View convertView, object dataContext, ViewGroup parent, int templateId)
{
if (templateId == 0)
{
// no template seen - so use a standard string view from Android and use ToString()
return GetSimpleView(convertView, dataContext);
}
// we have a templateid so lets use bind and inflate on it :)
var viewToUse = convertView?.Tag as IMvxListItemView;
if (viewToUse != null)
{
if (viewToUse.TemplateId != templateId)
{
viewToUse = null;
}
}
if (viewToUse == null)
{
viewToUse = CreateBindableView(dataContext, parent, templateId);
viewToUse.Content.Tag = viewToUse as Object;
}
else
{
BindBindableView(dataContext, viewToUse);
}
BindBindableView(dataContext, viewToUse);
return viewToUse.Content;// as View;
}
protected virtual void BindBindableView(object source, IMvxListItemView viewToUse)
{
viewToUse.DataContext = source;
}
protected virtual void BindBindableView(
object source, IMvxListItemView viewToUse)
=> viewToUse.DataContext = source;
protected virtual IMvxListItemView CreateBindableView(object dataContext, ViewGroup parent, int templateId)
protected virtual IMvxListItemView CreateBindableView(
object dataContext, ViewGroup parent, int templateId)
{
return new MvxListItemView(Context, BindingContext.LayoutInflaterHolder, dataContext, parent, templateId);
if (SimpleItemTemplateIds.Contains(templateId) ||
Android.Resource.Layout.SimpleSpinnerDropDownItem == templateId)
{
return new MvxSimpleListItemView(Context, BindingContext.LayoutInflaterHolder,
dataContext, parent, templateId);
}
return new MvxListItemView(Context, BindingContext.LayoutInflaterHolder,
dataContext, parent, templateId);
}
}
public class MvxAdapter<TItem> : MvxAdapter where TItem : class
{
public MvxAdapter(Context context) : base(context, MvxAndroidBindingContextHelpers.Current())
public MvxAdapter(Context context)
: base(context, MvxAndroidBindingContextHelpers.Current())
{
}
public MvxAdapter(Context context, IMvxAndroidBindingContext bindingContext) : base(context, bindingContext)
public MvxAdapter(Context context, IMvxAndroidBindingContext bindingContext)
: base(context, bindingContext)
{
}
public MvxAdapter(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
public MvxAdapter(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
[MvxSetToNullAfterBinding]
public new IEnumerable<TItem> ItemsSource
{
get
{
return base.ItemsSource as IEnumerable<TItem>;
}
set
{
base.ItemsSource = value;
}
get => base.ItemsSource as IEnumerable<TItem>;
set => base.ItemsSource = value;
}
}
}
}
}

Просмотреть файл

@ -29,7 +29,9 @@ namespace MvvmCross.Binding.Droid.Views
: base(context, attrs)
{
var itemTemplateId = MvxAttributeHelpers.ReadListItemTemplateId(context, attrs);
adapter.ItemTemplateId = itemTemplateId;
if (itemTemplateId > 0)
adapter.ItemTemplateId = itemTemplateId;
Adapter = adapter;
// note - we shouldn't realy need both of these... but we do

Просмотреть файл

@ -37,8 +37,11 @@ namespace MvvmCross.Binding.Droid.Views
SetAdapter(adapter);
adapter.GroupTemplateId = groupTemplateId;
adapter.ItemTemplateId = itemTemplateId;
if (groupTemplateId > 0)
adapter.GroupTemplateId = groupTemplateId;
if (itemTemplateId > 0)
adapter.ItemTemplateId = itemTemplateId;
}
protected MvxExpandableListView(IntPtr javaReference, JniHandleOwnership transfer)

Просмотреть файл

@ -16,36 +16,29 @@ using Object = Java.Lang.Object;
namespace MvvmCross.Binding.Droid.Views
{
[Register("mvvmcross.binding.droid.views.MvxListItemView")]
public class MvxListItemView
: Object
, IMvxListItemView
, IMvxBindingContextOwner
, View.IOnAttachStateChangeListener
public class MvxListItemView : Object, IMvxListItemView,
IMvxBindingContextOwner, View.IOnAttachStateChangeListener
{
private readonly IMvxAndroidBindingContext _bindingContext;
private View _content;
private object _cachedDataContext;
private bool _isAttachedToWindow;
public MvxListItemView(Context context,
IMvxLayoutInflaterHolder layoutInflaterHolder,
object dataContext,
ViewGroup parent,
int templateId)
IMvxLayoutInflaterHolder layoutInflaterHolder, object dataContext,
ViewGroup parent, int templateId)
{
_bindingContext = new MvxAndroidBindingContext(context, layoutInflaterHolder, dataContext);
TemplateId = templateId;
Content = _bindingContext.BindingInflate(templateId, parent, false);
}
private object _cachedDataContext;
private bool _isAttachedToWindow;
public void OnViewAttachedToWindow(View attachedView)
{
_isAttachedToWindow = true;
if (_cachedDataContext != null
&& DataContext == null)
{
if (_cachedDataContext != null && DataContext == null)
DataContext = _cachedDataContext;
}
}
public void OnViewDetachedFromWindow(View detachedView)
@ -57,17 +50,13 @@ namespace MvvmCross.Binding.Droid.Views
public IMvxBindingContext BindingContext
{
get { return _bindingContext; }
set { throw new NotImplementedException("BindingContext is readonly in the list item"); }
get => _bindingContext;
set => throw new NotImplementedException("BindingContext is readonly in the list item");
}
private View _content;
public View Content
{
get
{
return _content;
}
get => _content;
set
{
_content = value;
@ -75,12 +64,9 @@ namespace MvvmCross.Binding.Droid.Views
}
}
public object DataContext
public virtual object DataContext
{
get
{
return _bindingContext.DataContext;
}
get => _bindingContext.DataContext;
set
{
if (_isAttachedToWindow)
@ -90,14 +76,11 @@ namespace MvvmCross.Binding.Droid.Views
else
{
_cachedDataContext = value;
if (_bindingContext.DataContext != null)
{
_bindingContext.DataContext = null;
}
_bindingContext.DataContext = null;
}
}
}
public int TemplateId { get; }
public int TemplateId { get; protected set; }
}
}

Просмотреть файл

@ -40,7 +40,8 @@ namespace MvvmCross.Binding.Droid.Views
return;
var itemTemplateId = MvxAttributeHelpers.ReadListItemTemplateId(context, attrs);
adapter.ItemTemplateId = itemTemplateId;
if (itemTemplateId > 0)
adapter.ItemTemplateId = itemTemplateId;
Adapter = adapter;
}
@ -51,10 +52,7 @@ namespace MvvmCross.Binding.Droid.Views
public new IMvxAdapter Adapter
{
get
{
return base.Adapter as IMvxAdapter;
}
get => base.Adapter as IMvxAdapter;
set
{
var existing = Adapter;
@ -63,8 +61,8 @@ namespace MvvmCross.Binding.Droid.Views
if (value != null && existing != null)
{
value.ItemsSource = existing.ItemsSource;
value.ItemTemplateId = existing.ItemTemplateId;
value.ItemsSource = existing.ItemsSource;
}
base.Adapter = value;
@ -77,22 +75,19 @@ namespace MvvmCross.Binding.Droid.Views
[MvxSetToNullAfterBinding]
public IEnumerable ItemsSource
{
get { return Adapter.ItemsSource; }
set { Adapter.ItemsSource = value; }
get => Adapter.ItemsSource;
set => Adapter.ItemsSource = value;
}
public int ItemTemplateId
{
get { return Adapter.ItemTemplateId; }
set { Adapter.ItemTemplateId = value; }
get => Adapter.ItemTemplateId;
set => Adapter.ItemTemplateId = value;
}
public new ICommand ItemClick
{
get
{
return _itemClick;
}
get => _itemClick;
set
{
_itemClick = value;
@ -112,10 +107,7 @@ namespace MvvmCross.Binding.Droid.Views
public new ICommand ItemLongClick
{
get
{
return _itemLongClick;
}
get => _itemLongClick;
set
{
_itemLongClick = value;
@ -149,14 +141,10 @@ namespace MvvmCross.Binding.Droid.Views
}
private void OnItemClick(object sender, ItemClickEventArgs e)
{
ExecuteCommandOnItem(ItemClick, e.Position);
}
=> ExecuteCommandOnItem(ItemClick, e.Position);
private void OnItemLongClick(object sender, ItemLongClickEventArgs e)
{
ExecuteCommandOnItem(ItemLongClick, e.Position);
}
=> ExecuteCommandOnItem(ItemLongClick, e.Position);
protected override void Dispose(bool disposing)
{

Просмотреть файл

@ -0,0 +1,29 @@
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
namespace MvvmCross.Binding.Droid.Views
{
[Register("mvvmcross.binding.droid.views.MvxSimpleListItemView")]
public class MvxSimpleListItemView : MvxListItemView
{
public MvxSimpleListItemView(Context context, IMvxLayoutInflaterHolder layoutInflaterHolder,
object dataContext, ViewGroup parent, int templateId)
: base(context, layoutInflaterHolder, dataContext, parent, templateId)
{
}
public override object DataContext
{
get => base.DataContext;
set
{
var context = base.DataContext = value;
var textView = Content as TextView;
if (textView != null)
textView.Text = context?.ToString();
}
}
}
}

Просмотреть файл

@ -24,7 +24,7 @@ namespace MvvmCross.Binding.Droid.Views
context, attrs,
new MvxAdapter(context)
{
SimpleViewLayoutId = Android.Resource.Layout.SimpleSpinnerItem,
ItemTemplateId = Android.Resource.Layout.SimpleSpinnerItem,
DropDownItemTemplateId = Android.Resource.Layout.SimpleSpinnerDropDownItem
})
{
@ -35,8 +35,12 @@ namespace MvvmCross.Binding.Droid.Views
{
var itemTemplateId = MvxAttributeHelpers.ReadListItemTemplateId(context, attrs);
var dropDownItemTemplateId = MvxAttributeHelpers.ReadDropDownListItemTemplateId(context, attrs);
adapter.ItemTemplateId = itemTemplateId;
adapter.DropDownItemTemplateId = dropDownItemTemplateId;
if (itemTemplateId > 0)
adapter.ItemTemplateId = itemTemplateId;
if (dropDownItemTemplateId > 0)
adapter.DropDownItemTemplateId = dropDownItemTemplateId;
Adapter = adapter;
ItemSelected += OnItemSelected;
}