Introduction
As I'm keep learning Xamarin cross platform , I was developing sample app which has recycler view and the view has nested controls like spinner and checkboxes. I was finding difficult to dynamically update the view row when I changed with spinner drop down value. So, I came up with a idea to event listner to the spinner drop down and pass the event listner to the main activity. In the main activity I can just update the underlying dataset and then there is option called notifyitemchanged, which will update the view row recycler list, based on the index provided.
Using the code
To start with, this article is about creating a recycler view in Xamarin Android and then adding spinner control on the each row and updating the list row dynamically based on the spinner drop down value selected. The basic concept of how to recycler view is explained on the Xamarin guid page very neatly (https://developer.xamarin.com/guides/android/user_interface/recyclerview/). My explanation here is limited to show how to add nested controls on the recycler view row and listen to the event listener of those nested controls and update the list row.
Let's start with the code; I have created ViewHolder
class, which will have reference to the view objects. The view holder class has spinner object and then the spinner object is assigned to the event "ItemSelected
" in the ViewHolder
class constructor. Here we are registering event handlers (in this case itemlistner and spinnerItemSelected
) with the View objects. Note that ItemSelected
event is an event called after the user has selected the value from the spinner drop down list and also I have added another event handler (i.e itemlistner) which tell which row is touched in the recycler view. Below is the complete example of the View Holder class.
public class VegeViewHolder: RecyclerView.ViewHolder
{
public ImageView Image { get; set; }
public TextView Name { get; set; }
public Spinner Quantity { get; set; }
public TextView Price { get; set; }
public TextView TotalAmount { get; set; }
public VegeViewHolder(View itemView, Action <int > itemlistner, Action <object,AdapterView.ItemSelectedEventArgs > spinnerItemSelected )
:base(itemView)
{
Image = itemView.FindViewById <ImageView > (Resource.Id.list_image);
Name = itemView.FindViewById <TextView > (Resource.Id.Name);
Price = itemView.FindViewById <TextView > (Resource.Id.Price);
Quantity = itemView.FindViewById <Spinner > (Resource.Id.spinner1);
TotalAmount = itemView.FindViewById <TextView > (Resource.Id.total);
itemView.Click += (sender, e) = > itemlistner (base.Position);
Quantity.ItemSelected+= new EventHandler <AdapterView.ItemSelectedEventArgs > (spinnerItemSelected);
}
}
Let's create a custom adapter class for the recycler view, Most of the "heavy-lifting" of our RecyclerView
integration code takes place in the adapter. RecyclerView
requires that we provide an adapter derived from RecyclerView.Adapter
to access our data source and populate each item with content from the data source. In this adapter class I have created a event handler which is assigned to the spinner control.
public event EventHandler <int > ItemClick;
public event EventHandler <AdapterView.ItemSelectedEventArgs > SpinnerItemSelectionChanged;
When implementing the recycler adapter we must override the following methods
OnCreateViewHolder
– Instantiates the item layout file and view holder. OnBindViewHolder
– Loads the data at the specified position into the views whose references are stored in the given view holder. ItemCount
– Returns the number of items in the data source.
The layout manager calls these methods while it is positioning items within the RecyclerView
. More detail information can be found at Xamarin Recycler view guide. Let's create the OnCreateViewHolder
method and Instantiates the view holder class and pass the event listener to the constructor.
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.From(parent.Context).
Inflate(Resource.Layout.list_items, parent, false);
VegeViewHolder vh = new VegeViewHolder(itemView, OnClick,spinner_ItemSelected);
return vh;
}
void OnClick(int position)
{
if (ItemClick != null)
ItemClick (this, position);
}
void spinner_ItemSelected (object sender, AdapterView.ItemSelectedEventArgs e)
{
if (SpinnerItemSelectionChanged != null)
SpinnerItemSelectionChanged (sender, e);
}
Now, lets create OnBindViewHolder
. When the layout manager is ready to display a particular view in the RecyclerView
's visible screen area, it calls the adapter's OnBindViewHolder
method to fill the item at the specified row position with content from the data source.
public override void OnBindViewHolder(RecyclerView.ViewHolder viewHolder, int position)
{
var item = Items[position];
var vh = viewHolder as VegeViewHolder;
var spinnerPos = 0;
var adapter =new ArrayAdapter<String>(Context, Android.Resource.Layout.SimpleSpinnerItem, _quantity);
adapter.SetDropDownViewResource (Android.Resource.Layout.SimpleSpinnerDropDownItem);
vh.Name.Text = item.Name;
vh.Price.Text = string.Format("Price: ${0}",item.Price);
vh.ItemView.Tag = position;
if (item.Quantity > 0) {
spinnerPos = adapter.GetPosition (item.Quantity.ToString ());
vh.TotalAmount.Text = string.Format ("${0}", item.Price * item.Quantity);
} else {
vh.TotalAmount.Text = "";
}
vh.Quantity.Tag = position;
vh.Quantity.Adapter = adapter;
vh.Quantity.SetSelection (spinnerPos);
vh.Image.SetImageResource (item.ImageId);
}
In the above code, at very high level, we are assigning values to the view objects at a specific row position with the values from the dataset.
Now its time to assign these event handlers to the main activity class. Therefore whenever user touches or change spinner value, these events are handled at activity class level. we'll create an item-click event handler method and SpinnerItemSelectionChangedEvent
in our MainActivity
. This handler briefly displays a toast that indicates which item row was touched and when spinner value is changed it toasts about the value selected from spinner drop down:
void OnItemClick(object sender, int position)
{
int itemPosition = position + 1;
Toast.MakeText(this, "Vegetable Item " + itemPosition, ToastLength.Short).Show();
}
void SpinnerItemSelectionChangedEvent(object sender, AdapterView.ItemSelectedEventArgs e)
{
Spinner spinner = (Spinner)sender;
var itemPosition = Convert.ToInt32 (spinner.Tag);
var currentquantity = vegeList[itemPosition].Quantity;
var selectedQuantity = Convert.ToInt32( spinner.GetItemAtPosition (e.Position).ToString());
if (currentquantity != selectedQuantity) {
vegeList [itemPosition].Quantity = selectedQuantity;
mAdapter.NotifyItemChanged (itemPosition);
}
}
From the above code SpinnerItemSelectionChangedEvent
, as you can see we implementing notifyitemchanged
when the Quantity(i.e spinner object) value has been changed.
NotifyItemChanged
– Signals that the item at the specified position has changed.
Once the NotifyItemChanged
is called, the recyclerview at the specified position will be updated/redrawn. The trick here is to make sure NotifyItemChanged
is called only if the oldvalue is not same as new value. Other wise, the adapter will go infinite loop, because when the item row is redrawn and assigning the event handler to spinner object, the SpinnerItemSelectionChangedEvent
is always triggered which then triggers NotifyItemChanged
. Therefore have to make sure NotifyItemChanged
is called only when the dataset values are different to the current value to update the row.
That's all about coding part, see below the end result.
Final Result
Final images here:
Points of Interest
What we have tried to do in this article is understand what is how to handle events of the nested controls placed inside android recycler view. It just not only the spinner control as explained in the above example, we can also implement event handlers to the checkbox, button, edit text controls when it placed on the recycler view.
History
SEP 2015 - Initial document