Friday, January 31, 2014

ListView Basics for Android (using a CustomAdapter)

If an application requires more than just simple strings in a ListView, the next easiest configuration to use is a Custom Adapter.   Usually, the data to display will come from list of objects which are part of model, representing the data structure.

Here is a very simple data class called People.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;

namespace BlogPostListActivity
{
    class People 
    {
        public string FirstName { get; set; }
        public string LastName { get; set;}
    }
}


When the app runs, the People class will be used to create an object for each of the rows that will be displayed.

Android will manage the scrolling as it did with the Array Adapter, but this time we'll need to create an adapter that will hand off the information from the data objects when requested.  Android will take care of getting them to the right place at the right time as the user scrolls up and down.

Our adapter class will inherit from the BaseAdapter and use a generic type <People> since that is the type of objects that will be displayed.

To function correctly, our adapter will have to override 3 key methods so that Android will have the information it needs at the proper time.


  • Count - Return the total number of objects in the list.
  • GetView - Android will pass the adapter a sequence number that it needs and this method will return a view that is populated with the particular object data that coincides with that sequence in the list of objects.
  • GetItemId - Android will again pass the adapter a sequence number and this method can return an identifier which corresponds to that particular object.  Usually this is just the same number as the sequence in the list of objects.


When the GetView method is called by Android, it will pass the location in memory of the View it needs your method to populate.  As the user scrolls down the screen, new rows will appear and your method will be called to populate them with the corresponding data.  In reality, Android is actually reusing the same memory locations over and over to save memory as the list scrolls.  This makes little difference to your GetView method since it will be building its view wherever Android requests it to, but occasionally it will be passed a null location if this is the first time that that particular row is being populated.  The method will have to test for this condition using an "if", and if it is null, "inflate" a new view in memory before populating it.



PeopleAdapter.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;

namespace BlogPostListActivity
{
    // Inherit from the BaseAdapter class and use a generic type which matches the data objects

    class PeopleAdapter:BaseAdapter<People>
    {

        private List<People> data; // a list of the data objects that will fill the rows
        private Activity context; // a variable that will be passed that ties back to the activity which is using the adapter

        public PeopleAdapter (Activity activity) 
        {
            context = activity; // this is the calling activity that is using the adapter

            // for simplicity, this example will just populate a list of People objects from a hard coded method when it is first created. 
            //  In most cases, the objects will be filled from some more involved data source

            data = PopulatePeopleData ();

        }        

        public override People this[int position]
        {
            get { return data [position]; } //return a particular data object based on position 
        }

        public override long GetItemId(int position)
        {
            return position; // this can be a unique id for each row, but returning the position is the easiest
        }

        public override int Count 
        {
            get { return data.Count; } // this tells Android how many potential rows there will be
        }

        public override View GetView (int position, View rowView, ViewGroup parent)
        {

            // the rowView that is being filled may, or may not exist.  If not, inflate a new one, 
            //  otherwiser reuse the one passed in.
            var view = rowView;
            if (view == null) {
                // ListActivities have built in listviews that fill the whole screen.  
               //  There is no need for an AXML Layout
               // SimpleListItem2 is a predefined style with 2 text views, the first being bolder than the second
                //   no special AXML is required if this style is adequate
                view = context.LayoutInflater.Inflate (Android.Resource.Layout.SimpleListItem2, null);
            }

            var people = data [position];  //get the people object that corresponds to this position

            // locate the 2 predefined text views and load the data from the object into them
            view.FindViewById<TextView> (Android.Resource.Id.Text1).Text = people.LastName;
            view.FindViewById<TextView> (Android.Resource.Id.Text2).Text = people.FirstName;

            return view;  // return the formated view

        }

