I was just wondering if the functionality of or similar to ExpandableListViews was lurking within the Mvvmmcross framework or is this type of control not applicable with multi platform requirements.
The functionality found at
http://deapsquatter.blogspot.com/2013/02/mvvmcrossdeapextensions.html is cool but not sure if expand/collapse functionality is available.
Any pointers/sample code would be appreciated
This is about it (I placed it into Deepsqautter's code). After I tidied up the code, I will place it somewhere handy, or maybe it's a usefull addition to mvvmcross itself...
The view:
public class BindableExpandableListView : ExpandableListView
{
public BindableExpandableListView(Context context, IAttributeSet attrs)
: this(context, attrs, new BindableExpandableListAdapter(context))
{
}
public BindableExpandableListView(Context context, IAttributeSet attrs, BindableExpandableListAdapter adapter)
: base(context, attrs)
{
var groupTemplateId = MvxAttributeHelpers.ReadAttributeValue(context, attrs,
MvxAndroidBindingResource.Instance
.ListViewStylableGroupId,
AndroidBindingResource.Instance
.BindableListGroupItemTemplateId);
var itemTemplateId = MvxAttributeHelpers.ReadListItemTemplateId(context, attrs);
SetAdapter(adapter);
adapter.GroupTemplateId = groupTemplateId;
adapter.ItemTemplateId = itemTemplateId;
}
// An expandableListView has ExpandableListAdapter as propertyname, but Adapter still exists but is always null.
protected BindableExpandableListAdapter ThisAdapter { get { return ExpandableListAdapter as BindableExpandableListAdapter; } }
private IEnumerable _itemsSource;
[MvxSetToNullAfterBinding]
public virtual IEnumerable ItemsSource
{
get { return ThisAdapter.ItemsSource; }
set { ThisAdapter.ItemsSource = value; }
}
public int ItemTemplateId
{
get { return ThisAdapter.ItemTemplateId; }
set { ThisAdapter.ItemTemplateId = value; }
}
private ICommand _itemClick;
public new ICommand ItemClick
{
get { return _itemClick; }
set { _itemClick = value; if (_itemClick != null) EnsureItemClickOverloaded(); }
}
public ICommand GroupClick { get; set; }
private bool _itemClickOverloaded = false;
private void EnsureItemClickOverloaded()
{
if (_itemClickOverloaded)
return;
_itemClickOverloaded = true;
base.ChildClick += (sender, args) => ExecuteCommandOnItem(this.ItemClick, args.GroupPosition, args.ChildPosition);
}
protected virtual void ExecuteCommandOnItem(ICommand command, int groupPosition, int position)
{
if (command == null)
return;
var item = ThisAdapter.GetRawItem(groupPosition, position);
if (item == null)
return;
if (!command.CanExecute(item))
return;
command.Execute(item);
}
}
and the adapter
public class BindableExpandableListAdapter : MvxAdapter, IExpandableListAdapter
{
private IList _itemsSource;
public BindableExpandableListAdapter(Context context)
: base(context)
{
}
int groupTemplateId;
public int GroupTemplateId
{
get { return groupTemplateId; }
set
{
if (groupTemplateId == value)
return;
groupTemplateId = value;
// since the template has changed then let's force the list to redisplay by firing NotifyDataSetChanged()
if (ItemsSource != null)
NotifyDataSetChanged();
}
}
protected override void SetItemsSource(System.Collections.IEnumerable value)
{
Mvx.Trace("Setting itemssource");
if (_itemsSource == value)
return;
var existingObservable = _itemsSource as INotifyCollectionChanged;
if (existingObservable != null)
existingObservable.CollectionChanged -= OnItemsSourceCollectionChanged;
_itemsSource = value as IList;
var newObservable = _itemsSource as INotifyCollectionChanged;
if (newObservable != null)
newObservable.CollectionChanged += OnItemsSourceCollectionChanged;
if (value != null)
{
// dit weggehaald FlattenAndSetSource(value);
}
else
base.SetItemsSource(null);
}
public int GroupCount { get { return (_itemsSource != null ? _itemsSource.Count : 0); } }
public void OnGroupExpanded(int groupPosition)
{
// do nothing
}
public void OnGroupCollapsed(int groupPosition)
{
// do nothing
}
public bool IsChildSelectable(int groupPosition, int childPosition)
{
return true;
}
public View GetGroupView(int groupPosition, bool isExpanded, View convertView, ViewGroup parent)
{
var item = _itemsSource[groupPosition];
return base.GetBindableView(convertView, item, GroupTemplateId);
}
public long GetGroupId(int groupPosition)
{
return groupPosition;
}
public Java.Lang.Object GetGroup(int groupPosition)
{
return null;
}
public long GetCombinedGroupId(long groupId)
{
return groupId;
}
public long GetCombinedChildId(long groupId, long childId)
{
return childId;
}
public object GetRawItem(int groupPosition, int position)
{
return ((_itemsSource[groupPosition]) as IEnumerable).Cast<object>().ToList()[position];
}
public View GetChildView(int groupPosition, int childPosition, bool isLastChild, View convertView, ViewGroup parent)
{
var sublist = ((_itemsSource[groupPosition]) as IEnumerable).Cast<object>().ToList();
var item = sublist[childPosition];
return base.GetBindableView(convertView, item, ItemTemplateId);
}
public int GetChildrenCount(int groupPosition)
{
return ((_itemsSource[groupPosition]) as IEnumerable).Cast<object>().ToList().Count();
}
public long GetChildId(int groupPosition, int childPosition)
{
return childPosition;
}
public Java.Lang.Object GetChild(int groupPosition, int childPosition)
{
return null;
}
//public object GetRawItem
}
And example XAML code:
<DeapExtensions.Binding.Droid.Views.BindableExpandableListView
android:minWidth="25px"
android:minHeight="25px"
android:id="@+id/toclist"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="ItemsSource Chapters; ItemClick ShowCommand"
local:MvxItemTemplate="@layout/indextocsectionlistitem"
local:GroupItemTemplate="@layout/indextocitem"
android:background="@android:color/white" />
And then even some data example (pseudo code):
public class SubItem {
public String Name {get; set;}
}
// this is the special part: a chapter does not CONTAIN a sublist, but IS a list of subitems.
public class Chapter : List<Subitem> {
public String Name {get; set;}
}
// en some usage code
var chapters = new List<Chapter>();
var chapter = new Chapter(){Name = "chap 1"};
chapter.Add(new SubItem(){Name = " 1"});
chapter.Add(new SubItem(){Name = "item 2"});
chapters.Add(chap);
This is my MvxBindingAttributes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MvxBinding">
<attr name="MvxBind" format="string"/>
<attr name="MvxLang" format="string"/>
</declare-styleable>
<declare-styleable name="MvxControl">
<attr name="MvxTemplate" format="string"/>
</declare-styleable>
<declare-styleable name="MvxListView">
<attr name="MvxItemTemplate" format="string"/>
<attr name="MvxDropDownItemTemplate" format="string"/>
<attr name="GroupItemTemplate" format="string"/>
</declare-styleable>
<item type="id" name="MvxBindingTagUnique"/>
<declare-styleable name="MvxImageView">
<attr name="MvxSource" format="string"/>
</declare-styleable>
</resources>
BTW: I have only one project left in MvvmCross, the rest has been converted to Xamarin.Forms or even away from Xamarin. So code is not updated anymore.
To my knowledge no-one has done this before
But you can convert an existing Android control into a bound control fairly simply - you've seen exactly how it's done in @deapsquatter's repo.
To convert an ExpandableListView I would take it one step at a time.
- First get the data in the structure you want to use
- Then handcraft a non-bound UI - just rapid prototyping
- Finally take @deapsquatter's sample and try to apply the same principles.
When/if you hit specific issues, then you can always come back here to ask about those issues. But by having 1 and 2 in place first, then you'll have something solid to ask about.
Hope that helps.