        private List<People> PopulatePeopleData() {
            // this is just done in this demo for simlicity to create the People objects
            return new List<People> ()
            {
                new People { FirstName = "Anita", LastName = "Drink" },
                new People { FirstName = "Amanda", LastName = "Reconwith" },
                new People { FirstName = "Warren", LastName = "Peace" },
                new People { FirstName = "Rhonda", LastName = "Corner" },
                new People { FirstName = "Karen", LastName = "Feeding" },
                new People { FirstName = "Rufus Lee", LastName = "King" },
                new People { FirstName = "Winston", LastName = "Payne" },
                new People { FirstName = "Marian", LastName = "Haste" },
                new People { FirstName = "Augusta", LastName = "Wind" },
                new People { FirstName = "Eileen", LastName = "Dover" },
                new People { FirstName = "Wendy", LastName = "Lottery" },
                new People { FirstName = "Betty", LastName = "Wont" },
                new People { FirstName = "Marty", LastName = "Graw" }
            };

        }
    }

}


The ListActivity is as follows.

MainActivity.cs

using System;
using System.Collections.Generic;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

namespace BlogPostListActivity
{
    [Activity (Label = "BlogPostListActivity", MainLauncher = true)]

    //Inherit from the ready made ListActivity Class for simplicity
    public class MainActivity : ListActivity
    {
        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            // create a new instance of the PeopleAdapter and pass a reference 

            // to this activity so that the adapter
            // can inflate new rows in the in the built-in 

            // ListView that comes with a ListActivity class
            ListView.Adapter = new PeopleAdapter (this);

        }
    }
}

SimpleListItem2 Style


Changing the adapter to use the TwoLineListItem style produces the following.

view = 
 context.LayoutInflater.Inflate (Android.Resource.Layout.TwoLineListItem, null);


TwoLineListItem Style

The SimpleListItem1 Style shown in the blog post on the ArrayAdapter can also be used, but it will only support the first text item.  There is also an ActivityListItem style which includes an image that will be covered in a later post.

Filtering and sorting can be performed by a CustomAdapter before it is displayed.  For example, modifying the PeopleAdapter to use a linq expression to sort on last name will re-order the data.

 //data = PopulatePeopleData ();
 data = (from p in PopulatePeopleData ()
                orderby p.LastName
                select p).ToList ();



Thursday, January 30, 2014

ListView Basics for Android (using an ArrayAdapter)

Lists of repeating items are fundamental to almost any computer application and date back to the earliest days of programming.  It's difficult to think of an application that won't require them at some point, so it's usually one of the first things to tackle when learning to develop for any new operating system.

In Android, a list of scrollable rows is called a ListView.  To function, the ListView needs a ListAdapter which is a class that will format a view for each row from the desired data when called for by the activity which is controlling the ListView.

The ListAdapter can be one of the following types:
  • ArrayAdapter - Binds to an array of strings
  • CustomAdapter - Binds to a list of objects
  • CursorAdapter - Binds to an SQLite query
The simplest approach to display a list of items is to use the built-in ListActivity class.  It will already contain a ListView which fills the entire screen, and a ListAdapter to bind the data.  No AXML layout is required to define the screen layout.  Using an ArrayAdapter is all that is necessary to get simple data onto the screen.

using System; 
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

namespace BlogPostListActivity
{
    [Activity (Label = "BlogPostListActivity", MainLauncher = true)]
    

    //Inherit from the ready made ListActivity Class for simplicity

    public class MainActivity : ListActivity
    {
        private string[] items;

        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            //create a list of names to display
            items = new string[] {
                "Anita Drink",
                "Amanda Reconwith",
                "Warren Peace",
                "Rhonda Corner",
                "Karen Feeding",
                "Rufus Lee King",
                "Winston Payne",
                "Marian Haste",
                "Augusta Wind",
                "Eileen Dover",
                "Wendy Lottery",
                "Betty Wont",
                "Marty Graw"
            };

            // Set the built-in ListAdapter to a new instance of an ArrayAdapter
            // Set the layout to the pre-defined SimpleListItem1 

           //  and pass it a reference to this activity and the items array
            ListAdapter = 

               new ArrayAdapter<String> 
                           (this, Android.Resource.Layout.SimpleListItem1, items);

        }
    }
}


Nothing further is need to display the array data.